Separate client dependencies from general project dependencies;

Vendor / lazy load some dependencies
This commit is contained in:
Martin Kleinschrodt 2018-06-01 15:32:16 +02:00
parent bffb2648b8
commit f7bb477baa
28 changed files with 4122 additions and 864 deletions

View File

@ -26,7 +26,6 @@
"no-loop-func": "error",
"no-multi-spaces": "error",
"no-multiple-empty-lines": ["error", { "max": 1, "maxBOF": 1, "maxEOF": 0 }],
"no-spaced-func": "error",
"no-trailing-spaces": "error",
"no-undef": "error",
"no-unsafe-finally": "error",
@ -37,7 +36,6 @@
"semi": "error",
"semi-spacing": "error",
"space-before-blocks": "error",
"space-before-function-paren": ["warn", "never"],
"space-in-parens": ["error", "never"],
"space-infix-ops": "warn",
"space-unary-ops": "error",

View File

@ -12,7 +12,7 @@
<link rel="stylesheet" href="./src/styles/fonts.css">
<script type="module" src="../node_modules/@webcomponents/webcomponentsjs/webcomponents-bundle.js"></script>
<script type="module" src="node_modules/@webcomponents/webcomponentsjs/webcomponents-bundle.js"></script>
<script type="module" src="./src/elements/app.js"></script>
</head>

108
app/package-lock.json generated Normal file
View File

@ -0,0 +1,108 @@
{
"name": "padlock-app",
"version": "3.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@polymer/font-roboto": {
"version": "3.0.0-pre.19",
"resolved": "https://registry.npmjs.org/@polymer/font-roboto/-/font-roboto-3.0.0-pre.19.tgz",
"integrity": "sha512-brLUbgLGzBVNlmHhpDHnWdPI6KNkHmTjtX+82dvHpeuUjPsey2uH4fFofWC+m29/yxY+GH5RGv493AfRUDcokQ=="
},
"@polymer/iron-a11y-keys-behavior": {
"version": "3.0.0-pre.19",
"resolved": "https://registry.npmjs.org/@polymer/iron-a11y-keys-behavior/-/iron-a11y-keys-behavior-3.0.0-pre.19.tgz",
"integrity": "sha512-jti4YDsjxTny2AJJxAQ89Gwq7+AMblOZLWBOQiciqDuRdj+nEelf227DAUwIejoII6cYQCV/Knw+Qit5zZx2kQ==",
"requires": {
"@polymer/polymer": "^3.0.0"
}
},
"@polymer/iron-flex-layout": {
"version": "3.0.0-pre.19",
"resolved": "https://registry.npmjs.org/@polymer/iron-flex-layout/-/iron-flex-layout-3.0.0-pre.19.tgz",
"integrity": "sha512-EZ25vUjmZXLc0hKJj338Ec1fQwemBsfe+Zo5d6ThzJzoSq72uGlZ8deLYNiAhIw6UZZxWttXcN6c/2kZ4qV18A==",
"requires": {
"@polymer/polymer": "^3.0.0"
}
},
"@polymer/iron-list": {
"version": "3.0.0-pre.19",
"resolved": "https://registry.npmjs.org/@polymer/iron-list/-/iron-list-3.0.0-pre.19.tgz",
"integrity": "sha512-NoFja6NeGJNnBdQl2MyD1ZdcKURCfLVozLcsrFmh92R4bnPfBa9l/ood2vec8LTEYidRjl1US1aPagd6bJhdrw==",
"requires": {
"@polymer/iron-a11y-keys-behavior": "^3.0.0-pre.19",
"@polymer/iron-resizable-behavior": "^3.0.0-pre.19",
"@polymer/iron-scroll-target-behavior": "^3.0.0-pre.19",
"@polymer/polymer": "^3.0.0"
}
},
"@polymer/iron-resizable-behavior": {
"version": "3.0.0-pre.19",
"resolved": "https://registry.npmjs.org/@polymer/iron-resizable-behavior/-/iron-resizable-behavior-3.0.0-pre.19.tgz",
"integrity": "sha512-lEpkbPPYtxMLW0SYTRxblHqvTus9M0VAhRTYA1ctz22vi06JrlBdE7NizA9hQwYLpaWU/vibGBJ7afaP/AM3Tg==",
"requires": {
"@polymer/polymer": "^3.0.0"
}
},
"@polymer/iron-scroll-target-behavior": {
"version": "3.0.0-pre.19",
"resolved": "https://registry.npmjs.org/@polymer/iron-scroll-target-behavior/-/iron-scroll-target-behavior-3.0.0-pre.19.tgz",
"integrity": "sha512-hD/ztlrjo8aQRPey0ekJYGtE4ikysMLc0N/1vEwxlcg50LYT/BPkaepwgB598gTq02dDwGMhJr2heXusRshhzA==",
"requires": {
"@polymer/polymer": "^3.0.0"
}
},
"@polymer/paper-spinner": {
"version": "3.0.0-pre.19",
"resolved": "https://registry.npmjs.org/@polymer/paper-spinner/-/paper-spinner-3.0.0-pre.19.tgz",
"integrity": "sha512-ZPbTZRZBYTL8jUqoFYkDzFRMT4wlM4REgJVV3cTf2CNWcdNNKiZt6ynJyEgjZwl2NBD+RL+x6NkrWO5cVuH37Q==",
"requires": {
"@polymer/paper-styles": "^3.0.0-pre.19",
"@polymer/polymer": "^3.0.0"
}
},
"@polymer/paper-styles": {
"version": "3.0.0-pre.19",
"resolved": "https://registry.npmjs.org/@polymer/paper-styles/-/paper-styles-3.0.0-pre.19.tgz",
"integrity": "sha512-ASspmHr6GJjDiPxOVsqR2mFSQim13nGy03iLeyBXxX9QQa1rXI6r16/HShBvg8MrncQSRRUHqkFCfRWO3CKpwA==",
"requires": {
"@polymer/font-roboto": "^3.0.0-pre.19",
"@polymer/iron-flex-layout": "^3.0.0-pre.19",
"@polymer/polymer": "^3.0.0"
}
},
"@polymer/polymer": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@polymer/polymer/-/polymer-3.0.2.tgz",
"integrity": "sha512-ow8AAjTe9ps8bantY9IvL0PT+xHf5VN3Cjahfr7gBJAc0lv3jTwGBv7pso65SHyrUJEEHeakhx6iPMl7qY4tfw==",
"requires": {
"@webcomponents/shadycss": "^1.2.0"
}
},
"@webcomponents/shadycss": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@webcomponents/shadycss/-/shadycss-1.2.1.tgz",
"integrity": "sha512-ZnjFb1Wf81CqMgs1dbT6JT6N4nY32bs2r5YC/5GSIb/ozJGR+UYs3ldJxtk1v5uZzPRIsAWv7wYSJcCp6ml8+A=="
},
"@webcomponents/webcomponentsjs": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@webcomponents/webcomponentsjs/-/webcomponentsjs-2.0.0.tgz",
"integrity": "sha512-f6izmesHd8E+rL32tftlXZmz1EAL+lLn8AUex0mJFLe39z53iTFNCqxAYsMmCA/QSGP+8X6D1it/JbuVl1jOHA=="
},
"autosize": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/autosize/-/autosize-4.0.2.tgz",
"integrity": "sha512-jnSyH2d+qdfPGpWlcuhGiHmqBJ6g3X+8T+iRwFrHPLVcdoGJE/x6Qicm6aDHfTsbgZKxyV8UU/YB2p4cjKDRRA=="
},
"moment": {
"version": "2.22.2",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz",
"integrity": "sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y="
},
"moment-duration-format": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/moment-duration-format/-/moment-duration-format-2.2.2.tgz",
"integrity": "sha1-uVdhLeJgFsmtnrYIfAVFc+USd3k="
}
}
}

View File

@ -1,22 +1,22 @@
{
"homepage": "http://padlock.io",
"name": "padlock",
"name": "padlock-app",
"version": "3.0.0",
"resolutions": {
"inherits": "2.0.3",
"samsam": "1.1.3",
"supports-color": "3.1.2",
"type-detect": "1.0.0",
"@webcomponents/webcomponentsjs": "2.0.0-beta.2"
},
"main": "index.js",
"main": "elements/app.js",
"author": "Martin Kleinschrodt <martin@maklesoft.com>",
"license": "GPL-3.0",
"dependencies": {
"@polymer/polymer": "^3.0.0",
"@polymer/iron-list": "^3.0.0-pre.18",
"@polymer/paper-spinner": "^3.0.0-pre.18",
"@webcomponents/webcomponentsjs": "^2.0.0"
"@polymer/polymer": "^3.0.0",
"@webcomponents/webcomponentsjs": "^2.0.0",
"moment": "^2.22.2",
"moment-duration-format": "^2.2.2",
"autosize": "^4.0.2"
},
"devDependencies": {}
"devDependencies": {},
"description": "Padlock Client",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
}
}

View File

@ -1,700 +0,0 @@
<script src="../padlock-lite.js"></script>
<script type="module" src="../styles/shared.js"></script>
<script type="module" src="../ui/animation/animation.js"></script>
<script type="module" src="../ui/base/base.js"></script>
<script type="module" src="../ui/dialog/dialog-mixin.js"></script>
<script type="module" src="../ui/icon/icon.js"></script>
<script type="module" src="../ui/input/input.js"></script>
<script type="module" src="../ui/loading-button/loading-button.js"></script>
<script type="module" src="../ui/locale/locale.js"></script>
<script type="module" src="../ui/notification/notification.js"></script>
<script type="module" src="../ui/payment-dialog/payment-dialog.js"></script>
<script type="module" src="../ui/promo/promo.js"></script>
<script type="module" src="../ui/promo/promo-dialog.js"></script>
<script type="module" src="../ui/sync/subinfo.js"></script>
<dom-module id="pl-cloud-dashboard">
<template>
<style include="shared">
:host {
display: flex;
flex-direction: column;
@apply --fullbleed;
background: var(--color-quaternary);
}
:host:not([ready]) main {
opacity: 0;
}
button {
display: block;
width: 100%;
box-sizing: border-box;
/* font-weight: bold; */
}
.account {
font-size: 130%;
font-weight: bold;
margin: 10px 0;
overflow-wrap: break-word;
}
.subscription-status {
font-size: 150%;
font-weight: bold;
margin: 10px 0;
}
header .title.back {
padding: 0;
/* margin-left: -10px; */
font-size: var(--font-size-small);
flex: none;
}
header .back-icon {
font-size: var(--font-size-small);
width: 30px;
margin-right: -20px;
}
.title.email {
text-align: center;
padding-right: 0;
}
.devices {
line-height: var(--row-height);
}
.devices .title {
text-align: center;
padding: 0 15px;
font-weight: bold;
}
.device {
display: flex;
}
.device-name {
flex: 1;
padding: 0 15px;
@apply --ellipsis;
}
.device > pl-icon {
width: var(--row-height);
height: var(--row-height);
}
#cardElement {
height: var(--row-height);
padding: 15px;
box-sizing: border-box;
}
.payment-method {
padding-top: 15px;
padding-bottom: 5px;
font-weight: bold;
}
.buy-subscription {
text-align: center;
padding: 30px;
}
.subscription-name {
font-weight: bold;
font-size: 130%;
}
.subscription-features {
list-style: none;
padding: 0;
}
.subscription-features li::before {
font-family: "FontAwesome";
content: "\f00c\ ";
}
.price {
margin: 10px 0;
font-size: 180%;
font-weight: bold;
}
.small {
font-size: var(--font-size-small);
}
.secure-payment {
display: block;
font-size: 12px;
padding: 5px;
text-align: center;
text-shadow: none;
}
.secure-payment::before {
font-family: "FontAwesome";
content: "\f023\ ";
vertical-align: middle;
position: relative;
top: 1px;
text-shadow: none;
}
.secure-payment > * {
vertical-align: middle;
}
.secure-payment > img {
height: 20px;
position: relative;
top: 1px;
}
.card-hint {
font-size: var(--font-size-small);
}
.card-hint:not([error=""]) {
color: #eb1c26;
text-shadow: none;
}
#cardDialog, #billingDialog, #invoicesDialog {
--pl-dialog-max-width: 500px;
}
#submitButton {
font-weight: bold;
}
#billingForm {
display: flex;
flex-direction: column;
}
.select-wrapper {
padding: 0 10px 0 5px;
}
#billingForm > input {
height: var(--row-height);
padding: 0 15px;
}
#billingForm select {
height: var(--row-height);
width: 100%;
text-shadow: inherit;
}
.invoices {
list-style: none;
padding: 0;
margin: 0;
}
.invoices li > a {
display: block;
height: var(--row-height);
line-height: var(--row-height);
padding: 0 15px;
text-align: center;
}
.info {
display: flex;
align-items: center;
}
.info-icon {
width: 80px;
height: 80px;
font-size: 60px;
margin: 10px 0 10px 10px;
}
.info-body {
padding: 20px 15px 20px 10px;
flex: 1;
}
.info-title {
font-size: 120%;
font-weight: bold;
margin-bottom: 5px;
}
.info-text {
font-size: var(--font-size-small);
}
.info-2 {
padding: 15px;
font-size: var(--font-size-small);
text-align: center;
line-height: normal;
}
.info-2 a {
text-decoration: underline;
}
@media (min-width: 700px) {
section {
width: 670px;
margin: 15px auto;
border-left: solid 1px rgba(0, 0, 0, 0.1);
border-right: solid 1px rgba(0, 0, 0, 0.1);
border-radius: 8px;
}
}
</style>
<header>
<div class="tap" on-click="_back">
<pl-icon icon="backward" class="back-icon"></pl-icon>
<pl-icon icon="logo"></pl-icon>
</div>
<div class="title email">[[ account.email ]]</div>
<a href="/logout/">
<pl-icon icon="logout" class="tap"></pl-icon>
</a>
</header>
<main>
<section class="highlight dark" hidden$="[[ !truthy(promo) ]]">
<pl-promo promo="[[ promo ]]" data-source="Dashboard - Promo" on-promo-redeem="_buySubscription" on-promo-expired="_promoExpired"></pl-promo>
</section>
<section class="highlight tiles warning" hidden$="[[ !isTrialing(account.subscription.status) ]]">
<div class="info">
<pl-icon class="info-icon" icon="time"></pl-icon>
<div class="info-body">
<div class="info-title">[[ $l("Trialing ({0} days left)", remainingTrialDays) ]]</div>
<div class="info-text">[[ trialingMessage(remainingTrialDays) ]]</div>
</div>
</div>
<button class="tap" on-click="_buySubscription" data-source="Dashboard - Trialing">[[ $l("Upgrade Now") ]]</button>
</section>
<section class="highlight tiles warning" hidden$="[[ !isTrialExpired(account.subscription.status) ]]">
<div class="info">
<pl-icon class="info-icon" icon="error"></pl-icon>
<div class="info-body">
<div class="info-title">[[ $l("Trial Expired") ]]</div>
<div class="info-text">[[ trialExpiredMessage() ]]</div>
</div>
</div>
<button class="tap" on-click="_buySubscription" data-source="Dashboard - Trial Expired">[[ $l("Upgrade Now") ]]</button>
</section>
<section class="highlight tiles warning" hidden$="[[ !isSubUnpaid(account.subscription.status) ]]">
<div class="info">
<pl-icon class="info-icon" icon="error"></pl-icon>
<div class="info-body">
<div class="info-title">[[ $l("Payment Failed") ]]</div>
<div class="info-text">[[ subUnpaidMessage() ]]</div>
</div>
</div>
<button class="tap" on-click="_updatePaymentMethod" data-source="Dashboard - Payment Failed">[[ $l("Update Payment Method") ]]</button>
<button class="tap" on-click="_contactSupport">[[ $l("Contact Support") ]]</button>
</section>
<section class="highlight tiles warning" hidden$="[[ !isSubCanceled(account.subscription.status) ]]">
<div class="info">
<pl-icon class="info-icon" icon="error"></pl-icon>
<div class="info-body">
<div class="info-title">[[ $l("Subscription Canceled") ]]</div>
<div class="info-text">[[ subCanceledMessage() ]]</div>
</div>
</div>
<button class="tap" on-click="_reactivateSubscription">[[ $l("Reactivate Subscription") ]]</button>
</section>
<section class="devices">
<div class="title">[[ $l("{0} Paired Devices", account.devices.length) ]]</div>
<div class="info-2" hidden$="[[ !_hasNoDevices(account.devices) ]]">[[ $l("Looks like you haven't installed the Padlock app on any devices yet!") ]]</div>
<button class="tap" on-click="_downloadApp" hidden$="[[ !_hasNoDevices(account.devices) ]]">[[ $l("Download App") ]]</button>
<dom-repeat items="[[ account.devices ]]">
<template>
<div class="device">
<div class="device-name">[[ item.description ]]</div>
<pl-icon icon="delete" on-click="_revokeDevice"></pl-icon>
</div>
</template>
</dom-repeat>
</section>
<section hidden$="[[ !truthy(account.paymentSource) ]]">
<div class="section-header">[[ $l("Billing") ]]</div>
<button class="tap" on-click="_updatePaymentMethod" data-source="App - Billing">[[ _paymentSourceLabel(account.paymentSource) ]]</button>
<button class="tap" on-click="_cancelSubscription"
hidden$="[[ !isSubActive(account.subscription.status) ]]">[[ $l("Cancel Subscription") ]]</button>
</section>
<section hidden$="[[ _showAdvanced ]]">
<button class="tap" on-click="_showAdvancedOptions">[[ $l("Advanced Options...") ]]</button>
</section>
<div hidden$="[[ !_showAdvanced ]]">
<section>
<div class="info-2">[[ _resetDataText() ]]</div>
<button class="tap" on-click="_resetData">[[ $l("Reset Data") ]]</button>
</section>
<section>
<div class="info-2">
Had enough? Deleting your acccount will wipe all your
online vault data and personal information from our systems.
Your connected devices will be locked out.
Any active subscriptions will be canceled immediately.
We may retain some billing information and transaction
history as required by law (consult our <a
href="https://padlock.io/privacy/" target="_blank">Privacy Policy</a>
for more information).<br>
<strong>Note:</strong> This will not affect data stored locally on your devices. You
may continue to use the app offline or wipe your data from your devices manually.
</div>
<pl-loading-button id="deleteAccountButton" class="tap" on-click="_deleteAccount">[[ $l("Delete Account") ]]</pl-loading-button>
</section>
</div>
</main>
<pl-dialog id="deleteStoreDialog">
<form action="/deletestore/" method="POST">
<div class="message">[[ _confirmResetDataText() ]]</div>
<input type="hidden" name="gorilla.csrf.Token" value="[[ csrfToken ]]">
<button class="tap tiles-2">[[ $l("Reset Data") ]]</button>
</form>
</pl-dialog>
<pl-dialog id="cancelSubscriptionDialog">
<form action="/unsubscribe/" method="POST">
<div class="message">[[ _cancelSubscriptionText() ]]</div>
<input type="hidden" name="gorilla.csrf.Token" value="[[ csrfToken ]]">
<button class="tap tiles-2">[[ $l("Cancel Subscription") ]]</button>
</form>
</pl-dialog>
<pl-dialog id="revokeDeviceDialog">
<form action="/revoke/" method="POST">
<div class="message">[[ _revokeDeviceMessage ]]</div>
<input type="hidden" name="gorilla.csrf.Token" value="[[ csrfToken ]]">
<input type="hidden" name="id" value="[[ _revokedDeviceId ]]">
<button class="tap tiles-2">[[ $l("Revoke Access") ]]</button>
</form>
</pl-dialog>
<pl-payment-dialog id="paymentDialog" stripe-pub-key="[[ stripePubKey ]]" csrf-token="[[ csrfToken ]]"></pl-payment-dialog>
</template>
<script type="module">
import '../styles/shared.js';
import '../ui/animation/animation.js';
import '../ui/base/base.js';
import '../ui/dialog/dialog-mixin.js';
import '../ui/icon/icon.js';
import '../ui/input/input.js';
import '../ui/loading-button/loading-button.js';
import '../ui/locale/locale.js';
import '../ui/notification/notification.js';
import '../ui/payment-dialog/payment-dialog.js';
import '../ui/promo/promo.js';
import '../ui/promo/promo-dialog.js';
import '../ui/sync/subinfo.js';
const { LocaleMixin, DialogMixin, NotificationMixin, AnimationMixin, SubInfoMixin, BaseElement } = padlock;
const { applyMixins } = padlock.util;
const { request } = padlock.ajax;
const { track, init } = padlock.tracking;
init({
request: padlock.ajax.request,
urlForPath(path) {
return `/${path}/`;
}
});
class Dashboard extends applyMixins(
BaseElement,
LocaleMixin,
DialogMixin,
NotificationMixin,
AnimationMixin,
SubInfoMixin
) {
static get is() { return "pl-cloud-dashboard"; }
static get properties() { return {
account: Object,
action: String,
token: Object,
csrfToken: String,
stripePubKey: String,
referer: String,
_showAdvanced: {
type: Boolean,
value: false
}
}; }
connectedCallback() {
super.connectedCallback();
this.$.paymentDialog.source = this;
setTimeout(() => {
this.animateCascade(this.root.querySelectorAll("section"));
this.setAttribute("ready", "");
}, 100);
switch (this.action) {
case "paired":
setTimeout(() => {
this.notify($l("{0} paired successfully!", this.token.description), "info", 3000);
}, 500);
break;
case "revoked":
setTimeout(() => {
this.notify($l("Access for {0} revoked successfully!", this.token.description), "info", 3000);
}, 500);
break;
case "reset":
setTimeout(() => this.notify($l("Successfully reset data!"), "info", 3000), 500);
break;
case "subscribed":
setTimeout(() => this.notify($l("Subscription added successfully!"), "info", 3000), 500);
break;
case "unsubscribed":
setTimeout(() => this.notify($l("Subscription canceled successfully!"), "info", 3000), 500);
break;
case "payment-updated":
setTimeout(() => this.notify($l("Payment method updated successfully!"), "info", 3000), 500);
break;
case "subscribe":
if (!this.isSubActive()) {
this._buySubscription();
}
break;
}
if (!localStorage.getItem("firstOpened")) {
localStorage.setItem("firstOpened", new Date().getTime());
this.choose($l(
"This is where you can manage your Padlock online " +
"account and see all your paired devices. You won't be able to access " +
"any of your actual data here, though, that can only be done from the Padlock app!"
), [
$l("Got it!")
], { title: "Welcome To Your Dashboard!", hideIcon: true });
}
track("Dashboard: Finish Loading");
}
subscribe(stripeToken = "", coupon = "", source = "") {
const params = new URLSearchParams();
params.set("stripeToken", stripeToken);
params.set("coupon", coupon);
params.set("source", source);
params.set("gorilla.csrf.Token", this.csrfToken);
return request(
"POST",
"/subscribe/",
params.toString(),
new Map()
.set("Content-Type", "application/x-www-form-urlencoded")
.set("Accept", "application/json")
);
}
_refreshAccount() {
return request("GET", "/account/")
.then((req) => this.account = JSON.parse(req.responseText));
}
_resetDataText() {
return $l(
"Want to start from scratch? Here you can reset your online vault data in case " +
"you lost your master password or simply want to start over. Your connected " +
"devices, billing information and subscription status will remain unaffected."
);
}
_confirmResetDataText() {
return $l(
"Are you sure you want to reset your online vault data? " +
"This action can not be undone! (Data stored locally on your devices will not be affected)"
);
}
_cancelSubscriptionText() {
return $l(
"Are you sure you want to cancel your subscription? Without an active subscription your access " +
"will be read-only, which means you won't be able to upload any new data or synchronize changes " +
"between devices!"
);
}
_resetData() {
this.$.deleteStoreDialog.open = true;
}
_revokeDevice(e) {
this._revokedDeviceId = e.model.item.tokenId;
this._revokeDeviceMessage = $l("Are you sure you want to revoke access for \"{0}\"?", e.model.item.description);
this.$.revokeDeviceDialog.open = true;
}
_buySubscription(e) {
this.$.paymentDialog.promo = this.account.promo;
this.$.paymentDialog.plan = this.account.plan;
this.$.paymentDialog.show(e && e.target.dataset.source || this.referer)
.then((success) => {
if (success) {
this._refreshAccount();
this.alert($l("Congratulations, you've successfully upgraded to Padlock Pro!"),
{ type: "success" });
}
});
}
_cancelSubscription() {
this.$.cancelSubscriptionDialog.open = true;
}
_updatePaymentMethod() {
this.$.paymentDialog.plan = null;
this.$.paymentDialog.show("Dashboard")
.then((success) => {
if (success) {
this._refreshAccount();
this.notify($l("Payment method updated successfully!"), "info", 2000);
}
});
}
_back() {
track("Dashboard: Back", {}).then(() => {
setTimeout(() => window.location = "https://padlock.io/", 200);
});
window.location = "padlock://?ref=dashboard";
}
_openBillingDialog() {
this.$.billingDialog.open = true;
}
_showInvoices(e) {
const button = e.target;
button.start();
request("GET", "/invoices/", undefined, new Map([["Accept", "application/json"]]))
.then((res) => {
this._invoices = JSON.parse(res.responseText);
this.$.invoicesDialog.open = true;
button.success();
});
}
_formatTimestamp(ts) {
return new Date(ts * 1000).toLocaleDateString();
}
_hasNoDevices() {
return !this.account.devices.length;
}
_downloadApp() {
window.open("https://padlock.io/downloads/", "_blank");
}
_promoExpired() {
this.notifyPath("account.promo");
}
_paymentSourceLabel(s) {
return s && `${s.brand} •••• •••• •••• ${s.lastFour}`;
}
_reactivateSubscription() {
this.subscribe(undefined, undefined, "Dashboard")
.then(() => {
this._refreshAccount();
this.alert($l("Subscription reactivated successfully!"), { type: "success" });
})
.catch((e) => this.alert(e.message, { type: "warning" }));
}
_deleteAccount() {
this.prompt(
$l("Are you sure you want to delete your Padlock online account?"),
$l("Type 'DELETE' To Confirm"),
"text",
$l("Delete Account"),
$l("Cancel"),
false,
(val) => {
return val === "DELETE" ? Promise.resolve(true) : Promise.reject("Type 'DELETE' to confirm!");
}
).then((confirmed) => {
if (confirmed === true) {
this.$.deleteAccountButton.start();
const params = new URLSearchParams();
params.set("gorilla.csrf.Token", this.csrfToken);
request(
"POST",
"/deleteaccount/",
params.toString(),
new Map([
["Content-Type", "application/x-www-form-urlencoded"],
["Accept", "application/json"]
])
)
.then(() => {
this.$.deleteAccountButton.success();
this.alert("Account deleted successfully. Sorry to see you go!", { type: "success" })
.then(() => window.close());
})
.catch((e) => {
this.$.deleteAccountButton.fail();
this.alert(e.message, { type: "warning" });
});
}
});
}
_showAdvancedOptions() {
this._showAdvanced = true;
}
}
window.customElements.define(Dashboard.is, Dashboard);
</script>
</dom-module>

View File

@ -1,4 +1,4 @@
import { sjcl } from "../vendor/sjcl";
import { sjcl } from "../../vendor/sjcl";
import { isCordova, hasNode } from "./platform";
declare var pbkdf2: undefined |

View File

@ -1,6 +1,6 @@
import * as papaparse from "papaparse";
import { Collection, Record } from "./data";
import { MemorySource, EncryptedSource } from "./source";
import { loadPapa } from "./import";
function recordsToTable(records: Record[]) {
// Array of column names
@ -55,8 +55,9 @@ function recordsToTable(records: Record[]) {
return table;
}
export function toCSV(records: Record[]): string {
return papaparse.unparse(recordsToTable(records));
export async function toCSV(records: Record[]): Promise<string> {
await loadPapa();
return Papa.unparse(recordsToTable(records));
}
export async function toPadlock(records: Record[], password: string): Promise<string> {

View File

@ -1,11 +1,15 @@
import { parse } from "papaparse";
import { Record, Field } from "./data";
import { Container } from "./crypto";
import { loadScript } from "./util";
export class ImportError {
constructor(public code: "invalid_csv") {}
}
export async function loadPapa() {
await loadScript("vendor/papaparse.js");
}
//* Detects if a string contains a SecuStore backup
export function isFromSecuStore(data: string): boolean {
return data.indexOf("SecuStore") != -1 && data.indexOf("#begin") != -1 && data.indexOf("#end") != -1;
@ -100,12 +104,14 @@ export function fromTable(data: string[][], nameColIndex?: number, tagsColIndex?
return records;
}
export function isCSV(data: string): Boolean {
return parse(data).errors.length === 0;
export async function isCSV(data: string): Promise<Boolean> {
await loadPapa();
return Papa.parse(data).errors.length === 0;
}
export function fromCSV(data: string, nameColIndex?: number, tagsColIndex?: number): Record[] {
const parsed = parse(data);
export async function fromCSV(data: string, nameColIndex?: number, tagsColIndex?: number): Promise<Record[]> {
await loadPapa();
const parsed = Papa.parse(data);
if (parsed.errors.length) {
throw new ImportError("invalid_csv");
}
@ -186,9 +192,10 @@ function lpParseRow(row: string[]): Record {
return new Record(row[nameIndex], fields, dir ? [dir] : []);
}
export function fromLastPass(data: string): Record[] {
let records = parse(data)
.data// Remove first row as it only contains field names
export async function fromLastPass(data: string): Promise<Record[]> {
await loadPapa();
let records = Papa.parse(data)
.data // Remove first row as it only contains field names
.slice(1)
// Filter out empty rows
.filter(row => row.length > 1)

View File

@ -1,11 +0,0 @@
import * as util from "./util";
import * as platform from "./platform";
import * as ajax from "./ajax";
import * as tracking from "./tracking";
export {
util,
ajax,
platform,
tracking
}

View File

@ -1,27 +0,0 @@
import * as util from "./util";
import * as crypto from "./crypto";
import * as ajax from "./ajax";
import * as source from "./source";
import * as data from "./data";
import * as imp from "./import";
import * as exp from "./export";
import * as platform from "./platform";
import * as file from "./file";
import * as messages from "./messages";
import * as stats from "./stats";
import * as tracking from "./tracking";
export {
util,
crypto,
source,
data,
ajax,
imp,
exp,
platform,
file,
messages,
stats,
tracking
}

View File

@ -1,13 +1,8 @@
import { request } from "./ajax";
import { Source } from "./source";
import { resolveLanguage } from "./util";
import { resolveLanguage, loadScript } from "./util";
import { getLocale, getPlatformName } from "./platform";
import { Settings } from "./data";
// import { satisfies } from "semver";
function satisfies(v1: string, v2: string): boolean {
return v1 === v2;
}
export interface Message {
id: string;
@ -37,6 +32,7 @@ export class Messages {
const aa = JSON.parse(data);
const read = await this.fetchRead();
const platform = await getPlatformName();
await loadScript("vendor/semver.js");
return aa
.map((a: any) => {
@ -66,7 +62,7 @@ export class Messages {
a.until >= now &&
(!a.platform || a.platform.includes(platform)) &&
(!a.subStatus || a.subStatus.includes(this.settings.syncSubStatus)) &&
(!a.version || satisfies(this.settings.version, a.version))
(!a.version || semver.satisfies(this.settings.version, a.version))
);
});
}

View File

@ -1,6 +1,5 @@
import * as moment from "moment";
import moment from "moment";
import "moment-duration-format";
import * as zxcvbn from "zxcvbn";
// RFC4122-compliant uuid generator
export function uuid(): string {
@ -92,6 +91,20 @@ export function isFuture(date: Date | string | number, duration: number) {
.isAfter();
}
export function passwordStrength(password: string): zxcvbn.ZXCVBNResult {
return zxcvbn(password);
export function loadScript(src: string): Promise<void> {
if (document.querySelector(`script[src="${src}"]`) !== null) {
return Promise.resolve();
}
const s = document.createElement("script");
s.src = src;
s.type = "text/javascript";
return new Promise(resolve => {
s.onload = () => resolve();
document.body.appendChild(s);
});
}
export async function passwordStrength(pwd: string): Promise<{score: number}> {
await loadScript("vendor/zxcvbn.js");
return window.zxcvbn(pwd);
}

View File

@ -6,8 +6,10 @@ import "./record-view.js";
import "./settings-view.js";
import "./start-view.js";
import "./title-bar.js";
import { Input } from "./input.js";
import { getPlatformName, getDeviceInfo, isTouch } from "../core/platform.js";
import { applyMixins } from "../core/util.js";
import { localize as $l } from "../core/locale.js";
import { BaseElement, html } from "./base.js";
import {
NotificationMixin,
@ -614,7 +616,7 @@ class App extends applyMixins(
//* Keyboard shortcuts
_keydown(event) {
if (this.locked || padlock.Input.activeInput) {
if (this.locked || Input.activeInput) {
return;
}

View File

@ -1,5 +1,5 @@
import "@polymer/polymer/polymer-legacy.js";
import { PolymerElement, html } from "@polymer/polymer/polymer-element.js";
import "../../node_modules/@polymer/polymer/polymer-legacy.js";
import { PolymerElement, html } from "../../node_modules/@polymer/polymer/polymer-element.js";
export class BaseElement extends PolymerElement {
truthy(val) {

View File

@ -0,0 +1,664 @@
import "../styles/shared.js";
import { LocaleMixin, DialogMixin, NotificationMixin, AnimationMixin, SubInfoMixin } from "../mixins";
import { applyMixins } from "../core/util.js";
import { request } from "../core/ajax.js";
import { track, init } from "../core/tracking.js";
import { localize as $l } from "../core/locale.js";
import { BaseElement, html } from "../elements/base.js";
import "./icon.js";
import "./input.js";
import "./loading-button.js";
import "./dialog-payment.js";
import "./promo.js";
import "./dialog-promo.js";
init({
request: request,
urlForPath(path) {
return `/${path}/`;
}
});
class Dashboard extends applyMixins(
BaseElement,
LocaleMixin,
DialogMixin,
NotificationMixin,
AnimationMixin,
SubInfoMixin
) {
static get is() {
return "pl-dashboard";
}
static get template() {
return html`
<style include="shared">
:host {
display: flex;
flex-direction: column;
@apply --fullbleed;
background: var(--color-quaternary);
}
:host:not([ready]) main {
opacity: 0;
}
button {
display: block;
width: 100%;
box-sizing: border-box;
/* font-weight: bold; */
}
.account {
font-size: 130%;
font-weight: bold;
margin: 10px 0;
overflow-wrap: break-word;
}
.subscription-status {
font-size: 150%;
font-weight: bold;
margin: 10px 0;
}
header .title.back {
padding: 0;
/* margin-left: -10px; */
font-size: var(--font-size-small);
flex: none;
}
header .back-icon {
font-size: var(--font-size-small);
width: 30px;
margin-right: -20px;
}
.title.email {
text-align: center;
padding-right: 0;
}
.devices {
line-height: var(--row-height);
}
.devices .title {
text-align: center;
padding: 0 15px;
font-weight: bold;
}
.device {
display: flex;
}
.device-name {
flex: 1;
padding: 0 15px;
@apply --ellipsis;
}
.device > pl-icon {
width: var(--row-height);
height: var(--row-height);
}
#cardElement {
height: var(--row-height);
padding: 15px;
box-sizing: border-box;
}
.payment-method {
padding-top: 15px;
padding-bottom: 5px;
font-weight: bold;
}
.buy-subscription {
text-align: center;
padding: 30px;
}
.subscription-name {
font-weight: bold;
font-size: 130%;
}
.subscription-features {
list-style: none;
padding: 0;
}
.subscription-features li::before {
font-family: "FontAwesome";
content: "\f00c\ ";
}
.price {
margin: 10px 0;
font-size: 180%;
font-weight: bold;
}
.small {
font-size: var(--font-size-small);
}
.secure-payment {
display: block;
font-size: 12px;
padding: 5px;
text-align: center;
text-shadow: none;
}
.secure-payment::before {
font-family: "FontAwesome";
content: "\f023\ ";
vertical-align: middle;
position: relative;
top: 1px;
text-shadow: none;
}
.secure-payment > * {
vertical-align: middle;
}
.secure-payment > img {
height: 20px;
position: relative;
top: 1px;
}
.card-hint {
font-size: var(--font-size-small);
}
.card-hint:not([error=""]) {
color: #eb1c26;
text-shadow: none;
}
#cardDialog, #billingDialog, #invoicesDialog {
--pl-dialog-max-width: 500px;
}
#submitButton {
font-weight: bold;
}
#billingForm {
display: flex;
flex-direction: column;
}
.select-wrapper {
padding: 0 10px 0 5px;
}
#billingForm > input {
height: var(--row-height);
padding: 0 15px;
}
#billingForm select {
height: var(--row-height);
width: 100%;
text-shadow: inherit;
}
.invoices {
list-style: none;
padding: 0;
margin: 0;
}
.invoices li > a {
display: block;
height: var(--row-height);
line-height: var(--row-height);
padding: 0 15px;
text-align: center;
}
.info {
display: flex;
align-items: center;
}
.info-icon {
width: 80px;
height: 80px;
font-size: 60px;
margin: 10px 0 10px 10px;
}
.info-body {
padding: 20px 15px 20px 10px;
flex: 1;
}
.info-title {
font-size: 120%;
font-weight: bold;
margin-bottom: 5px;
}
.info-text {
font-size: var(--font-size-small);
}
.info-2 {
padding: 15px;
font-size: var(--font-size-small);
text-align: center;
line-height: normal;
}
.info-2 a {
text-decoration: underline;
}
@media (min-width: 700px) {
section {
width: 670px;
margin: 15px auto;
border-left: solid 1px rgba(0, 0, 0, 0.1);
border-right: solid 1px rgba(0, 0, 0, 0.1);
border-radius: 8px;
}
}
</style>
<header>
<div class="tap" on-click="_back">
<pl-icon icon="backward" class="back-icon"></pl-icon>
<pl-icon icon="logo"></pl-icon>
</div>
<div class="title email">[[ account.email ]]</div>
<a href="/logout/">
<pl-icon icon="logout" class="tap"></pl-icon>
</a>
</header>
<main>
<section class="highlight dark" hidden$="[[ !truthy(promo) ]]">
<pl-promo promo="[[ promo ]]" data-source="Dashboard - Promo" on-promo-redeem="_buySubscription" on-promo-expired="_promoExpired"></pl-promo>
</section>
<section class="highlight tiles warning" hidden$="[[ !isTrialing(account.subscription.status) ]]">
<div class="info">
<pl-icon class="info-icon" icon="time"></pl-icon>
<div class="info-body">
<div class="info-title">[[ $l("Trialing ({0} days left)", remainingTrialDays) ]]</div>
<div class="info-text">[[ trialingMessage(remainingTrialDays) ]]</div>
</div>
</div>
<button class="tap" on-click="_buySubscription" data-source="Dashboard - Trialing">[[ $l("Upgrade Now") ]]</button>
</section>
<section class="highlight tiles warning" hidden$="[[ !isTrialExpired(account.subscription.status) ]]">
<div class="info">
<pl-icon class="info-icon" icon="error"></pl-icon>
<div class="info-body">
<div class="info-title">[[ $l("Trial Expired") ]]</div>
<div class="info-text">[[ trialExpiredMessage() ]]</div>
</div>
</div>
<button class="tap" on-click="_buySubscription" data-source="Dashboard - Trial Expired">[[ $l("Upgrade Now") ]]</button>
</section>
<section class="highlight tiles warning" hidden$="[[ !isSubUnpaid(account.subscription.status) ]]">
<div class="info">
<pl-icon class="info-icon" icon="error"></pl-icon>
<div class="info-body">
<div class="info-title">[[ $l("Payment Failed") ]]</div>
<div class="info-text">[[ subUnpaidMessage() ]]</div>
</div>
</div>
<button class="tap" on-click="_updatePaymentMethod" data-source="Dashboard - Payment Failed">[[ $l("Update Payment Method") ]]</button>
<button class="tap" on-click="_contactSupport">[[ $l("Contact Support") ]]</button>
</section>
<section class="highlight tiles warning" hidden$="[[ !isSubCanceled(account.subscription.status) ]]">
<div class="info">
<pl-icon class="info-icon" icon="error"></pl-icon>
<div class="info-body">
<div class="info-title">[[ $l("Subscription Canceled") ]]</div>
<div class="info-text">[[ subCanceledMessage() ]]</div>
</div>
</div>
<button class="tap" on-click="_reactivateSubscription">[[ $l("Reactivate Subscription") ]]</button>
</section>
<section class="devices">
<div class="title">[[ $l("{0} Paired Devices", account.devices.length) ]]</div>
<div class="info-2" hidden$="[[ !_hasNoDevices(account.devices) ]]">[[ $l("Looks like you haven't installed the Padlock app on any devices yet!") ]]</div>
<button class="tap" on-click="_downloadApp" hidden$="[[ !_hasNoDevices(account.devices) ]]">[[ $l("Download App") ]]</button>
<dom-repeat items="[[ account.devices ]]">
<template>
<div class="device">
<div class="device-name">[[ item.description ]]</div>
<pl-icon icon="delete" on-click="_revokeDevice"></pl-icon>
</div>
</template>
</dom-repeat>
</section>
<section hidden$="[[ !truthy(account.paymentSource) ]]">
<div class="section-header">[[ $l("Billing") ]]</div>
<button class="tap" on-click="_updatePaymentMethod" data-source="App - Billing">[[ _paymentSourceLabel(account.paymentSource) ]]</button>
<button class="tap" on-click="_cancelSubscription"
hidden$="[[ !isSubActive(account.subscription.status) ]]">[[ $l("Cancel Subscription") ]]</button>
</section>
<section hidden$="[[ _showAdvanced ]]">
<button class="tap" on-click="_showAdvancedOptions">[[ $l("Advanced Options...") ]]</button>
</section>
<div hidden$="[[ !_showAdvanced ]]">
<section>
<div class="info-2">[[ _resetDataText() ]]</div>
<button class="tap" on-click="_resetData">[[ $l("Reset Data") ]]</button>
</section>
<section>
<div class="info-2">
Had enough? Deleting your acccount will wipe all your
online vault data and personal information from our systems.
Your connected devices will be locked out.
Any active subscriptions will be canceled immediately.
We may retain some billing information and transaction
history as required by law (consult our <a
href="https://padlock.io/privacy/" target="_blank">Privacy Policy</a>
for more information).<br>
<strong>Note:</strong> This will not affect data stored locally on your devices. You
may continue to use the app offline or wipe your data from your devices manually.
</div>
<pl-loading-button id="deleteAccountButton" class="tap" on-click="_deleteAccount">[[ $l("Delete Account") ]]</pl-loading-button>
</section>
</div>
</main>
<pl-dialog id="deleteStoreDialog">
<form action="/deletestore/" method="POST">
<div class="message">[[ _confirmResetDataText() ]]</div>
<input type="hidden" name="gorilla.csrf.Token" value="[[ csrfToken ]]">
<button class="tap tiles-2">[[ $l("Reset Data") ]]</button>
</form>
</pl-dialog>
<pl-dialog id="cancelSubscriptionDialog">
<form action="/unsubscribe/" method="POST">
<div class="message">[[ _cancelSubscriptionText() ]]</div>
<input type="hidden" name="gorilla.csrf.Token" value="[[ csrfToken ]]">
<button class="tap tiles-2">[[ $l("Cancel Subscription") ]]</button>
</form>
</pl-dialog>
<pl-dialog id="revokeDeviceDialog">
<form action="/revoke/" method="POST">
<div class="message">[[ _revokeDeviceMessage ]]</div>
<input type="hidden" name="gorilla.csrf.Token" value="[[ csrfToken ]]">
<input type="hidden" name="id" value="[[ _revokedDeviceId ]]">
<button class="tap tiles-2">[[ $l("Revoke Access") ]]</button>
</form>
</pl-dialog>
<pl-payment-dialog id="paymentDialog" stripe-pub-key="[[ stripePubKey ]]" csrf-token="[[ csrfToken ]]">
</pl-payment-dialog>
`;
}
static get properties() {
return {
account: Object,
action: String,
token: Object,
csrfToken: String,
stripePubKey: String,
referer: String,
_showAdvanced: {
type: Boolean,
value: false
}
};
}
connectedCallback() {
super.connectedCallback();
this.$.paymentDialog.source = this;
setTimeout(() => {
this.animateCascade(this.root.querySelectorAll("section"));
this.setAttribute("ready", "");
}, 100);
switch (this.action) {
case "paired":
setTimeout(() => {
this.notify($l("{0} paired successfully!", this.token.description), "info", 3000);
}, 500);
break;
case "revoked":
setTimeout(() => {
this.notify($l("Access for {0} revoked successfully!", this.token.description), "info", 3000);
}, 500);
break;
case "reset":
setTimeout(() => this.notify($l("Successfully reset data!"), "info", 3000), 500);
break;
case "subscribed":
setTimeout(() => this.notify($l("Subscription added successfully!"), "info", 3000), 500);
break;
case "unsubscribed":
setTimeout(() => this.notify($l("Subscription canceled successfully!"), "info", 3000), 500);
break;
case "payment-updated":
setTimeout(() => this.notify($l("Payment method updated successfully!"), "info", 3000), 500);
break;
case "subscribe":
if (!this.isSubActive()) {
this._buySubscription();
}
break;
}
if (!localStorage.getItem("firstOpened")) {
localStorage.setItem("firstOpened", new Date().getTime());
this.choose(
$l(
"This is where you can manage your Padlock online " +
"account and see all your paired devices. You won't be able to access " +
"any of your actual data here, though, that can only be done from the Padlock app!"
),
[$l("Got it!")],
{ title: "Welcome To Your Dashboard!", hideIcon: true }
);
}
track("Dashboard: Finish Loading");
}
subscribe(stripeToken = "", coupon = "", source = "") {
const params = new URLSearchParams();
params.set("stripeToken", stripeToken);
params.set("coupon", coupon);
params.set("source", source);
params.set("gorilla.csrf.Token", this.csrfToken);
return request(
"POST",
"/subscribe/",
params.toString(),
new Map().set("Content-Type", "application/x-www-form-urlencoded").set("Accept", "application/json")
);
}
_refreshAccount() {
return request("GET", "/account/").then(req => (this.account = JSON.parse(req.responseText)));
}
_resetDataText() {
return $l(
"Want to start from scratch? Here you can reset your online vault data in case " +
"you lost your master password or simply want to start over. Your connected " +
"devices, billing information and subscription status will remain unaffected."
);
}
_confirmResetDataText() {
return $l(
"Are you sure you want to reset your online vault data? " +
"This action can not be undone! (Data stored locally on your devices will not be affected)"
);
}
_cancelSubscriptionText() {
return $l(
"Are you sure you want to cancel your subscription? Without an active subscription your access " +
"will be read-only, which means you won't be able to upload any new data or synchronize changes " +
"between devices!"
);
}
_resetData() {
this.$.deleteStoreDialog.open = true;
}
_revokeDevice(e) {
this._revokedDeviceId = e.model.item.tokenId;
this._revokeDeviceMessage = $l('Are you sure you want to revoke access for "{0}"?', e.model.item.description);
this.$.revokeDeviceDialog.open = true;
}
_buySubscription(e) {
this.$.paymentDialog.promo = this.account.promo;
this.$.paymentDialog.plan = this.account.plan;
this.$.paymentDialog.show((e && e.target.dataset.source) || this.referer).then(success => {
if (success) {
this._refreshAccount();
this.alert($l("Congratulations, you've successfully upgraded to Padlock Pro!"), { type: "success" });
}
});
}
_cancelSubscription() {
this.$.cancelSubscriptionDialog.open = true;
}
_updatePaymentMethod() {
this.$.paymentDialog.plan = null;
this.$.paymentDialog.show("Dashboard").then(success => {
if (success) {
this._refreshAccount();
this.notify($l("Payment method updated successfully!"), "info", 2000);
}
});
}
_back() {
track("Dashboard: Back", {}).then(() => {
setTimeout(() => (window.location = "https://padlock.io/"), 200);
});
window.location = "padlock://?ref=dashboard";
}
_openBillingDialog() {
this.$.billingDialog.open = true;
}
_showInvoices(e) {
const button = e.target;
button.start();
request("GET", "/invoices/", undefined, new Map([["Accept", "application/json"]])).then(res => {
this._invoices = JSON.parse(res.responseText);
this.$.invoicesDialog.open = true;
button.success();
});
}
_formatTimestamp(ts) {
return new Date(ts * 1000).toLocaleDateString();
}
_hasNoDevices() {
return !this.account.devices.length;
}
_downloadApp() {
window.open("https://padlock.io/downloads/", "_blank");
}
_promoExpired() {
this.notifyPath("account.promo");
}
_paymentSourceLabel(s) {
return s && `${s.brand} •••• •••• •••• ${s.lastFour}`;
}
_reactivateSubscription() {
this.subscribe(undefined, undefined, "Dashboard")
.then(() => {
this._refreshAccount();
this.alert($l("Subscription reactivated successfully!"), { type: "success" });
})
.catch(e => this.alert(e.message, { type: "warning" }));
}
_deleteAccount() {
this.prompt(
$l("Are you sure you want to delete your Padlock online account?"),
$l("Type 'DELETE' To Confirm"),
"text",
$l("Delete Account"),
$l("Cancel"),
false,
val => {
return val === "DELETE" ? Promise.resolve(true) : Promise.reject("Type 'DELETE' to confirm!");
}
).then(confirmed => {
if (confirmed === true) {
this.$.deleteAccountButton.start();
const params = new URLSearchParams();
params.set("gorilla.csrf.Token", this.csrfToken);
request(
"POST",
"/deleteaccount/",
params.toString(),
new Map([["Content-Type", "application/x-www-form-urlencoded"], ["Accept", "application/json"]])
)
.then(() => {
this.$.deleteAccountButton.success();
this.alert("Account deleted successfully. Sorry to see you go!", { type: "success" }).then(() =>
window.close()
);
})
.catch(e => {
this.$.deleteAccountButton.fail();
this.alert(e.message, { type: "warning" });
});
}
});
}
_showAdvancedOptions() {
this._showAdvanced = true;
}
}
window.customElements.define(Dashboard.is, Dashboard);

View File

@ -63,10 +63,10 @@ class PlExport extends applyMixins(BaseElement, DataMixin, LocaleMixin, DialogMi
_downloadCSV() {
this.confirm(exportCSVWarning, $l("Download"), $l("Cancel"), { type: "warning" }).then(confirm => {
if (confirm) {
setTimeout(() => {
setTimeout(async () => {
const date = new Date().toISOString().substr(0, 10);
const fileName = `padlock-export-${date}.csv`;
const csv = toCSV(this.exportRecords);
const csv = await toCSV(this.exportRecords);
const a = document.createElement("a");
a.href = `data:application/octet-stream,${encodeURIComponent(csv)}`;
a.download = fileName;
@ -77,24 +77,25 @@ class PlExport extends applyMixins(BaseElement, DataMixin, LocaleMixin, DialogMi
});
}
_copyCSV() {
this.confirm(exportCSVWarning, $l("Copy to Clipboard"), $l("Cancel"), { type: "warning" }).then(confirm => {
if (confirm) {
setClipboard(toCSV(this.exportRecords)).then(() =>
this.alert(
$l(
"Your data has successfully been copied to the system " +
"clipboard. You can now paste it into the spreadsheet program of your choice."
)
)
);
this.dispatch("data-exported");
}
async _copyCSV() {
const confirmed = await this.confirm(exportCSVWarning, $l("Copy to Clipboard"), $l("Cancel"), {
type: "warning"
});
if (confirmed) {
const csv = await toCSV(this.exportRecords);
setClipboard(csv);
this.alert(
$l(
"Your data has successfully been copied to the system " +
"clipboard. You can now paste it into the spreadsheet program of your choice."
)
);
this.dispatch("data-exported");
}
}
_getEncryptedData() {
return this.prompt(
async _getEncryptedData() {
const pwd = await this.prompt(
$l(
"Please choose a password to protect your data. This may be the same as " +
"your master password or something else, but make sure it is sufficiently strong!"
@ -103,35 +104,38 @@ class PlExport extends applyMixins(BaseElement, DataMixin, LocaleMixin, DialogMi
"password",
$l("Confirm"),
$l("Cancel")
).then(pwd => {
if (!pwd) {
if (pwd === "") {
this.alert($l("Please enter a password!"));
}
return Promise.reject();
}
if (passwordStrength(pwd).score < 2) {
return this.confirm(
$l(
"WARNING: The password you entered is weak which makes it easier for " +
"attackers to break the encryption used to protect your data. Try to use a longer " +
"password or include a variation of uppercase, lowercase and special characters as " +
"well as numbers."
),
$l("Use Anyway"),
$l("Choose Different Password"),
{ type: "warning" }
).then(confirm => {
if (!confirm) {
return Promise.reject();
}
);
return toPadlock(this.exportRecords, pwd);
});
} else {
return toPadlock(this.exportRecords, pwd);
if (!pwd) {
if (pwd === "") {
this.alert($l("Please enter a password!"));
}
});
return Promise.reject();
}
const strength = await passwordStrength(pwd);
if (strength.score < 2) {
const confirmed = await this.confirm(
$l(
"WARNING: The password you entered is weak which makes it easier for " +
"attackers to break the encryption used to protect your data. Try to use a longer " +
"password or include a variation of uppercase, lowercase and special characters as " +
"well as numbers."
),
$l("Use Anyway"),
$l("Choose Different Password"),
{ type: "warning" }
);
if (confirmed) {
return toPadlock(this.exportRecords, pwd);
} else {
return this._getEncryptedData();
}
} else {
return toPadlock(this.exportRecords, pwd);
}
}
_downloadEncrypted() {

View File

@ -383,11 +383,11 @@ class SettingsView extends applyMixins(BaseElement, DataMixin, LocaleMixin, Dial
});
}
_importString(rawStr) {
async _importString(rawStr) {
const isPadlock = imp.isFromPadlock(rawStr);
const isSecuStore = imp.isFromSecuStore(rawStr);
const isLastPass = imp.isFromLastPass(rawStr);
const isCSV = imp.isCSV(rawStr);
const isCSV = await imp.isCSV(rawStr);
return Promise.resolve()
.then(() => {
if (isPadlock || isSecuStore) {

View File

@ -1,7 +1,7 @@
import "../styles/shared.js";
import { BaseElement, html } from "./base.js";
import { applyMixins, wait, passwordStrength } from "../core/util";
import { isTouch, getAppStoreLink, checkForUpdates } from "../core/platform";
import { applyMixins, wait, passwordStrength } from "../core/util.js";
import { isTouch, getAppStoreLink, checkForUpdates } from "../core/platform.js";
import { localize as $l } from "../core/locale.js";
import { track } from "../core/tracking.js";
import * as stats from "../core/stats.js";
@ -336,7 +336,7 @@ class StartView extends applyMixins(BaseElement, DataMixin, LocaleMixin, DialogM
</div>
<div class="strength-meter">[[ _pwdStrength ]]</div>
<div class="strength-meter">[[ _displayPwdStrength ]]</div>
</div>
<div class="hint">
@ -399,6 +399,7 @@ class StartView extends applyMixins(BaseElement, DataMixin, LocaleMixin, DialogM
static get properties() {
return {
newPwd: String,
open: {
type: Boolean,
value: false,
@ -422,12 +423,21 @@ class StartView extends applyMixins(BaseElement, DataMixin, LocaleMixin, DialogM
computed: "_computeMode(_hasData)"
},
_pwdStrength: {
type: Object
},
_displayPwdStrength: {
type: String,
computed: "_passwordStrength(newPwd)"
computed: "_computeDisplayPwdStrength(_pwdStrength.score)"
}
};
}
static get observers() {
return [
"_updatePwdStrength(newPwd)"
];
}
constructor() {
super();
this._failCount = 0;
@ -584,7 +594,7 @@ class StartView extends applyMixins(BaseElement, DataMixin, LocaleMixin, DialogM
track("Setup: Email", { Skipped: true });
}
_enterNewPassword() {
async _enterNewPassword() {
this.$.newPasswordInput.blur();
const pwd = this.$.newPasswordInput.value;
@ -602,7 +612,8 @@ class StartView extends applyMixins(BaseElement, DataMixin, LocaleMixin, DialogM
track("Setup: Choose Password");
};
if (passwordStrength(pwd).score < 2) {
const strength = await passwordStrength(pwd);
if (strength.score < 2) {
this.choose(
$l(
"The password you entered is weak which makes it easier for attackers to break " +
@ -782,8 +793,11 @@ class StartView extends applyMixins(BaseElement, DataMixin, LocaleMixin, DialogM
});
}
_passwordStrength(pwd) {
const score = pwd ? passwordStrength(pwd).score : -1;
async _updatePwdStrength(newPwd) {
this._pwdStrength = newPwd ? await passwordStrength(newPwd) : null;
}
_computeDisplayPwdStrength(score = -1) {
const strength = score === -1 ? "" : score < 2 ? $l("weak") : score < 4 ? $l("medium") : $l("strong");
return strength && $l("strength: {0}", strength);
}

View File

@ -1,8 +1,8 @@
import '../../../../node_modules/@polymer/polymer/polymer-legacy.js';
const $_documentContainer = document.createElement('template');
$_documentContainer.setAttribute('style', 'display: none;');
import "../../node_modules/@polymer/polymer/polymer-legacy.js";
const _documentContainer = document.createElement("template");
_documentContainer.setAttribute("style", "display: none;");
$_documentContainer.innerHTML = `<custom-style>
_documentContainer.innerHTML = `<custom-style>
<style is="custom-style">
body {
@ -136,4 +136,4 @@ $_documentContainer.innerHTML = `<custom-style>
</style>
</custom-style>`;
document.head.appendChild($_documentContainer.content);
document.head.appendChild(_documentContainer.content);

View File

@ -1,8 +1,8 @@
import './config.js';
const $_documentContainer = document.createElement('template');
$_documentContainer.setAttribute('style', 'display: none;');
import "./config.js";
const _documentContainer = document.createElement("template");
_documentContainer.setAttribute("style", "display: none;");
$_documentContainer.innerHTML = `<dom-module id="shared">
_documentContainer.innerHTML = `<dom-module id="shared">
<template>
<style>
:host {
@ -353,4 +353,4 @@ $_documentContainer.innerHTML = `<dom-module id="shared">
</template>
</dom-module>`;
document.head.appendChild($_documentContainer.content);
document.head.appendChild(_documentContainer.content);

1654
app/vendor/papaparse.js vendored Executable file

File diff suppressed because it is too large Load Diff

1516
app/vendor/semver.js vendored Normal file

File diff suppressed because it is too large Load Diff

28
app/vendor/zxcvbn.js vendored Normal file

File diff suppressed because one or more lines are too long

18
package-lock.json generated
View File

@ -190,9 +190,9 @@
"dev": true
},
"@types/papaparse": {
"version": "4.1.30",
"resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-4.1.30.tgz",
"integrity": "sha512-TAD0bBzBuAMojwLfxmVmHvHwWpefIC+H3PUk19xzmg5YAzPQ0Q/vOXy3JQvgNPhItQW7QKoaN4Rp2DpCIs+ACQ==",
"version": "4.1.34",
"resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-4.1.34.tgz",
"integrity": "sha512-SuX4vK/DtIriwrjFbA7YwrAu+JNkc2G/5Pd9PrGhKlfCSrJxlUg4xqKyKT4b9y90OKGWUnnBeJDx/iNJdBlukQ==",
"dev": true
},
"@types/parse5": {
@ -6647,9 +6647,9 @@
}
},
"moment": {
"version": "2.21.0",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.21.0.tgz",
"integrity": "sha512-TCZ36BjURTeFTM/CwRcViQlfkMvL1/vFISuNLO5GkcVm1+QHfbSiNqZuWeMFjj1/3+uAjXswgRk30j1kkLYJBQ=="
"version": "2.22.2",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz",
"integrity": "sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y="
},
"moment-duration-format": {
"version": "2.2.2",
@ -7063,9 +7063,9 @@
"dev": true
},
"papaparse": {
"version": "4.3.5",
"resolved": "https://registry.npmjs.org/papaparse/-/papaparse-4.3.5.tgz",
"integrity": "sha1-ts31yub+nsYDsb5m8RSmOsZFoDY="
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/papaparse/-/papaparse-4.4.0.tgz",
"integrity": "sha1-a829qAhz4Az7C9zXpFcccqmkAWg="
},
"parents": {
"version": "1.0.1",

View File

@ -15,7 +15,7 @@
"@types/core-js": "^0.9.35",
"@types/mocha": "^2.2.33",
"@types/moment-duration-format": "^2.2.0",
"@types/papaparse": "^4.1.28",
"@types/papaparse": "^4.1.34",
"@types/semver": "^5.3.33",
"@types/zxcvbn": "^4.4.0",
"archiver": "^1.2.0",
@ -47,21 +47,11 @@
"watchify": "^3.7.0"
},
"dependencies": {
"@polymer/iron-list": "^3.0.0-pre.18",
"@polymer/paper-spinner": "^3.0.0-pre.18",
"@polymer/polymer": "^3.0.0",
"@webcomponents/webcomponentsjs": "^2.0.0",
"autosize": "^4.0.2",
"electron-store": "^1.1.0",
"electron-updater": "^2.21.0",
"fs-extra": "^5.0.0",
"moment": "^2.21.0",
"moment-duration-format": "^2.2.2",
"papaparse": "^4.1.2",
"semver": "^5.5.0",
"uuid": "^3.1.0",
"yargs": "^4.8.1",
"zxcvbn": "^4.4.2"
"yargs": "^4.8.1"
},
"scripts": {
"compile": "gulp compile --silent",

1
typings/dom.d.ts vendored
View File

@ -4,6 +4,7 @@ declare interface Window {
webkitRequestFileSystem: any;
PERSISTENT: number;
require: (m: String) => any;
zxcvbn: (password: string, userInputs?: string[]) => zxcvbn.ZXCVBNResult;
}
declare interface Navigator {