1
0
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:
Will Martin
2023-10-23 13:46:49 -04:00
committed by GitHub
parent a4303fac59
commit ec866c744e
35 changed files with 800 additions and 118 deletions

View File

@@ -1,6 +0,0 @@
export enum DuoFactor {
Push,
Call,
Passcode,
SendPasscodesBySms,
}

View File

@@ -1,5 +0,0 @@
export enum DuoStatus {
Success,
Error,
Info,
}

View File

@@ -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";

View File

@@ -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" };
}
}

View File

@@ -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;
}

View File

@@ -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[];
}

View File

@@ -1,2 +1 @@
export { DuoUi, DuoChoice, DuoDevice } from "./duo-ui";
export { Ui } from "./ui";

View File

@@ -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;
}