diff --git a/libs/importer/src/importers/lastpass/access/enums/duo-factor.ts b/libs/importer/src/importers/lastpass/access/enums/duo-factor.ts new file mode 100644 index 00000000000..aa65583935e --- /dev/null +++ b/libs/importer/src/importers/lastpass/access/enums/duo-factor.ts @@ -0,0 +1,6 @@ +export enum DuoFactor { + Push, + Call, + Passcode, + SendPasscodesBySms, +} diff --git a/libs/importer/src/importers/lastpass/access/enums/duo-status.ts b/libs/importer/src/importers/lastpass/access/enums/duo-status.ts new file mode 100644 index 00000000000..6397db5dc91 --- /dev/null +++ b/libs/importer/src/importers/lastpass/access/enums/duo-status.ts @@ -0,0 +1,5 @@ +export enum DuoStatus { + Success, + Error, + Info, +} diff --git a/libs/importer/src/importers/lastpass/access/enums/idp-provider.ts b/libs/importer/src/importers/lastpass/access/enums/idp-provider.ts new file mode 100644 index 00000000000..32e74c36ee1 --- /dev/null +++ b/libs/importer/src/importers/lastpass/access/enums/idp-provider.ts @@ -0,0 +1,8 @@ +export enum IdpProvider { + Azure = 0, + OktaAuthServer = 1, + OktaNoAuthServer = 2, + Google = 3, + PingOne = 4, + OneLogin = 5, +} diff --git a/libs/importer/src/importers/lastpass/access/enums/index.ts b/libs/importer/src/importers/lastpass/access/enums/index.ts new file mode 100644 index 00000000000..0059030e0aa --- /dev/null +++ b/libs/importer/src/importers/lastpass/access/enums/index.ts @@ -0,0 +1,6 @@ +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"; +export { Platform } from "./platform"; diff --git a/libs/importer/src/importers/lastpass/access/enums/lastpass-login-type.ts b/libs/importer/src/importers/lastpass/access/enums/lastpass-login-type.ts new file mode 100644 index 00000000000..611dd0b6dab --- /dev/null +++ b/libs/importer/src/importers/lastpass/access/enums/lastpass-login-type.ts @@ -0,0 +1,5 @@ +export enum LastpassLoginType { + MasterPassword = 0, + // Not sure what Types 1 and 2 are? + Federated = 3, +} diff --git a/libs/importer/src/importers/lastpass/access/otp-method.ts b/libs/importer/src/importers/lastpass/access/enums/otp-method.ts similarity index 100% rename from libs/importer/src/importers/lastpass/access/otp-method.ts rename to libs/importer/src/importers/lastpass/access/enums/otp-method.ts diff --git a/libs/importer/src/importers/lastpass/access/platform.ts b/libs/importer/src/importers/lastpass/access/enums/platform.ts similarity index 100% rename from libs/importer/src/importers/lastpass/access/platform.ts rename to libs/importer/src/importers/lastpass/access/enums/platform.ts diff --git a/libs/importer/src/importers/lastpass/access/index.ts b/libs/importer/src/importers/lastpass/access/index.ts new file mode 100644 index 00000000000..a124a44b315 --- /dev/null +++ b/libs/importer/src/importers/lastpass/access/index.ts @@ -0,0 +1 @@ +export { Vault } from "./vault"; diff --git a/libs/importer/src/importers/lastpass/access/account.ts b/libs/importer/src/importers/lastpass/access/models/account.ts similarity index 100% rename from libs/importer/src/importers/lastpass/access/account.ts rename to libs/importer/src/importers/lastpass/access/models/account.ts diff --git a/libs/importer/src/importers/lastpass/access/chunk.ts b/libs/importer/src/importers/lastpass/access/models/chunk.ts similarity index 100% rename from libs/importer/src/importers/lastpass/access/chunk.ts rename to libs/importer/src/importers/lastpass/access/models/chunk.ts diff --git a/libs/importer/src/importers/lastpass/access/client-info.ts b/libs/importer/src/importers/lastpass/access/models/client-info.ts similarity index 69% rename from libs/importer/src/importers/lastpass/access/client-info.ts rename to libs/importer/src/importers/lastpass/access/models/client-info.ts index fbe13d57d65..275cdc00d3f 100644 --- a/libs/importer/src/importers/lastpass/access/client-info.ts +++ b/libs/importer/src/importers/lastpass/access/models/client-info.ts @@ -1,4 +1,4 @@ -import { Platform } from "./platform"; +import { Platform } from "../enums"; export class ClientInfo { platform: Platform; diff --git a/libs/importer/src/importers/lastpass/access/federated-user-context.ts b/libs/importer/src/importers/lastpass/access/models/federated-user-context.ts similarity index 100% rename from libs/importer/src/importers/lastpass/access/federated-user-context.ts rename to libs/importer/src/importers/lastpass/access/models/federated-user-context.ts diff --git a/libs/importer/src/importers/lastpass/access/models/index.ts b/libs/importer/src/importers/lastpass/access/models/index.ts new file mode 100644 index 00000000000..a0c6121a354 --- /dev/null +++ b/libs/importer/src/importers/lastpass/access/models/index.ts @@ -0,0 +1,10 @@ +export { Account } from "./account"; +export { Chunk } from "./chunk"; +export { ClientInfo } from "./client-info"; +export { FederatedUserContext } from "./federated-user-context"; +export { OobResult } from "./oob-result"; +export { OtpResult } from "./otp-result"; +export { ParserOptions } from "./parser-options"; +export { Session } from "./session"; +export { SharedFolder } from "./shared-folder"; +export { UserTypeContext } from "./user-type-context"; diff --git a/libs/importer/src/importers/lastpass/access/oob-result.ts b/libs/importer/src/importers/lastpass/access/models/oob-result.ts similarity index 100% rename from libs/importer/src/importers/lastpass/access/oob-result.ts rename to libs/importer/src/importers/lastpass/access/models/oob-result.ts diff --git a/libs/importer/src/importers/lastpass/access/otp-result.ts b/libs/importer/src/importers/lastpass/access/models/otp-result.ts similarity index 100% rename from libs/importer/src/importers/lastpass/access/otp-result.ts rename to libs/importer/src/importers/lastpass/access/models/otp-result.ts diff --git a/libs/importer/src/importers/lastpass/access/parser-options.ts b/libs/importer/src/importers/lastpass/access/models/parser-options.ts similarity index 100% rename from libs/importer/src/importers/lastpass/access/parser-options.ts rename to libs/importer/src/importers/lastpass/access/models/parser-options.ts diff --git a/libs/importer/src/importers/lastpass/access/session.ts b/libs/importer/src/importers/lastpass/access/models/session.ts similarity index 78% rename from libs/importer/src/importers/lastpass/access/session.ts rename to libs/importer/src/importers/lastpass/access/models/session.ts index 4c712872632..f691968a7a7 100644 --- a/libs/importer/src/importers/lastpass/access/session.ts +++ b/libs/importer/src/importers/lastpass/access/models/session.ts @@ -1,4 +1,4 @@ -import { Platform } from "./platform"; +import { Platform } from "../enums"; export class Session { id: string; diff --git a/libs/importer/src/importers/lastpass/access/shared-folder.ts b/libs/importer/src/importers/lastpass/access/models/shared-folder.ts similarity index 100% rename from libs/importer/src/importers/lastpass/access/shared-folder.ts rename to libs/importer/src/importers/lastpass/access/models/shared-folder.ts diff --git a/libs/importer/src/importers/lastpass/access/user-type-context.ts b/libs/importer/src/importers/lastpass/access/models/user-type-context.ts similarity index 63% rename from libs/importer/src/importers/lastpass/access/user-type-context.ts rename to libs/importer/src/importers/lastpass/access/models/user-type-context.ts index f2629d59516..9d849281c2d 100644 --- a/libs/importer/src/importers/lastpass/access/user-type-context.ts +++ b/libs/importer/src/importers/lastpass/access/models/user-type-context.ts @@ -1,17 +1,19 @@ +import { IdpProvider, LastpassLoginType } from "../enums"; + export class UserTypeContext { - type: Type; + type: LastpassLoginType; IdentityProviderGUID: string; IdentityProviderURL: string; OpenIDConnectAuthority: string; OpenIDConnectClientId: string; CompanyId: number; - Provider: Provider; + Provider: IdpProvider; PkceEnabled: boolean; IsPasswordlessEnabled: boolean; isFederated(): boolean { return ( - this.type === Type.Federated && + this.type === LastpassLoginType.Federated && this.hasValue(this.IdentityProviderURL) && this.hasValue(this.OpenIDConnectAuthority) && this.hasValue(this.OpenIDConnectClientId) @@ -22,18 +24,3 @@ export class UserTypeContext { return str != null && str.trim() !== ""; } } - -export enum Provider { - Azure = 0, - OktaAuthServer = 1, - OktaNoAuthServer = 2, - Google = 3, - PingOne = 4, - OneLogin = 5, -} - -export enum Type { - MasterPassword = 0, - // Not sure what Types 1 and 2 are? - Federated = 3, -} diff --git a/libs/importer/src/importers/lastpass/access/binary-reader.ts b/libs/importer/src/importers/lastpass/access/services/binary-reader.ts similarity index 91% rename from libs/importer/src/importers/lastpass/access/binary-reader.ts rename to libs/importer/src/importers/lastpass/access/services/binary-reader.ts index 706afbd9e9b..e7a434e957f 100644 --- a/libs/importer/src/importers/lastpass/access/binary-reader.ts +++ b/libs/importer/src/importers/lastpass/access/services/binary-reader.ts @@ -12,7 +12,7 @@ export class BinaryReader { readBytes(count: number): Uint8Array { if (this.position + count > this.arr.length) { - throw "End of array reached"; + throw new Error("End of array reached"); } const slice = this.arr.subarray(this.position, this.position + count); this.position += count; @@ -62,10 +62,10 @@ export class BinaryReader { seekFromCurrentPosition(offset: number) { const newPosition = this.position + offset; if (newPosition < 0) { - throw "Position cannot be negative"; + throw new Error("Position cannot be negative"); } if (newPosition > this.arr.length) { - throw "Array not large enough to seek to this position"; + throw new Error("Array not large enough to seek to this position"); } this.position = newPosition; } diff --git a/libs/importer/src/importers/lastpass/access/client.ts b/libs/importer/src/importers/lastpass/access/services/client.ts similarity index 94% rename from libs/importer/src/importers/lastpass/access/client.ts rename to libs/importer/src/importers/lastpass/access/services/client.ts index 0a3c8fefe52..2d8b503f01d 100644 --- a/libs/importer/src/importers/lastpass/access/client.ts +++ b/libs/importer/src/importers/lastpass/access/services/client.ts @@ -1,21 +1,23 @@ import { HttpStatusCode } from "@bitwarden/common/enums"; import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { Account } from "./account"; +import { OtpMethod, Platform } from "../enums"; +import { + Account, + Chunk, + ClientInfo, + OobResult, + OtpResult, + ParserOptions, + Session, + SharedFolder, +} from "../models"; +import { Ui } from "../ui"; + import { BinaryReader } from "./binary-reader"; -import { Chunk } from "./chunk"; -import { ClientInfo } from "./client-info"; import { CryptoUtils } from "./crypto-utils"; -import { OobResult } from "./oob-result"; -import { OtpMethod } from "./otp-method"; -import { OtpResult } from "./otp-result"; import { Parser } from "./parser"; -import { ParserOptions } from "./parser-options"; -import { Platform } from "./platform"; import { RestClient } from "./rest-client"; -import { Session } from "./session"; -import { SharedFolder } from "./shared-folder"; -import { Ui } from "./ui"; const PlatformToUserAgent = new Map([ [Platform.Desktop, "cli"], @@ -68,7 +70,7 @@ export class Client { const reader = new BinaryReader(blob); const chunks = this.parser.extractChunks(reader); if (!this.isComplete(chunks)) { - throw "Blob is truncated or corrupted"; + throw new Error("Blob is truncated or corrupted"); } return await this.parseAccounts(chunks, encryptionKey, privateKey, options); } @@ -236,11 +238,11 @@ export class Client { passcode = ui.provideYubikeyPasscode(); break; default: - throw "Invalid OTP method"; + throw new Error("Invalid OTP method"); } if (passcode == OtpResult.cancel) { - throw "Second factor step is canceled by the user"; + throw new Error("Second factor step is canceled by the user"); } const response = await this.performSingleLoginRequest( @@ -273,7 +275,7 @@ export class Client { ): Promise { const answer = this.approveOob(username, parameters, ui, rest); if (answer == OobResult.cancel) { - throw "Out of band step is canceled by the user"; + throw new Error("Out of band step is canceled by the user"); } const extraParameters = new Map(); @@ -319,7 +321,7 @@ export class Client { private approveOob(username: string, parameters: Map, ui: Ui, rest: RestClient) { const method = parameters.get("outofbandtype"); if (method == null) { - throw "Out of band method is not specified"; + throw new Error("Out of band method is not specified"); } switch (method) { case "lastpassauth": @@ -329,7 +331,7 @@ export class Client { case "salesforcehash": return ui.approveSalesforceAuth(); default: - throw "Out of band method " + method + " is not supported"; + throw new Error("Out of band method " + method + " is not supported"); } } @@ -410,7 +412,7 @@ export class Client { if (attr != null) { return attr; } - throw "Unknown response schema: attribute " + name + " is missing"; + throw new Error("Unknown response schema: attribute " + name + " is missing"); } private getOptionalErrorAttribute(response: Document, name: string): string { @@ -505,7 +507,9 @@ export class Client { private makeError(response: Response) { // TODO: error parsing - throw "HTTP request to " + response.url + " failed with status " + response.status + "."; + throw new Error( + "HTTP request to " + response.url + " failed with status " + response.status + "." + ); } private makeLoginError(response: Document): string { diff --git a/libs/importer/src/importers/lastpass/access/crypto-utils.ts b/libs/importer/src/importers/lastpass/access/services/crypto-utils.ts similarity index 96% rename from libs/importer/src/importers/lastpass/access/crypto-utils.ts rename to libs/importer/src/importers/lastpass/access/services/crypto-utils.ts index c8d9f8a168b..4de046f2aa3 100644 --- a/libs/importer/src/importers/lastpass/access/crypto-utils.ts +++ b/libs/importer/src/importers/lastpass/access/services/crypto-utils.ts @@ -6,7 +6,7 @@ export class CryptoUtils { async deriveKey(username: string, password: string, iterationCount: number) { if (iterationCount < 0) { - throw "Iteration count should be positive"; + throw new Error("Iteration count should be positive"); } if (iterationCount == 1) { return await this.cryptoFunctionService.hash(username + password, "sha256"); @@ -27,7 +27,7 @@ export class CryptoUtils { ExclusiveOr(arr1: Uint8Array, arr2: Uint8Array) { if (arr1.length !== arr2.length) { - throw "Arrays must be the same length."; + throw new Error("Arrays must be the same length."); } const result = new Uint8Array(arr1.length); for (let i = 0; i < arr1.length; i++) { diff --git a/libs/importer/src/importers/lastpass/access/services/index.ts b/libs/importer/src/importers/lastpass/access/services/index.ts new file mode 100644 index 00000000000..2610efdb694 --- /dev/null +++ b/libs/importer/src/importers/lastpass/access/services/index.ts @@ -0,0 +1,5 @@ +export { BinaryReader } from "./binary-reader"; +export { Client } from "./client"; +export { CryptoUtils } from "./crypto-utils"; +export { Parser } from "./parser"; +export { RestClient } from "./rest-client"; diff --git a/libs/importer/src/importers/lastpass/access/parser.ts b/libs/importer/src/importers/lastpass/access/services/parser.ts similarity index 97% rename from libs/importer/src/importers/lastpass/access/parser.ts rename to libs/importer/src/importers/lastpass/access/services/parser.ts index fc4b3b4a49a..3d64490be12 100644 --- a/libs/importer/src/importers/lastpass/access/parser.ts +++ b/libs/importer/src/importers/lastpass/access/services/parser.ts @@ -1,12 +1,10 @@ import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { Account } from "./account"; +import { Account, Chunk, ParserOptions, SharedFolder } from "../models"; + import { BinaryReader } from "./binary-reader"; -import { Chunk } from "./chunk"; import { CryptoUtils } from "./crypto-utils"; -import { ParserOptions } from "./parser-options"; -import { SharedFolder } from "./shared-folder"; const AllowedSecureNoteTypes = new Set([ "Server", @@ -285,7 +283,7 @@ export class Parser { const header = "LastPassPrivateKey<"; const footer = ">LastPassPrivateKey"; if (!decrypted.startsWith(header) || !decrypted.endsWith(footer)) { - throw "Failed to decrypt private key"; + throw new Error("Failed to decrypt private key"); } const parsedKey = decrypted.substring(header.length, decrypted.length - footer.length); diff --git a/libs/importer/src/importers/lastpass/access/rest-client.ts b/libs/importer/src/importers/lastpass/access/services/rest-client.ts similarity index 100% rename from libs/importer/src/importers/lastpass/access/rest-client.ts rename to libs/importer/src/importers/lastpass/access/services/rest-client.ts diff --git a/libs/importer/src/importers/lastpass/access/duo-ui.ts b/libs/importer/src/importers/lastpass/access/ui/duo-ui.ts similarity index 81% rename from libs/importer/src/importers/lastpass/access/duo-ui.ts rename to libs/importer/src/importers/lastpass/access/ui/duo-ui.ts index 61b52d2582a..60afd0ad9df 100644 --- a/libs/importer/src/importers/lastpass/access/duo-ui.ts +++ b/libs/importer/src/importers/lastpass/access/ui/duo-ui.ts @@ -1,3 +1,5 @@ +import { DuoFactor, DuoStatus } from "../enums"; + // Adds Duo functionality to the module-specific Ui class. export abstract class DuoUi { // To cancel return null @@ -8,19 +10,6 @@ export abstract class DuoUi { updateDuoStatus: (status: DuoStatus, text: string) => void; } -export enum DuoFactor { - Push, - Call, - Passcode, - SendPasscodesBySms, -} - -export enum DuoStatus { - Success, - Error, - Info, -} - export interface DuoChoice { device: DuoDevice; factor: DuoFactor; diff --git a/libs/importer/src/importers/lastpass/access/ui/index.ts b/libs/importer/src/importers/lastpass/access/ui/index.ts new file mode 100644 index 00000000000..e4edc3b6b48 --- /dev/null +++ b/libs/importer/src/importers/lastpass/access/ui/index.ts @@ -0,0 +1,2 @@ +export { DuoUi, DuoChoice, DuoDevice } from "./duo-ui"; +export { Ui } from "./ui"; diff --git a/libs/importer/src/importers/lastpass/access/ui.ts b/libs/importer/src/importers/lastpass/access/ui/ui.ts similarity index 93% rename from libs/importer/src/importers/lastpass/access/ui.ts rename to libs/importer/src/importers/lastpass/access/ui/ui.ts index fad86596187..2338e8a291e 100644 --- a/libs/importer/src/importers/lastpass/access/ui.ts +++ b/libs/importer/src/importers/lastpass/access/ui/ui.ts @@ -1,6 +1,6 @@ +import { OobResult, OtpResult } from "../models"; + import { DuoUi } from "./duo-ui"; -import { OobResult } from "./oob-result"; -import { OtpResult } from "./otp-result"; export abstract class Ui extends DuoUi { // To cancel return OtpResult.Cancel, otherwise only valid data is expected. diff --git a/libs/importer/src/importers/lastpass/access/vault.ts b/libs/importer/src/importers/lastpass/access/vault.ts index 157965804c2..a461239eea8 100644 --- a/libs/importer/src/importers/lastpass/access/vault.ts +++ b/libs/importer/src/importers/lastpass/access/vault.ts @@ -3,16 +3,16 @@ import { HttpStatusCode } from "@bitwarden/common/enums"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { Account } from "./account"; -import { Client } from "./client"; -import { ClientInfo } from "./client-info"; -import { CryptoUtils } from "./crypto-utils"; -import { FederatedUserContext } from "./federated-user-context"; -import { Parser } from "./parser"; -import { ParserOptions } from "./parser-options"; -import { RestClient } from "./rest-client"; +import { IdpProvider } from "./enums"; +import { + Account, + ClientInfo, + FederatedUserContext, + ParserOptions, + UserTypeContext, +} from "./models"; +import { Client, CryptoUtils, Parser, RestClient } from "./services"; import { Ui } from "./ui"; -import { Provider, UserTypeContext } from "./user-type-context"; export class Vault { accounts: Account[]; @@ -47,7 +47,7 @@ export class Vault { parserOptions: ParserOptions = ParserOptions.default ): Promise { if (federatedUser == null) { - throw "Federated user context is not set."; + throw new Error("Federated user context is not set."); } const k1 = await this.getK1(federatedUser); const k2 = await this.getK2(federatedUser); @@ -77,32 +77,33 @@ export class Vault { this.userType.PkceEnabled = json.PkceEnabled; this.userType.Provider = json.Provider; this.userType.type = json.type; + return; } - throw "Cannot determine LastPass user type."; + throw new Error("Cannot determine LastPass user type."); } private async getK1(federatedUser: FederatedUserContext): Promise { if (this.userType == null) { - throw "User type is not set."; + throw new Error("User type is not set."); } if (!this.userType.isFederated()) { - throw "Cannot get k1 for LastPass user that is not federated."; + throw new Error("Cannot get k1 for LastPass user that is not federated."); } if (federatedUser == null) { - throw "Federated user is not set."; + throw new Error("Federated user is not set."); } let k1: Uint8Array = null; if (federatedUser.idpUserInfo?.LastPassK1 !== null) { return Utils.fromByteStringToArray(federatedUser.idpUserInfo.LastPassK1); - } else if (this.userType.Provider === Provider.Azure) { + } else if (this.userType.Provider === IdpProvider.Azure) { k1 = await this.getK1Azure(federatedUser); - } else if (this.userType.Provider === Provider.Google) { + } else if (this.userType.Provider === IdpProvider.Google) { k1 = await this.getK1Google(federatedUser); } else { - const b64Encoded = this.userType.Provider === Provider.PingOne; + const b64Encoded = this.userType.Provider === IdpProvider.PingOne; k1 = this.getK1FromAccessToken(federatedUser, b64Encoded); } @@ -110,7 +111,7 @@ export class Vault { return k1; } - throw "Cannot get k1."; + throw new Error("Cannot get k1."); } private async getK1Azure(federatedUser: FederatedUserContext) { @@ -175,11 +176,11 @@ export class Vault { private async getK2(federatedUser: FederatedUserContext): Promise { if (this.userType == null) { - throw "User type is not set."; + throw new Error("User type is not set."); } if (!this.userType.isFederated()) { - throw "Cannot get k2 for LastPass user that is not federated."; + throw new Error("Cannot get k2 for LastPass user that is not federated."); } const rest = new RestClient(); @@ -195,6 +196,6 @@ export class Vault { return Utils.fromB64ToArray(k2); } } - throw "Cannot get k2."; + throw new Error("Cannot get k2."); } }