mirror of
https://github.com/bitwarden/browser.git
synced 2024-11-25 12:15:18 +01:00
[PM-4419] Add lastpass direct importer to browser (#6638)
* 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 * add browser lastpass oidc/sso connector * add getRedirectUrlWithParams * params * rename to getOidcRedirectUrlWithParams * refactor oob login flow * Add messages needed for Lastpass import flow Taken from https://github.com/bitwarden/clients/pull/6541/files#diff-47e9af6d0d7d691a507534f7955edaa9fb37be8cf1c1981fd2ba898e99b6130d * Update apps/browser/src/connectors/sso.ts Co-authored-by: Cesar Gonzalez <cesar.a.gonzalezcs@gmail.com> * Update libs/importer/src/components/lastpass/import-lastpass.component.ts Co-authored-by: Cesar Gonzalez <cesar.a.gonzalezcs@gmail.com> * fix error * Removing fieldset due to merge of https://github.com/bitwarden/clients/pull/6626 * Add sso-connector to manifest.v3 * Make linter happy * 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 * Add missing messages forLastpass import * extract helper asyncValidatorsFinished * Remove files related to DuoUI we didn't need to differentiate for MFA via Duo * Add missing import * use clientType * triple = * lastpassAuthResult for web sso connector * remove browser sso connector * use web vault for oidc redirect url * revert formGroup.value access * process lastpassAuthResult * simplify message handler logic * consolidate logic for lastpass auth result * swap lastpass logic in sso connector * 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 * show LP importer on browser client * add missing i18n to 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 * Forward LP sso callback message to LP direct import service * Add missing collection label * Add missing `invalidFilePassword` to messages.json --------- 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: William Martin <contact@willmartian.com> Co-authored-by: Cesar Gonzalez <cesar.a.gonzalezcs@gmail.com>
This commit is contained in:
parent
bf1016f1fd
commit
afc9128653
@ -2501,6 +2501,9 @@
|
|||||||
"importEncKeyError": {
|
"importEncKeyError": {
|
||||||
"message": "Error decrypting the exported file. Your encryption key does not match the encryption key used export the data."
|
"message": "Error decrypting the exported file. Your encryption key does not match the encryption key used export the data."
|
||||||
},
|
},
|
||||||
|
"invalidFilePassword": {
|
||||||
|
"message": "Invalid file password, please use the password you entered when you created the export file."
|
||||||
|
},
|
||||||
"importDestination": {
|
"importDestination": {
|
||||||
"message": "Import destination"
|
"message": "Import destination"
|
||||||
},
|
},
|
||||||
@ -2611,8 +2614,65 @@
|
|||||||
"useBrowserName": {
|
"useBrowserName": {
|
||||||
"message": "Use browser"
|
"message": "Use browser"
|
||||||
},
|
},
|
||||||
|
"multifactorAuthenticationCancelled": {
|
||||||
|
"message": "Multifactor authentication cancelled"
|
||||||
|
},
|
||||||
|
"noLastPassDataFound": {
|
||||||
|
"message": "No LastPass data found"
|
||||||
|
},
|
||||||
|
"incorrectUsernameOrPassword": {
|
||||||
|
"message": "Incorrect username or password"
|
||||||
|
},
|
||||||
|
"multifactorAuthenticationFailed": {
|
||||||
|
"message": "Multifactor authentication failed"
|
||||||
|
},
|
||||||
|
"includeSharedFolders": {
|
||||||
|
"message": "Include shared folders"
|
||||||
|
},
|
||||||
|
"lastPassEmail": {
|
||||||
|
"message": "LastPass Email"
|
||||||
|
},
|
||||||
|
"importingYourAccount": {
|
||||||
|
"message": "Importing your account..."
|
||||||
|
},
|
||||||
|
"lastPassMFARequired": {
|
||||||
|
"message": "LastPass multifactor authentication required"
|
||||||
|
},
|
||||||
|
"lastPassMFADesc": {
|
||||||
|
"message": "Enter your one-time passcode from your authentication app"
|
||||||
|
},
|
||||||
|
"lastPassOOBDesc": {
|
||||||
|
"message": "Approve the login request in your authentication app or enter a one-time passcode."
|
||||||
|
},
|
||||||
|
"passcode": {
|
||||||
|
"message": "Passcode"
|
||||||
|
},
|
||||||
|
"lastPassMasterPassword": {
|
||||||
|
"message": "LastPass master password"
|
||||||
|
},
|
||||||
|
"lastPassAuthRequired": {
|
||||||
|
"message": "LastPass authentication required"
|
||||||
|
},
|
||||||
|
"awaitingSSO": {
|
||||||
|
"message": "Awaiting SSO authentication"
|
||||||
|
},
|
||||||
|
"awaitingSSODesc": {
|
||||||
|
"message": "Please continue to log in using your company credentials."
|
||||||
|
},
|
||||||
"seeDetailedInstructions": {
|
"seeDetailedInstructions": {
|
||||||
"message": "See detailed instructions on our help site at",
|
"message": "See detailed instructions on our help site at",
|
||||||
"description": "This is followed a by a hyperlink to the help website."
|
"description": "This is followed a by a hyperlink to the help website."
|
||||||
|
},
|
||||||
|
"importDirectlyFromLastPass": {
|
||||||
|
"message": "Import directly from LastPass"
|
||||||
|
},
|
||||||
|
"importFromCSV": {
|
||||||
|
"message": "Import from CSV"
|
||||||
|
},
|
||||||
|
"lastPassTryAgainCheckEmail": {
|
||||||
|
"message": "Try again or look for an email from LastPass to verify it's you."
|
||||||
|
},
|
||||||
|
"collection": {
|
||||||
|
"message": "Collection"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ window.addEventListener(
|
|||||||
command: event.data.command,
|
command: event.data.command,
|
||||||
code: event.data.code,
|
code: event.data.code,
|
||||||
state: event.data.state,
|
state: event.data.state,
|
||||||
|
lastpass: event.data.lastpass,
|
||||||
referrer: event.source.location.hostname,
|
referrer: event.source.location.hostname,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -259,15 +259,22 @@ export default class RuntimeBackground {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
if (msg.lastpass) {
|
||||||
BrowserApi.createNewTab(
|
this.messagingService.send("importCallbackLastPass", {
|
||||||
"popup/index.html?uilocation=popout#/sso?code=" +
|
code: msg.code,
|
||||||
encodeURIComponent(msg.code) +
|
state: msg.state,
|
||||||
"&state=" +
|
});
|
||||||
encodeURIComponent(msg.state)
|
} else {
|
||||||
);
|
try {
|
||||||
} catch {
|
BrowserApi.createNewTab(
|
||||||
this.logService.error("Unable to open sso popout tab");
|
"popup/index.html?uilocation=popout#/sso?code=" +
|
||||||
|
encodeURIComponent(msg.code) +
|
||||||
|
"&state=" +
|
||||||
|
encodeURIComponent(msg.state)
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
this.logService.error("Unable to open sso popout tab");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -5,9 +5,12 @@ require("./sso.scss");
|
|||||||
document.addEventListener("DOMContentLoaded", () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
const code = getQsParam("code");
|
const code = getQsParam("code");
|
||||||
const state = getQsParam("state");
|
const state = getQsParam("state");
|
||||||
|
const lastpass = getQsParam("lp");
|
||||||
|
|
||||||
if (state != null && state.includes(":clientId=browser")) {
|
if (lastpass === "1") {
|
||||||
initiateBrowserSso(code, state);
|
initiateBrowserSso(code, state, true);
|
||||||
|
} else if (state != null && state.includes(":clientId=browser")) {
|
||||||
|
initiateBrowserSso(code, state, false);
|
||||||
} else {
|
} else {
|
||||||
window.location.href = window.location.origin + "/#/sso?code=" + code + "&state=" + state;
|
window.location.href = window.location.origin + "/#/sso?code=" + code + "&state=" + state;
|
||||||
// Match any characters between "_returnUri='" and the next "'"
|
// Match any characters between "_returnUri='" and the next "'"
|
||||||
@ -20,8 +23,8 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function initiateBrowserSso(code: string, state: string) {
|
function initiateBrowserSso(code: string, state: string, lastpass: boolean) {
|
||||||
window.postMessage({ command: "authResult", code: code, state: state }, "*");
|
window.postMessage({ command: "authResult", code: code, state: state, lastpass: lastpass }, "*");
|
||||||
const handOffMessage = ("; " + document.cookie)
|
const handOffMessage = ("; " + document.cookie)
|
||||||
.split("; ssoHandOffMessage=")
|
.split("; ssoHandOffMessage=")
|
||||||
.pop()
|
.pop()
|
||||||
|
@ -188,7 +188,8 @@ export class ImportComponent implements OnInit, OnDestroy {
|
|||||||
protected get showLastPassToggle(): boolean {
|
protected get showLastPassToggle(): boolean {
|
||||||
return (
|
return (
|
||||||
this.format === "lastpasscsv" &&
|
this.format === "lastpasscsv" &&
|
||||||
this.platformUtilsService.getClientType() === ClientType.Desktop
|
(this.platformUtilsService.getClientType() === ClientType.Desktop ||
|
||||||
|
this.platformUtilsService.getClientType() === ClientType.Browser)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
protected get showLastPassOptions(): boolean {
|
protected get showLastPassOptions(): boolean {
|
||||||
|
@ -3,9 +3,11 @@ import { OidcClient } from "oidc-client-ts";
|
|||||||
import { Subject, firstValueFrom } from "rxjs";
|
import { Subject, firstValueFrom } from "rxjs";
|
||||||
|
|
||||||
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
||||||
|
import { ClientType } from "@bitwarden/common/enums";
|
||||||
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
|
||||||
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
|
||||||
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
||||||
|
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
|
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
|
||||||
@ -32,13 +34,14 @@ export class LastPassDirectImportService {
|
|||||||
constructor(
|
constructor(
|
||||||
private tokenService: TokenService,
|
private tokenService: TokenService,
|
||||||
private cryptoFunctionService: CryptoFunctionService,
|
private cryptoFunctionService: CryptoFunctionService,
|
||||||
|
private environmentService: EnvironmentService,
|
||||||
private appIdService: AppIdService,
|
private appIdService: AppIdService,
|
||||||
private lastPassDirectImportUIService: LastPassDirectImportUIService,
|
private lastPassDirectImportUIService: LastPassDirectImportUIService,
|
||||||
|
private platformUtilsService: PlatformUtilsService,
|
||||||
private passwordGenerationService: PasswordGenerationServiceAbstraction,
|
private passwordGenerationService: PasswordGenerationServiceAbstraction,
|
||||||
private broadcasterService: BroadcasterService,
|
private broadcasterService: BroadcasterService,
|
||||||
private ngZone: NgZone,
|
private ngZone: NgZone,
|
||||||
private dialogService: DialogService,
|
private dialogService: DialogService
|
||||||
private platformUtilsService: PlatformUtilsService
|
|
||||||
) {
|
) {
|
||||||
this.vault = new Vault(this.cryptoFunctionService, this.tokenService);
|
this.vault = new Vault(this.cryptoFunctionService, this.tokenService);
|
||||||
|
|
||||||
@ -110,8 +113,7 @@ export class LastPassDirectImportService {
|
|||||||
this.oidcClient = new OidcClient({
|
this.oidcClient = new OidcClient({
|
||||||
authority: this.vault.userType.openIDConnectAuthorityBase,
|
authority: this.vault.userType.openIDConnectAuthorityBase,
|
||||||
client_id: this.vault.userType.openIDConnectClientId,
|
client_id: this.vault.userType.openIDConnectClientId,
|
||||||
// TODO: this is different per client
|
redirect_uri: this.getOidcRedirectUrl(),
|
||||||
redirect_uri: "bitwarden://import-callback-lp",
|
|
||||||
response_type: "code",
|
response_type: "code",
|
||||||
scope: this.vault.userType.oidcScope,
|
scope: this.vault.userType.oidcScope,
|
||||||
response_mode: "query",
|
response_mode: "query",
|
||||||
@ -131,6 +133,25 @@ export class LastPassDirectImportService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getOidcRedirectUrlWithParams(oidcCode: string, oidcState: string) {
|
||||||
|
const redirectUri = this.oidcClient.settings.redirect_uri;
|
||||||
|
const params = "code=" + oidcCode + "&state=" + oidcState;
|
||||||
|
if (redirectUri.indexOf("bitwarden://") === 0) {
|
||||||
|
return redirectUri + "/?" + params;
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirectUri + "&" + params;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getOidcRedirectUrl() {
|
||||||
|
const clientType = this.platformUtilsService.getClientType();
|
||||||
|
if (clientType === ClientType.Desktop) {
|
||||||
|
return "bitwarden://import-callback-lp";
|
||||||
|
}
|
||||||
|
const webUrl = this.environmentService.getWebVaultUrl();
|
||||||
|
return webUrl + "/sso-connector.html?lp=1";
|
||||||
|
}
|
||||||
|
|
||||||
private async handleStandardImport(
|
private async handleStandardImport(
|
||||||
email: string,
|
email: string,
|
||||||
password: string,
|
password: string,
|
||||||
@ -150,7 +171,7 @@ export class LastPassDirectImportService {
|
|||||||
includeSharedFolders: boolean
|
includeSharedFolders: boolean
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const response = await this.oidcClient.processSigninResponse(
|
const response = await this.oidcClient.processSigninResponse(
|
||||||
this.oidcClient.settings.redirect_uri + "/?code=" + oidcCode + "&state=" + oidcState
|
this.getOidcRedirectUrlWithParams(oidcCode, oidcState)
|
||||||
);
|
);
|
||||||
const userState = response.userState as any;
|
const userState = response.userState as any;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user