mirror of
https://github.com/bitwarden/browser
synced 2025-12-20 02:03:39 +00:00
[PM-4195] LastPass importer flow (#6541)
* Split up import/export into separate modules * Fix routing and apply PR feedback * Renamed OrganizationExport exports to OrganizationVaultExport * Make import dialogs standalone and move them to libs/importer * Make import.component re-usable - Move functionality which was previously present on the org-import.component into import.component - Move import.component into libs/importer Make import.component standalone Create import-web.component to represent Web UI Fix module imports and routing Remove unused org-import-files * Enable importing on deskop Create import-dialog Create file-menu entry to open import-dialog Extend messages.json to include all the necessary messages from shared components * Renamed filenames according to export rename * Make ImportWebComponent standalone, simplify routing * Pass organizationId as Input to ImportComponent * use formLoading and formDisabled outputs * use formLoading & formDisabled in desktop * Emit an event when the import succeeds Remove Angular router from base-component as other clients might not have routing (i.e. desktop) Move logic that happened on web successful import into the import-web.component * Enable importing on deskop Create import-dialog Create file-menu entry to open import-dialog Extend messages.json to include all the necessary messages from shared components * use formLoading & formDisabled in desktop * Add missing message for importBlockedByPolicy callout * Remove commented code for submit button * Implement onSuccessfulImport to close dialog on success * fix table themes on desktop & browser * fix fileSelector button styles * update selectors to use tools prefix; remove unused selectors * update selectors * Wall off UI components in libs/importer Create barrel-file for libs/importer/components Remove components and dialog exports from libs/importer/index.ts Extend libs/shared/tsconfig.libs.json to include @bitwarden/importer/ui -> libs/importer/components Extend apps/web/tsconfig.ts to include @bitwarden/importer/ui Update all usages * Rename @bitwarden/importer to @bitwarden/importer/core Create more barrel files in libs/importer/* Update imports within libs/importer Extend tsconfig files Update imports in web, desktop, browser and cli * import-lastpass wip * Lazy-load the ImportWebComponent via both routes * Fix import path for ImportComponent * add validation; add shared folders field * clean up logic * fill fileContent on account change * Use SharedModule as import in import-web.component * show spinner on pending validation; properly debounce; refactor to loadCSVData func * fix pending submit guard * hide on web, show on desktop & browser * reset user agent fieldset styles * fix validation * File selector should be displayed as secondary * update validation * Fix setUserTypeContext always throwing * refactor to password dialog approach * remove control on destroy; dont submit on enter keydown * helper to serialize vault accounts (#6556) * helper to serialize vault accounts * prettier * add prompts * Add missing messages for file-password-prompt * Add missing messages for import-error-dialog * Add missing message for import-success-dialog * Create client-info * Separate submit and handling import, add error-handling * Move catch and error handling into submit * Remove AsyncValidator logic from handleImport * Add support for filtering shared accounts * add sso flow to lp import (#6574) * stub out some sso flow * use computer props * lastpass callback * baseOpenIDConnectAuthority * openIDConnectAuthorityBase * comments * camelCase user type context model * processSigninResponse * Refactor handleImport * use large dialogSize * remove extra setUserTypeContext * fix passwordGenerationService provider; pass all errors to ValidationErrors * add await SSO dialog & logic * Move lastpass related files into separate folder * Use bitSubmit to override submit preventDefault (#6607) Co-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com> * Use large dialogSize * revert jslib changes * PM-4398 - Add missing importWarning * make ui class methods async * add LastPassDirectImportService * update error handling * add OOB methods (manual passcode only) * fix typo * respond to SSO callback * localize error messages * remove uneeded comment * update i18n * add await sso i18n * add not implemented error to service * fix getting k2 * fix k1 bugs * null checks should not be strict * update awaiting sso dialog * update approveDuoWebSdk * refactor oob login flow * Removing fieldset due to merge of https://github.com/bitwarden/clients/pull/6626 * Refactoring to push logic into the service vs the component Move all methods related to MFA-UI into a LastPassDirectImportUIService Move all logic around the import into a LastPassDirectImportService The component now only has the necessary flows but no knowledge on how to use the lastpass import lib or the need for a OIDC client * Remove unneeded passwordGenerationService * move all import logic to service * apply code review: remove name attributes; use protected fields; use formGroup.value * rename submit method and add comment * update textarea id * update i18n * remove rogue todo comment * extract helper asyncValidatorsFinished * Remove files related to DuoUI we didn't need to differentiate for MFA via Duo * Add missing import * revert formGroup.value access * add email to signInRequest * add try again error message * add try again i18n * consistent clientinfo id (#6654) --------- Co-authored-by: William Martin <contact@willmartian.com> * hide on browser * add lastpass prefix * add shared i18n copy to web and browser * rename deeplink * use protected field * rename el ids * refactor: remove nested conditional * update form ids in consuming client components * remove unnecessary return statement * fix file id * use ngIf * use hidden because of getElementById * Remove OIDC lib logging --------- Co-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com> Co-authored-by: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> Co-authored-by: Kyle Spearrin <kspearrin@users.noreply.github.com> Co-authored-by: Kyle Spearrin <kyle.spearrin@gmail.com>
This commit is contained in:
@@ -1,6 +0,0 @@
|
||||
export enum DuoFactor {
|
||||
Push,
|
||||
Call,
|
||||
Passcode,
|
||||
SendPasscodesBySms,
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
export enum DuoStatus {
|
||||
Success,
|
||||
Error,
|
||||
Info,
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
export { DuoFactor } from "./duo-factor";
|
||||
export { DuoStatus } from "./duo-status";
|
||||
export { IdpProvider } from "./idp-provider";
|
||||
export { LastpassLoginType } from "./lastpass-login-type";
|
||||
export { OtpMethod } from "./otp-method";
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
|
||||
import { Platform } from "../enums";
|
||||
|
||||
export class ClientInfo {
|
||||
@@ -7,7 +5,7 @@ export class ClientInfo {
|
||||
id: string;
|
||||
description: string;
|
||||
|
||||
static createClientInfo(): ClientInfo {
|
||||
return { platform: Platform.Desktop, id: Utils.newGuid(), description: "Importer" };
|
||||
static createClientInfo(id: string): ClientInfo {
|
||||
return { platform: Platform.Desktop, id, description: "Importer" };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,22 +273,9 @@ export class Client {
|
||||
ui: Ui,
|
||||
rest: RestClient
|
||||
): Promise<Session> {
|
||||
const answer = await this.approveOob(username, parameters, ui, rest);
|
||||
if (answer == OobResult.cancel) {
|
||||
throw new Error("Out of band step is canceled by the user");
|
||||
}
|
||||
|
||||
const extraParameters = new Map<string, any>();
|
||||
if (answer.waitForOutOfBand) {
|
||||
extraParameters.set("outofbandrequest", 1);
|
||||
} else {
|
||||
extraParameters.set("otp", answer.passcode);
|
||||
}
|
||||
|
||||
let session: Session = null;
|
||||
for (;;) {
|
||||
// In case of the OOB auth the server doesn't respond instantly. This works more like a long poll.
|
||||
// The server times out in about 10 seconds so there's no need to back off.
|
||||
// In case of the OOB auth the server doesn't respond instantly. This works more like a long poll.
|
||||
// The server times out in about 10 seconds so there's no need to back off.
|
||||
const attemptLogin = async (extraParameters: Map<string, any>): Promise<Session> => {
|
||||
const response = await this.performSingleLoginRequest(
|
||||
username,
|
||||
password,
|
||||
@@ -298,9 +285,9 @@ export class Client {
|
||||
rest
|
||||
);
|
||||
|
||||
session = this.extractSessionFromLoginResponse(response, keyIterationCount, clientInfo);
|
||||
const session = this.extractSessionFromLoginResponse(response, keyIterationCount, clientInfo);
|
||||
if (session != null) {
|
||||
break;
|
||||
return session;
|
||||
}
|
||||
|
||||
if (this.getOptionalErrorAttribute(response, "cause") != "outofbandrequired") {
|
||||
@@ -310,11 +297,37 @@ export class Client {
|
||||
// Retry
|
||||
extraParameters.set("outofbandretry", "1");
|
||||
extraParameters.set("outofbandretryid", this.getErrorAttribute(response, "retryid"));
|
||||
}
|
||||
|
||||
if (answer.rememberMe) {
|
||||
await this.markDeviceAsTrusted(session, clientInfo, rest);
|
||||
}
|
||||
return attemptLogin(extraParameters);
|
||||
};
|
||||
|
||||
const pollingLoginSession = () => {
|
||||
const extraParameters = new Map<string, any>();
|
||||
extraParameters.set("outofbandrequest", 1);
|
||||
return attemptLogin(extraParameters);
|
||||
};
|
||||
|
||||
const passcodeLoginSession = async () => {
|
||||
const answer = await this.approveOob(username, parameters, ui, rest);
|
||||
|
||||
if (answer == OobResult.cancel) {
|
||||
throw new Error("Out of band step is canceled by the user");
|
||||
}
|
||||
const extraParameters = new Map<string, any>();
|
||||
extraParameters.set("otp", answer.passcode);
|
||||
const session = await attemptLogin(extraParameters);
|
||||
if (answer.rememberMe) {
|
||||
await this.markDeviceAsTrusted(session, clientInfo, rest);
|
||||
}
|
||||
return session;
|
||||
};
|
||||
|
||||
const session: Session = await Promise.race([
|
||||
pollingLoginSession(),
|
||||
passcodeLoginSession(),
|
||||
]).finally(() => {
|
||||
ui.closeMFADialog();
|
||||
});
|
||||
return session;
|
||||
}
|
||||
|
||||
@@ -356,9 +369,9 @@ export class Client {
|
||||
parameters: Map<string, string>,
|
||||
ui: Ui,
|
||||
rest: RestClient
|
||||
): OobResult {
|
||||
// TODO: implement this
|
||||
return OobResult.cancel;
|
||||
): Promise<OobResult> {
|
||||
// TODO: implement this instead of calling `approveDuo`
|
||||
return ui.approveDuo();
|
||||
}
|
||||
|
||||
private async markDeviceAsTrusted(session: Session, clientInfo: ClientInfo, rest: RestClient) {
|
||||
@@ -539,6 +552,8 @@ export class Client {
|
||||
return "Second factor code is incorrect";
|
||||
case "multifactorresponsefailed":
|
||||
return "Out of band authentication failed";
|
||||
case "unifiedloginresult":
|
||||
return "unifiedloginresult";
|
||||
default:
|
||||
return message?.value ?? cause.value;
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
import { DuoFactor, DuoStatus } from "../enums";
|
||||
|
||||
// Adds Duo functionality to the module-specific Ui class.
|
||||
export abstract class DuoUi {
|
||||
// To cancel return null
|
||||
chooseDuoFactor: (devices: [DuoDevice]) => DuoChoice;
|
||||
// To cancel return null or blank
|
||||
provideDuoPasscode: (device: DuoDevice) => string;
|
||||
// This updates the UI with the messages from the server.
|
||||
updateDuoStatus: (status: DuoStatus, text: string) => void;
|
||||
}
|
||||
|
||||
export interface DuoChoice {
|
||||
device: DuoDevice;
|
||||
factor: DuoFactor;
|
||||
rememberMe: boolean;
|
||||
}
|
||||
|
||||
export interface DuoDevice {
|
||||
id: string;
|
||||
name: string;
|
||||
factors: DuoFactor[];
|
||||
}
|
||||
@@ -1,2 +1 @@
|
||||
export { DuoUi, DuoChoice, DuoDevice } from "./duo-ui";
|
||||
export { Ui } from "./ui";
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import { OobResult, OtpResult } from "../models";
|
||||
|
||||
import { DuoUi } from "./duo-ui";
|
||||
|
||||
export abstract class Ui extends DuoUi {
|
||||
export abstract class Ui {
|
||||
// To cancel return OtpResult.Cancel, otherwise only valid data is expected.
|
||||
provideGoogleAuthPasscode: () => Promise<OtpResult>;
|
||||
provideMicrosoftAuthPasscode: () => Promise<OtpResult>;
|
||||
@@ -26,4 +23,7 @@ export abstract class Ui extends DuoUi {
|
||||
approveLastPassAuth: () => Promise<OobResult>;
|
||||
approveDuo: () => Promise<OobResult>;
|
||||
approveSalesforceAuth: () => Promise<OobResult>;
|
||||
|
||||
/** Close MFA dialog on import success or error */
|
||||
closeMFADialog: () => void;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user