1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-17 16:53:34 +00:00

[PM-5404, PM-3518] Migrate user decryption options to new service (#7344)

* create new user decryption options service

* rename new service to user decryption options

* add hasMasterPassword to user decryption options service

* migrate device trust service to new user decryption options service

* add migration for user-decryption-options

* migrate sync service and calls to trust-device-service

* rename abstraction file

* migrate two factor component

* migrate two factor spec

* migrate sso component

* migrate set-password component

* migrate base login decryption component

* migrate organization options component

* fix component imports

* add missing imports
- remove state service calls
- add update user decryption options method

* remove acct decryption options from account

* lint

* fix tests and linting

* fix browser

* fix desktop

* add user decryption options service to cli

* remove default value from migration

* bump migration number

* fix merge conflict

* fix vault timeout settings

* fix cli

* more fixes

* add user decryption options service to deps of vault timeout settings service

* update login strategy service with user decryption options

* remove early return from sync bandaid for user decryption options

* move user decryption options service to lib/auth

* move user decryption options to libs/auth

* fix reference

* fix browser

* check user decryption options after 2fa check

* update migration and revert tsconfig changes

* add more documentation

* clear user decryption options on logout

* fix tests by creating helper for user decryption options

* fix tests

* pr feedback

* fix factory

* update migration

* add tests

* update missed migration num in test
This commit is contained in:
Jake Fink
2024-03-20 20:33:57 -04:00
committed by GitHub
parent e2fe1e1567
commit 2111b37c32
68 changed files with 1158 additions and 360 deletions

View File

@@ -1,2 +1,3 @@
export * from "./rotateable-key-set";
export * from "./login-credentials";
export * from "./user-decryption-options";

View File

@@ -0,0 +1,153 @@
import { Jsonify } from "type-fest";
import { KeyConnectorUserDecryptionOptionResponse } from "@bitwarden/common/auth/models/response/user-decryption-options/key-connector-user-decryption-option.response";
import { TrustedDeviceUserDecryptionOptionResponse } from "@bitwarden/common/auth/models/response/user-decryption-options/trusted-device-user-decryption-option.response";
import { IdentityTokenResponse } from "@bitwarden/common/src/auth/models/response/identity-token.response";
/**
* Key Connector decryption options. Intended to be sent to the client for use after authentication.
* @see {@link UserDecryptionOptions}
*/
export class KeyConnectorUserDecryptionOption {
/** The URL of the key connector configured for this user. */
keyConnectorUrl: string;
/**
* Initializes a new instance of the KeyConnectorUserDecryptionOption from a response object.
* @param response The key connector user decryption option response object.
* @returns A new instance of the KeyConnectorUserDecryptionOption. Will initialize even if the response is nullish.
*/
static fromResponse(
response: KeyConnectorUserDecryptionOptionResponse,
): KeyConnectorUserDecryptionOption {
const options = new KeyConnectorUserDecryptionOption();
options.keyConnectorUrl = response?.keyConnectorUrl ?? null;
return options;
}
/**
* Initializes a new instance of a KeyConnectorUserDecryptionOption from a JSON object.
* @param obj JSON object to deserialize.
* @returns A new instance of the KeyConnectorUserDecryptionOption. Will initialize even if the JSON object is nullish.
*/
static fromJSON(
obj: Jsonify<KeyConnectorUserDecryptionOption>,
): KeyConnectorUserDecryptionOption {
return Object.assign(new KeyConnectorUserDecryptionOption(), obj);
}
}
/**
* Trusted device decryption options. Intended to be sent to the client for use after authentication.
* @see {@link UserDecryptionOptions}
*/
export class TrustedDeviceUserDecryptionOption {
/** True if an admin has approved an admin auth request previously made from this device. */
hasAdminApproval: boolean;
/** True if the user has a device capable of approving an auth request. */
hasLoginApprovingDevice: boolean;
/** True if the user has manage reset password permission, as these users must be forced to have a master password. */
hasManageResetPasswordPermission: boolean;
/**
* Initializes a new instance of the TrustedDeviceUserDecryptionOption from a response object.
* @param response The trusted device user decryption option response object.
* @returns A new instance of the TrustedDeviceUserDecryptionOption. Will initialize even if the response is nullish.
*/
static fromResponse(
response: TrustedDeviceUserDecryptionOptionResponse,
): TrustedDeviceUserDecryptionOption {
const options = new TrustedDeviceUserDecryptionOption();
options.hasAdminApproval = response?.hasAdminApproval ?? false;
options.hasLoginApprovingDevice = response?.hasLoginApprovingDevice ?? false;
options.hasManageResetPasswordPermission = response?.hasManageResetPasswordPermission ?? false;
return options;
}
/**
* Initializes a new instance of the TrustedDeviceUserDecryptionOption from a JSON object.
* @param obj JSON object to deserialize.
* @returns A new instance of the TrustedDeviceUserDecryptionOption. Will initialize even if the JSON object is nullish.
*/
static fromJSON(
obj: Jsonify<TrustedDeviceUserDecryptionOption>,
): TrustedDeviceUserDecryptionOption {
return Object.assign(new TrustedDeviceUserDecryptionOption(), obj);
}
}
/**
* Represents the decryption options the user has configured on the server. This is intended to be sent
* to the client on authentication, and can be used to determine how to decrypt the user's vault.
*/
export class UserDecryptionOptions {
/** True if the user has a master password configured on the server. */
hasMasterPassword: boolean;
/** {@link TrustedDeviceUserDecryptionOption} */
trustedDeviceOption?: TrustedDeviceUserDecryptionOption;
/** {@link KeyConnectorUserDecryptionOption} */
keyConnectorOption?: KeyConnectorUserDecryptionOption;
/**
* Initializes a new instance of the UserDecryptionOptions from a response object.
* @param response user decryption options response object
* @returns A new instance of the UserDecryptionOptions.
* @throws If the response is nullish, this method will throw an error. User decryption options
* are required for client initialization.
*/
// TODO: Change response type to `UserDecryptionOptionsResponse` after 2023.10 release (https://bitwarden.atlassian.net/browse/PM-3537)
static fromResponse(response: IdentityTokenResponse): UserDecryptionOptions {
if (response == null) {
throw new Error("User Decryption Options are required for client initialization.");
}
const decryptionOptions = new UserDecryptionOptions();
if (response.userDecryptionOptions) {
// If the response has userDecryptionOptions, this means it's on a post-TDE server version and can interrogate
// the new decryption options.
const responseOptions = response.userDecryptionOptions;
decryptionOptions.hasMasterPassword = responseOptions.hasMasterPassword;
decryptionOptions.trustedDeviceOption = TrustedDeviceUserDecryptionOption.fromResponse(
responseOptions.trustedDeviceOption,
);
decryptionOptions.keyConnectorOption = KeyConnectorUserDecryptionOption.fromResponse(
responseOptions.keyConnectorOption,
);
} else {
// If the response does not have userDecryptionOptions, this means it's on a pre-TDE server version and so
// we must base our decryption options on the presence of the keyConnectorUrl.
// Note that the presence of keyConnectorUrl implies that the user does not have a master password, as in pre-TDE
// server versions, a master password short-circuited the addition of the keyConnectorUrl to the response.
// TODO: remove this check after 2023.10 release (https://bitwarden.atlassian.net/browse/PM-3537)
const usingKeyConnector = response.keyConnectorUrl != null;
decryptionOptions.hasMasterPassword = !usingKeyConnector;
if (usingKeyConnector) {
decryptionOptions.keyConnectorOption = new KeyConnectorUserDecryptionOption();
decryptionOptions.keyConnectorOption.keyConnectorUrl = response.keyConnectorUrl;
}
}
return decryptionOptions;
}
/**
* Initializes a new instance of the UserDecryptionOptions from a JSON object.
* @param obj JSON object to deserialize.
* @returns A new instance of the UserDecryptionOptions. Will initialize even if the JSON object is nullish.
*/
static fromJSON(obj: Jsonify<UserDecryptionOptions>): UserDecryptionOptions {
const decryptionOptions = Object.assign(new UserDecryptionOptions(), obj);
decryptionOptions.trustedDeviceOption = TrustedDeviceUserDecryptionOption.fromJSON(
obj?.trustedDeviceOption,
);
decryptionOptions.keyConnectorOption = KeyConnectorUserDecryptionOption.fromJSON(
obj?.keyConnectorOption,
);
return decryptionOptions;
}
}

View File

@@ -1 +1,2 @@
export * from "./domain";
export * from "./spec";

View File

@@ -0,0 +1,38 @@
import {
KeyConnectorUserDecryptionOption,
TrustedDeviceUserDecryptionOption,
UserDecryptionOptions,
} from "../domain";
// To discourage creating new user decryption options, we don't expose a constructor.
// These helpers are for testing purposes only.
/** Testing helper for creating new instances of `UserDecryptionOptions` */
export class FakeUserDecryptionOptions extends UserDecryptionOptions {
constructor(init: Partial<UserDecryptionOptions>) {
super();
Object.assign(this, init);
}
}
/** Testing helper for creating new instances of `KeyConnectorUserDecryptionOption` */
export class FakeKeyConnectorUserDecryptionOption extends KeyConnectorUserDecryptionOption {
constructor(keyConnectorUrl: string) {
super();
this.keyConnectorUrl = keyConnectorUrl;
}
}
/** Testing helper for creating new instances of `TrustedDeviceUserDecryptionOption` */
export class FakeTrustedDeviceUserDecryptionOption extends TrustedDeviceUserDecryptionOption {
constructor(
hasAdminApproval: boolean,
hasLoginApprovingDevice: boolean,
hasManageResetPasswordPermission: boolean,
) {
super();
this.hasAdminApproval = hasAdminApproval;
this.hasLoginApprovingDevice = hasLoginApprovingDevice;
this.hasManageResetPasswordPermission = hasManageResetPasswordPermission;
}
}

View File

@@ -0,0 +1 @@
export * from "./fake-user-decryption-options";