Merge pull request #466 from padloc/feature/update-v2-auto-migration
Update automatic migration of legacy (v2) accounts.
This commit is contained in:
commit
dc62acd832
|
@ -23,6 +23,8 @@ import { displayProvisioning } from "../lib/provisioning";
|
|||
import { StartAuthRequestResponse } from "@padloc/core/src/api";
|
||||
import { Confetti } from "./confetti";
|
||||
import { singleton } from "../lib/singleton";
|
||||
import { PBES2Container } from "@padloc/core/src/container";
|
||||
import { importLegacyContainer } from "../lib/import";
|
||||
|
||||
@customElement("pl-login-signup")
|
||||
export class LoginOrSignup extends StartForm {
|
||||
|
@ -158,10 +160,12 @@ export class LoginOrSignup extends StartForm {
|
|||
authenticatorIndex?: number;
|
||||
pendingRequest?: StartAuthRequestResponse;
|
||||
}): Promise<{
|
||||
email: string;
|
||||
token: string;
|
||||
accountStatus: AccountStatus;
|
||||
provisioning: AccountProvisioning;
|
||||
deviceTrusted: boolean;
|
||||
legacyData?: PBES2Container;
|
||||
} | null> {
|
||||
try {
|
||||
if (!req) {
|
||||
|
@ -238,6 +242,11 @@ export class LoginOrSignup extends StartForm {
|
|||
return;
|
||||
}
|
||||
|
||||
if (authRes.accountStatus === AccountStatus.Unregistered && authRes.legacyData) {
|
||||
this._migrateLegacyAccount(authRes);
|
||||
return;
|
||||
}
|
||||
|
||||
router.go(authRes.accountStatus === AccountStatus.Active ? "login" : "signup", {
|
||||
...this.router.params,
|
||||
email,
|
||||
|
@ -317,15 +326,6 @@ export class LoginOrSignup extends StartForm {
|
|||
|
||||
this.go("start", { email });
|
||||
|
||||
// if (!verify.hasAccount) {
|
||||
// if (verify.hasLegacyAccount) {
|
||||
// this._migrateAccount(email, password, verify.legacyToken!, verify.token);
|
||||
// } else {
|
||||
// this._accountDoesntExist(email);
|
||||
// }
|
||||
// return;
|
||||
// }
|
||||
|
||||
return;
|
||||
case ErrorCode.INVALID_CREDENTIALS:
|
||||
this._loginError = $l("Wrong master password. Please try again!");
|
||||
|
@ -489,6 +489,95 @@ export class LoginOrSignup extends StartForm {
|
|||
}
|
||||
}
|
||||
|
||||
protected async _migrateLegacyAccount(authResponse: {
|
||||
email: string;
|
||||
legacyData?: PBES2Container;
|
||||
token: string;
|
||||
}): Promise<boolean> {
|
||||
const legacyData = authResponse.legacyData!;
|
||||
|
||||
this._submitEmailButton.start();
|
||||
|
||||
const choice = await alert(
|
||||
$l(
|
||||
"You don't have a Padloc 4 account yet but we've found " +
|
||||
"an account from an older version. " +
|
||||
"Would you like to migrate your account to Padloc 4 now?"
|
||||
),
|
||||
{
|
||||
title: "Account Migration",
|
||||
icon: "user",
|
||||
options: [$l("Migrate"), $l("Learn More"), $l("Cancel")],
|
||||
}
|
||||
);
|
||||
|
||||
if (choice === 1) {
|
||||
window.open("https://padloc.app/help/migrate-v3", "_system");
|
||||
return this._migrateLegacyAccount(authResponse);
|
||||
} else if (choice === 2) {
|
||||
this._submitEmailButton.stop();
|
||||
return false;
|
||||
}
|
||||
|
||||
const password = await prompt($l("Please enter your master password!"), {
|
||||
title: $l("Migrating Account"),
|
||||
placeholder: $l("Enter Master Password"),
|
||||
confirmLabel: $l("Submit"),
|
||||
type: "password",
|
||||
preventAutoClose: true,
|
||||
validate: async (password: string) => {
|
||||
try {
|
||||
await legacyData.unlock(password);
|
||||
} catch (e) {
|
||||
throw $l("Wrong password! Please try again!");
|
||||
}
|
||||
return password;
|
||||
},
|
||||
});
|
||||
const items = await importLegacyContainer(legacyData);
|
||||
|
||||
if (items && password) {
|
||||
await this.app.signup({ email: authResponse.email, password, name: "", authToken: authResponse.token });
|
||||
await this.app.addItems(items, this.app.mainVault!);
|
||||
const deleteLegacy = await confirm(
|
||||
$l(
|
||||
"Your account and all associated data was migrated successfully! Do you want to delete your old account now?"
|
||||
),
|
||||
$l("Yes"),
|
||||
$l("No"),
|
||||
{ title: $l("Delete Legacy Account"), icon: "delete", preventAutoClose: true }
|
||||
);
|
||||
|
||||
if (deleteLegacy) {
|
||||
await this.app.api.deleteLegacyAccount();
|
||||
}
|
||||
|
||||
await alert(
|
||||
$l(
|
||||
"All done! Please note that you won't be able to access your Padloc 4 account " +
|
||||
"with older versions of the app, so please make sure you have the latest version installed " +
|
||||
"on all your devices! (You can find download links for all platforms at " +
|
||||
"https://padloc.app/downloads/). Enjoy using Padloc 4!"
|
||||
),
|
||||
{
|
||||
title: $l("Migration Complete"),
|
||||
type: "success",
|
||||
}
|
||||
);
|
||||
|
||||
const { email: _email, name: _name, authToken, deviceTrusted, ...params } = this.router.params;
|
||||
this.go("signup/success", params);
|
||||
this._submitEmailButton.success();
|
||||
return true;
|
||||
} else {
|
||||
alert($l("Unfortunately we could not complete migration of your data."), {
|
||||
type: "warning",
|
||||
});
|
||||
this._submitEmailButton.stop();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static styles = [
|
||||
...StartForm.styles,
|
||||
css`
|
||||
|
|
|
@ -1,13 +1,7 @@
|
|||
import { translate as $l } from "@padloc/locale/src/translate";
|
||||
import { GetLegacyDataParams } from "@padloc/core/src/api";
|
||||
import { VaultItem } from "@padloc/core/src/item";
|
||||
import { mixins, shared } from "../styles";
|
||||
import { Routing } from "../mixins/routing";
|
||||
import { StateMixin } from "../mixins/state";
|
||||
import { animateElement, animateCascade } from "../lib/animation";
|
||||
import { alert, confirm, prompt } from "../lib/dialog";
|
||||
import { importLegacyContainer } from "../lib/import";
|
||||
import { app } from "../globals";
|
||||
import { Logo } from "./logo";
|
||||
import "./icon";
|
||||
import { css, LitElement } from "lit";
|
||||
|
@ -151,101 +145,4 @@ export abstract class StartForm extends Routing(StateMixin(LitElement)) {
|
|||
rumble() {
|
||||
animateElement(this.renderRoot.querySelector("form")!, { animation: "rumble", duration: 200, clear: true });
|
||||
}
|
||||
|
||||
protected async _migrateAccount(
|
||||
email: string,
|
||||
password: string,
|
||||
legacyToken: string,
|
||||
authToken: string,
|
||||
name = ""
|
||||
): Promise<boolean> {
|
||||
const choice = await alert(
|
||||
$l(
|
||||
"You don't have a Padloc 3 account yet but we've found " +
|
||||
"an account from an older version. " +
|
||||
"Would you like to migrate your account to Padloc 3 now?"
|
||||
),
|
||||
{
|
||||
title: "Account Migration",
|
||||
icon: "user",
|
||||
options: [$l("Migrate"), $l("Learn More"), $l("Cancel")],
|
||||
}
|
||||
);
|
||||
|
||||
if (choice === 1) {
|
||||
window.open("https://padloc.app/help/migrate-v3", "_system");
|
||||
return this._migrateAccount(email, password, legacyToken, authToken, name);
|
||||
} else if (choice === 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const legacyData = await app.api.getLegacyData(
|
||||
new GetLegacyDataParams({
|
||||
email,
|
||||
verify: legacyToken,
|
||||
})
|
||||
);
|
||||
|
||||
let items: VaultItem[] | null = null;
|
||||
try {
|
||||
if (!password) {
|
||||
throw "No password provided";
|
||||
}
|
||||
await legacyData.unlock(password);
|
||||
items = await importLegacyContainer(legacyData);
|
||||
} catch (e) {
|
||||
password = await prompt($l("Please enter your master password!"), {
|
||||
title: $l("Migrating Account"),
|
||||
placeholder: $l("Enter Master Password"),
|
||||
confirmLabel: $l("Submit"),
|
||||
type: "password",
|
||||
preventAutoClose: true,
|
||||
validate: async (password: string) => {
|
||||
try {
|
||||
await legacyData.unlock(password);
|
||||
items = await importLegacyContainer(legacyData);
|
||||
} catch (e) {
|
||||
throw $l("Wrong password! Please try again!");
|
||||
}
|
||||
return password;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (items && password) {
|
||||
await app.signup({ email, password, authToken, name });
|
||||
await app.addItems(items, app.mainVault!);
|
||||
const deleteLegacy = await confirm(
|
||||
$l(
|
||||
"Your account and all associated data was migrated successfully! Do you want to delete your old account now?"
|
||||
),
|
||||
$l("Yes"),
|
||||
$l("No"),
|
||||
{ title: $l("Delete Legacy Account"), icon: "delete", preventAutoClose: true }
|
||||
);
|
||||
|
||||
if (deleteLegacy) {
|
||||
await app.api.deleteLegacyAccount();
|
||||
}
|
||||
|
||||
await alert(
|
||||
$l(
|
||||
"All done! Please note that you won't be able to access your Padloc 3 account " +
|
||||
"with older versions of the app, so please make sure you have the latest version installed " +
|
||||
"on all your devices! (You can find download links for all platforms at " +
|
||||
"https://padloc.app/downloads/). Enjoy using Padloc 3!"
|
||||
),
|
||||
{
|
||||
title: $l("Migration Complete"),
|
||||
type: "success",
|
||||
}
|
||||
);
|
||||
return true;
|
||||
} else {
|
||||
alert($l("Unfortunately we could not complete migration of your data."), {
|
||||
type: "warning",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -277,6 +277,7 @@ export class WebPlatform extends StubPlatform implements Platform {
|
|||
async completeAuthRequest(req: StartAuthRequestResponse) {
|
||||
if (req.requestStatus === AuthRequestStatus.Verified) {
|
||||
return {
|
||||
email: req.email,
|
||||
token: req.token,
|
||||
deviceTrusted: req.deviceTrusted,
|
||||
accountStatus: req.accountStatus!,
|
||||
|
@ -290,7 +291,7 @@ export class WebPlatform extends StubPlatform implements Platform {
|
|||
throw new Err(ErrorCode.AUTHENTICATION_FAILED, $l("The request was canceled."));
|
||||
}
|
||||
|
||||
const { accountStatus, deviceTrusted, provisioning } = await app.api.completeAuthRequest(
|
||||
const { accountStatus, deviceTrusted, provisioning, legacyData } = await app.api.completeAuthRequest(
|
||||
new CompleteAuthRequestParams({
|
||||
id: req.id,
|
||||
data,
|
||||
|
@ -299,10 +300,12 @@ export class WebPlatform extends StubPlatform implements Platform {
|
|||
);
|
||||
|
||||
return {
|
||||
email: req.email,
|
||||
token: req.token,
|
||||
deviceTrusted,
|
||||
accountStatus,
|
||||
provisioning,
|
||||
legacyData,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -200,6 +200,9 @@ export class CompleteAuthRequestResponse extends Serializable {
|
|||
@AsSerializable(AccountProvisioning)
|
||||
provisioning!: AccountProvisioning;
|
||||
|
||||
@AsSerializable(PBES2Container)
|
||||
legacyData?: PBES2Container;
|
||||
|
||||
constructor(props?: Partial<CompleteAuthRequestResponse>) {
|
||||
super();
|
||||
props && Object.assign(this, props);
|
||||
|
|
|
@ -8,6 +8,7 @@ import { KeyStoreEntryInfo } from "./key-store";
|
|||
import { SessionInfo } from "./session";
|
||||
import { SRPSession } from "./srp";
|
||||
import { getIdFromEmail, uuid } from "./util";
|
||||
import { PBES2Container } from "./container";
|
||||
|
||||
export enum AuthPurpose {
|
||||
Signup = "signup",
|
||||
|
@ -227,6 +228,9 @@ export class Auth extends Serializable implements Storable {
|
|||
expires: string;
|
||||
}[] = [];
|
||||
|
||||
@AsSerializable(PBES2Container)
|
||||
legacyData?: PBES2Container;
|
||||
|
||||
constructor(public email: string = "") {
|
||||
super();
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import { Storage, MemoryStorage } from "./storage";
|
|||
import { AccountStatus, AuthPurpose, AuthType } from "./auth";
|
||||
import { AccountProvisioning } from "./provisioning";
|
||||
import { StartAuthRequestResponse } from "./api";
|
||||
import { PBES2Container } from "./container";
|
||||
|
||||
/**
|
||||
* Object representing all information available for a given device.
|
||||
|
@ -116,10 +117,12 @@ export interface Platform {
|
|||
}): Promise<StartAuthRequestResponse>;
|
||||
|
||||
completeAuthRequest(req: StartAuthRequestResponse): Promise<{
|
||||
email: string;
|
||||
token: string;
|
||||
accountStatus: AccountStatus;
|
||||
deviceTrusted: boolean;
|
||||
provisioning: AccountProvisioning;
|
||||
legacyData?: PBES2Container;
|
||||
}>;
|
||||
|
||||
readonly platformAuthType: AuthType | null;
|
||||
|
@ -192,10 +195,12 @@ export class StubPlatform implements Platform {
|
|||
}
|
||||
|
||||
async completeAuthRequest(_req: StartAuthRequestResponse): Promise<{
|
||||
email: string;
|
||||
token: string;
|
||||
accountStatus: AccountStatus;
|
||||
deviceTrusted: boolean;
|
||||
provisioning: AccountProvisioning;
|
||||
legacyData?: PBES2Container;
|
||||
}> {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
|
@ -290,10 +295,12 @@ export function startAuthRequest(opts: {
|
|||
}
|
||||
|
||||
export function completeAuthRequest(req: StartAuthRequestResponse): Promise<{
|
||||
email: string;
|
||||
token: string;
|
||||
accountStatus: AccountStatus;
|
||||
deviceTrusted: boolean;
|
||||
provisioning: AccountProvisioning;
|
||||
legacyData?: PBES2Container;
|
||||
}> {
|
||||
return platform.completeAuthRequest(req);
|
||||
}
|
||||
|
|
|
@ -473,6 +473,7 @@ export class Controller extends API {
|
|||
accountStatus: auth.accountStatus,
|
||||
deviceTrusted,
|
||||
provisioning: provisioning.account,
|
||||
legacyData: auth.legacyData,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1781,6 +1782,13 @@ export class Controller extends API {
|
|||
if (!auth) {
|
||||
auth = new Auth(email);
|
||||
await auth.init();
|
||||
|
||||
// We didn't find anything for this user in the database.
|
||||
// Let's see if there is any legacy (v2) data for this user.
|
||||
const legacyData = await this.legacyServer?.getStore(email);
|
||||
if (legacyData) {
|
||||
auth.legacyData = legacyData;
|
||||
}
|
||||
}
|
||||
|
||||
let updateAuth = false;
|
||||
|
|
Loading…
Reference in New Issue