mirror of
https://github.com/bitwarden/browser.git
synced 2024-11-21 11:35:34 +01:00
[PM-4195] Lastpass access lib refactors (#6566)
* Fix setUserTypeContext by adding missing return * Throw new Error instead of just string * Move enums and models into separate folders * Move UI classes into separate folder * Move FederatedUserContext import * Move services into a separate folder * Add barrel file for lastpass access lib * Fix build by updating imports after move --------- Co-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com>
This commit is contained in:
parent
b592b71df1
commit
67fa1e06d0
@ -0,0 +1,6 @@
|
||||
export enum DuoFactor {
|
||||
Push,
|
||||
Call,
|
||||
Passcode,
|
||||
SendPasscodesBySms,
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
export enum DuoStatus {
|
||||
Success,
|
||||
Error,
|
||||
Info,
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
export enum IdpProvider {
|
||||
Azure = 0,
|
||||
OktaAuthServer = 1,
|
||||
OktaNoAuthServer = 2,
|
||||
Google = 3,
|
||||
PingOne = 4,
|
||||
OneLogin = 5,
|
||||
}
|
@ -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";
|
@ -0,0 +1,5 @@
|
||||
export enum LastpassLoginType {
|
||||
MasterPassword = 0,
|
||||
// Not sure what Types 1 and 2 are?
|
||||
Federated = 3,
|
||||
}
|
1
libs/importer/src/importers/lastpass/access/index.ts
Normal file
1
libs/importer/src/importers/lastpass/access/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { Vault } from "./vault";
|
@ -1,4 +1,4 @@
|
||||
import { Platform } from "./platform";
|
||||
import { Platform } from "../enums";
|
||||
|
||||
export class ClientInfo {
|
||||
platform: Platform;
|
10
libs/importer/src/importers/lastpass/access/models/index.ts
Normal file
10
libs/importer/src/importers/lastpass/access/models/index.ts
Normal file
@ -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";
|
@ -1,4 +1,4 @@
|
||||
import { Platform } from "./platform";
|
||||
import { Platform } from "../enums";
|
||||
|
||||
export class Session {
|
||||
id: string;
|
@ -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,
|
||||
}
|
@ -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;
|
||||
}
|
@ -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, string>([
|
||||
[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<Session> {
|
||||
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<string, any>();
|
||||
@ -319,7 +321,7 @@ export class Client {
|
||||
private approveOob(username: string, parameters: Map<string, string>, 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 {
|
@ -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++) {
|
@ -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";
|
@ -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<string>([
|
||||
"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);
|
@ -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;
|
2
libs/importer/src/importers/lastpass/access/ui/index.ts
Normal file
2
libs/importer/src/importers/lastpass/access/ui/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export { DuoUi, DuoChoice, DuoDevice } from "./duo-ui";
|
||||
export { Ui } from "./ui";
|
@ -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.
|
@ -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<void> {
|
||||
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<Uint8Array> {
|
||||
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<Uint8Array> {
|
||||
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.");
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user