mirror of
https://github.com/bitwarden/browser
synced 2026-02-14 15:33:55 +00:00
PM-14445: Fix compilation with strict mode
KM code only related, but there are few changes in auth and platform too to enable it.
This commit is contained in:
@@ -70,7 +70,7 @@ export class BrowserKeyService extends DefaultKeyService {
|
||||
protected override async getKeyFromStorage(
|
||||
keySuffix: KeySuffixOptions,
|
||||
userId?: UserId,
|
||||
): Promise<UserKey> {
|
||||
): Promise<UserKey | null> {
|
||||
if (keySuffix === KeySuffixOptions.Biometric) {
|
||||
const biometricsResult = await this.biometricsService.authenticateBiometric();
|
||||
|
||||
|
||||
2
apps/desktop/desktop_native/napi/index.d.ts
vendored
2
apps/desktop/desktop_native/napi/index.d.ts
vendored
@@ -17,7 +17,7 @@ export declare namespace passwords {
|
||||
export declare namespace biometrics {
|
||||
export function prompt(hwnd: Buffer, message: string): Promise<boolean>
|
||||
export function available(): Promise<boolean>
|
||||
export function setBiometricSecret(service: string, account: string, secret: string, keyMaterial: KeyMaterial | undefined | null, ivB64: string): Promise<string>
|
||||
export function setBiometricSecret(service: string, account: string, secret: string, keyMaterial: KeyMaterial | undefined | null, ivB64: string | null): Promise<string>
|
||||
export function getBiometricSecret(service: string, account: string, keyMaterial?: KeyMaterial | undefined | null): Promise<string>
|
||||
/**
|
||||
* Derives key material from biometric data. Returns a string encoded with a
|
||||
|
||||
@@ -22,7 +22,7 @@ export class BiometricsRendererIPCListener {
|
||||
serviceName += message.keySuffix;
|
||||
}
|
||||
|
||||
let val: string | boolean = null;
|
||||
let val: string | boolean | null = null;
|
||||
|
||||
if (!message.action) {
|
||||
return val;
|
||||
|
||||
@@ -73,7 +73,7 @@ export default class BiometricUnixMain implements OsBiometricService {
|
||||
return null;
|
||||
} else {
|
||||
const encValue = new EncString(value);
|
||||
this.setIv(encValue.iv);
|
||||
this.setIv(encValue.iv ?? null);
|
||||
const storageDetails = await this.getStorageDetails({ clientKeyHalfB64: clientKeyPartB64 });
|
||||
const storedValue = await biometrics.getBiometricSecret(
|
||||
service,
|
||||
@@ -132,7 +132,7 @@ export default class BiometricUnixMain implements OsBiometricService {
|
||||
|
||||
// Nulls out key material in order to force a re-derive. This should only be used in getBiometricKey
|
||||
// when we want to force a re-derive of the key material.
|
||||
private setIv(iv: string) {
|
||||
private setIv(iv: string | null) {
|
||||
this._iv = iv;
|
||||
this._osKeyHalf = null;
|
||||
}
|
||||
@@ -140,8 +140,8 @@ export default class BiometricUnixMain implements OsBiometricService {
|
||||
private async getStorageDetails({
|
||||
clientKeyHalfB64,
|
||||
}: {
|
||||
clientKeyHalfB64: string;
|
||||
}): Promise<{ key_material: biometrics.KeyMaterial; ivB64: string }> {
|
||||
clientKeyHalfB64: string | undefined;
|
||||
}): Promise<{ key_material: biometrics.KeyMaterial; ivB64: string | null }> {
|
||||
if (this._osKeyHalf == null) {
|
||||
const keyMaterial = await biometrics.deriveKeyMaterial(this._iv);
|
||||
// osKeyHalf is based on the iv and in contrast to windows is not locked behind user verefication!
|
||||
|
||||
@@ -52,7 +52,7 @@ export default class BiometricWindowsMain implements OsBiometricService {
|
||||
return value;
|
||||
} else {
|
||||
const encValue = new EncString(value);
|
||||
this.setIv(encValue.iv);
|
||||
this.setIv(encValue.iv ?? null);
|
||||
const storageDetails = await this.getStorageDetails({
|
||||
clientKeyHalfB64,
|
||||
});
|
||||
@@ -102,8 +102,8 @@ export default class BiometricWindowsMain implements OsBiometricService {
|
||||
private async getStorageDetails({
|
||||
clientKeyHalfB64,
|
||||
}: {
|
||||
clientKeyHalfB64: string;
|
||||
}): Promise<{ key_material: biometrics.KeyMaterial; ivB64: string }> {
|
||||
clientKeyHalfB64: string | undefined;
|
||||
}): Promise<{ key_material: biometrics.KeyMaterial; ivB64: string | null }> {
|
||||
if (this._osKeyHalf == null) {
|
||||
// Prompts Windows Hello
|
||||
const keyMaterial = await biometrics.deriveKeyMaterial(this._iv);
|
||||
@@ -122,7 +122,7 @@ export default class BiometricWindowsMain implements OsBiometricService {
|
||||
|
||||
// Nulls out key material in order to force a re-derive. This should only be used in getBiometricKey
|
||||
// when we want to force a re-derive of the key material.
|
||||
private setIv(iv: string) {
|
||||
private setIv(iv: string | null) {
|
||||
this._iv = iv;
|
||||
this._osKeyHalf = null;
|
||||
}
|
||||
@@ -141,9 +141,9 @@ export default class BiometricWindowsMain implements OsBiometricService {
|
||||
encryptedValue: EncString,
|
||||
service: string,
|
||||
storageKey: string,
|
||||
clientKeyPartB64: string,
|
||||
clientKeyPartB64: string | undefined,
|
||||
) {
|
||||
if (encryptedValue.iv == null || encryptedValue == null) {
|
||||
if (encryptedValue == null || encryptedValue.iv == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -175,7 +175,7 @@ export default class BiometricWindowsMain implements OsBiometricService {
|
||||
storageKey,
|
||||
}: {
|
||||
value: SymmetricCryptoKey;
|
||||
clientKeyPartB64: string;
|
||||
clientKeyPartB64: string | undefined;
|
||||
service: string;
|
||||
storageKey: string;
|
||||
}): Promise<boolean> {
|
||||
@@ -206,7 +206,7 @@ export default class BiometricWindowsMain implements OsBiometricService {
|
||||
/** Derives a witness key from a symmetric key being stored for biometric protection */
|
||||
private witnessKeyMaterial(
|
||||
symmetricKey: SymmetricCryptoKey,
|
||||
clientKeyPartB64: string,
|
||||
clientKeyPartB64: string | undefined,
|
||||
): biometrics.KeyMaterial {
|
||||
const key = symmetricKey?.macKeyB64 ?? symmetricKey?.keyB64;
|
||||
return {
|
||||
|
||||
@@ -9,7 +9,7 @@ import { WindowMain } from "../../main/window.main";
|
||||
import { DesktopBiometricsService, OsBiometricService } from "./desktop.biometrics.service";
|
||||
|
||||
export class BiometricsService extends DesktopBiometricsService {
|
||||
private platformSpecificService: OsBiometricService;
|
||||
private platformSpecificService: OsBiometricService | undefined;
|
||||
private clientKeyHalves = new Map<string, string>();
|
||||
|
||||
constructor(
|
||||
@@ -65,18 +65,30 @@ export class BiometricsService extends DesktopBiometricsService {
|
||||
}
|
||||
|
||||
async supportsBiometric() {
|
||||
if (this.platformSpecificService === undefined) {
|
||||
throw new Error("Biometric service not loaded");
|
||||
}
|
||||
return await this.platformSpecificService.osSupportsBiometric();
|
||||
}
|
||||
|
||||
async biometricsNeedsSetup() {
|
||||
if (this.platformSpecificService === undefined) {
|
||||
throw new Error("Biometric service not loaded");
|
||||
}
|
||||
return await this.platformSpecificService.osBiometricsNeedsSetup();
|
||||
}
|
||||
|
||||
async biometricsSupportsAutoSetup() {
|
||||
if (this.platformSpecificService === undefined) {
|
||||
throw new Error("Biometric service not loaded");
|
||||
}
|
||||
return await this.platformSpecificService.osBiometricsCanAutoSetup();
|
||||
}
|
||||
|
||||
async biometricsSetup() {
|
||||
if (this.platformSpecificService === undefined) {
|
||||
throw new Error("Biometric service not loaded");
|
||||
}
|
||||
await this.platformSpecificService.osBiometricsSetup();
|
||||
}
|
||||
|
||||
@@ -96,12 +108,16 @@ export class BiometricsService extends DesktopBiometricsService {
|
||||
}
|
||||
|
||||
async authenticateBiometric(): Promise<boolean> {
|
||||
if (this.platformSpecificService === undefined) {
|
||||
throw new Error("Biometric service not loaded");
|
||||
}
|
||||
|
||||
let result = false;
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.interruptProcessReload(
|
||||
() => {
|
||||
return this.platformSpecificService.authenticateBiometric();
|
||||
return this.platformSpecificService!.authenticateBiometric();
|
||||
},
|
||||
(response) => {
|
||||
result = response;
|
||||
@@ -112,14 +128,22 @@ export class BiometricsService extends DesktopBiometricsService {
|
||||
}
|
||||
|
||||
async isBiometricUnlockAvailable(): Promise<boolean> {
|
||||
if (this.platformSpecificService === undefined) {
|
||||
throw new Error("Biometric service not loaded");
|
||||
}
|
||||
|
||||
return await this.platformSpecificService.osSupportsBiometric();
|
||||
}
|
||||
|
||||
async getBiometricKey(service: string, storageKey: string): Promise<string | null> {
|
||||
if (this.platformSpecificService === undefined) {
|
||||
throw new Error("Biometric service not loaded");
|
||||
}
|
||||
|
||||
return await this.interruptProcessReload(async () => {
|
||||
await this.enforceClientKeyHalf(service, storageKey);
|
||||
|
||||
return await this.platformSpecificService.getBiometricKey(
|
||||
return await this.platformSpecificService!.getBiometricKey(
|
||||
service,
|
||||
storageKey,
|
||||
this.getClientKeyHalf(service, storageKey),
|
||||
@@ -128,6 +152,10 @@ export class BiometricsService extends DesktopBiometricsService {
|
||||
}
|
||||
|
||||
async setBiometricKey(service: string, storageKey: string, value: string): Promise<void> {
|
||||
if (this.platformSpecificService === undefined) {
|
||||
throw new Error("Biometric service not loaded");
|
||||
}
|
||||
|
||||
await this.enforceClientKeyHalf(service, storageKey);
|
||||
|
||||
return await this.platformSpecificService.setBiometricKey(
|
||||
@@ -156,6 +184,10 @@ export class BiometricsService extends DesktopBiometricsService {
|
||||
}
|
||||
|
||||
async deleteBiometricKey(service: string, storageKey: string): Promise<void> {
|
||||
if (this.platformSpecificService === undefined) {
|
||||
throw new Error("Biometric service not loaded");
|
||||
}
|
||||
|
||||
this.clientKeyHalves.delete(this.clientKeyHalfKey(service, storageKey));
|
||||
return await this.platformSpecificService.deleteBiometricKey(service, storageKey);
|
||||
}
|
||||
@@ -163,15 +195,15 @@ export class BiometricsService extends DesktopBiometricsService {
|
||||
private async interruptProcessReload<T>(
|
||||
callback: () => Promise<T>,
|
||||
restartReloadCallback: (arg: T) => boolean = () => false,
|
||||
): Promise<T> {
|
||||
): Promise<T | null> {
|
||||
this.messagingService.send("cancelProcessReload");
|
||||
let restartReload = false;
|
||||
let response: T;
|
||||
let response: T | null = null;
|
||||
try {
|
||||
response = await callback();
|
||||
restartReload ||= restartReloadCallback(response);
|
||||
} catch (error) {
|
||||
if (error.message === "Biometric authentication failed") {
|
||||
if (error instanceof Error && error.message === "Biometric authentication failed") {
|
||||
restartReload = false;
|
||||
} else {
|
||||
restartReload = true;
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from "./emergency-access.module";
|
||||
export * from "./services";
|
||||
export * from "./request/emergency-access-update.request";
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { OrganizationUserResetPasswordWithIdRequest } from "@bitwarden/admin-console/common";
|
||||
import { WebauthnRotateCredentialRequest } from "@bitwarden/common/auth/models/request/webauthn-rotate-credential.request";
|
||||
import { EncryptedString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||
import { SendWithIdRequest } from "@bitwarden/common/src/tools/send/models/request/send-with-id.request";
|
||||
import { CipherWithIdRequest } from "@bitwarden/common/src/vault/models/request/cipher-with-id.request";
|
||||
import { FolderWithIdRequest } from "@bitwarden/common/src/vault/models/request/folder-with-id.request";
|
||||
@@ -7,9 +8,12 @@ import { FolderWithIdRequest } from "@bitwarden/common/src/vault/models/request/
|
||||
import { EmergencyAccessWithIdRequest } from "../../../auth/emergency-access/request/emergency-access-update.request";
|
||||
|
||||
export class UpdateKeyRequest {
|
||||
masterPasswordHash: string;
|
||||
key: string;
|
||||
privateKey: string;
|
||||
constructor(
|
||||
readonly masterPasswordHash: EncryptedString,
|
||||
readonly key: string,
|
||||
readonly privateKey: EncryptedString,
|
||||
) {}
|
||||
|
||||
ciphers: CipherWithIdRequest[] = [];
|
||||
folders: FolderWithIdRequest[] = [];
|
||||
sends: SendWithIdRequest[] = [];
|
||||
|
||||
@@ -22,9 +22,8 @@ import { FolderWithIdRequest } from "@bitwarden/common/vault/models/request/fold
|
||||
import { KeyService } from "@bitwarden/key-management";
|
||||
|
||||
import { OrganizationUserResetPasswordService } from "../../admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service";
|
||||
import { WebauthnLoginAdminService } from "../core";
|
||||
import { EmergencyAccessService } from "../emergency-access";
|
||||
import { EmergencyAccessWithIdRequest } from "../emergency-access/request/emergency-access-update.request";
|
||||
import { WebauthnLoginAdminService } from "../../auth/core";
|
||||
import { EmergencyAccessService, EmergencyAccessWithIdRequest } from "../../auth/emergency-access";
|
||||
|
||||
import { UserKeyRotationApiService } from "./user-key-rotation-api.service";
|
||||
import { UserKeyRotationService } from "./user-key-rotation.service";
|
||||
|
||||
@@ -80,15 +80,8 @@ export class UserKeyRotationService {
|
||||
throw new Error("User key could not be created");
|
||||
}
|
||||
|
||||
// Create new request
|
||||
const request = new UpdateKeyRequest();
|
||||
|
||||
// Add new user key
|
||||
request.key = newEncUserKey.encryptedString;
|
||||
|
||||
// Add master key hash
|
||||
const masterPasswordHash = await this.keyService.hashMasterKey(masterPassword, masterKey);
|
||||
request.masterPasswordHash = masterPasswordHash;
|
||||
|
||||
// Get original user key
|
||||
// Note: We distribute the legacy key, but not all domains actually use it. If any of those
|
||||
@@ -99,7 +92,14 @@ export class UserKeyRotationService {
|
||||
this.logService.info("[Userkey rotation] Is legacy user: " + isMasterKey);
|
||||
|
||||
// Add re-encrypted data
|
||||
request.privateKey = await this.encryptPrivateKey(newUserKey, user.id);
|
||||
const privateKey = (await this.encryptPrivateKey(newUserKey, user.id))!;
|
||||
|
||||
// Create new request
|
||||
const request = new UpdateKeyRequest(
|
||||
newEncUserKey.encryptedString!,
|
||||
masterPasswordHash,
|
||||
privateKey,
|
||||
);
|
||||
|
||||
const rotatedCiphers = await this.cipherService.getRotatedData(
|
||||
originalUserKey,
|
||||
@@ -177,6 +177,6 @@ export class UserKeyRotationService {
|
||||
if (!privateKey) {
|
||||
throw new Error("No private key found for user key rotation");
|
||||
}
|
||||
return (await this.encryptService.encrypt(privateKey, newUserKey)).encryptedString;
|
||||
return (await this.encryptService.encrypt(privateKey, newUserKey)).encryptedString!;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,9 @@ export class MigrateFromLegacyEncryptionComponent {
|
||||
}
|
||||
|
||||
const activeUser = await firstValueFrom(this.accountService.activeAccount$);
|
||||
if (activeUser === null) {
|
||||
throw new Error("No active user.");
|
||||
}
|
||||
|
||||
const hasUserKey = await this.keyService.hasUserKey(activeUser.id);
|
||||
if (hasUserKey) {
|
||||
@@ -56,6 +59,9 @@ export class MigrateFromLegacyEncryptionComponent {
|
||||
}
|
||||
|
||||
const masterPassword = this.formGroup.value.masterPassword;
|
||||
if (!masterPassword) {
|
||||
throw new Error("Master password cannot be empty.");
|
||||
}
|
||||
|
||||
try {
|
||||
await this.syncService.fullSync(false, true);
|
||||
@@ -71,7 +77,10 @@ export class MigrateFromLegacyEncryptionComponent {
|
||||
this.messagingService.send("logout");
|
||||
} catch (e) {
|
||||
// If the error is due to missing folders, we can delete all folders and try again
|
||||
if (e.message === "All existing folders must be included in the rotation.") {
|
||||
if (
|
||||
e instanceof Error &&
|
||||
e.message === "All existing folders must be included in the rotation."
|
||||
) {
|
||||
const deleteFolders = await this.dialogService.openSimpleDialog({
|
||||
type: "warning",
|
||||
title: { key: "encryptionKeyUpdateCannotProceed" },
|
||||
|
||||
@@ -45,7 +45,7 @@ export abstract class PinServiceAbstraction {
|
||||
/**
|
||||
* Clears the ephemeral (stored in memory) version of the UserKey, encrypted by the PinKey.
|
||||
*/
|
||||
abstract clearPinKeyEncryptedUserKeyEphemeral(userId: UserId): Promise<void>;
|
||||
abstract clearPinKeyEncryptedUserKeyEphemeral(userId?: UserId): Promise<void>;
|
||||
|
||||
/**
|
||||
* Creates a pinKeyEncryptedUserKey from the provided PIN and UserKey.
|
||||
@@ -99,7 +99,7 @@ export abstract class PinServiceAbstraction {
|
||||
/**
|
||||
* Clears the old MasterKey, encrypted by the PinKey.
|
||||
*/
|
||||
abstract clearOldPinKeyEncryptedMasterKey: (userId: UserId) => Promise<void>;
|
||||
abstract clearOldPinKeyEncryptedMasterKey: (userId?: UserId) => Promise<void>;
|
||||
|
||||
/**
|
||||
* Makes a PinKey from the provided PIN.
|
||||
|
||||
@@ -168,7 +168,7 @@ export class PinService implements PinServiceAbstraction {
|
||||
);
|
||||
}
|
||||
|
||||
async clearPinKeyEncryptedUserKeyEphemeral(userId: UserId): Promise<void> {
|
||||
async clearPinKeyEncryptedUserKeyEphemeral(userId?: UserId): Promise<void> {
|
||||
this.validateUserId(userId, "Cannot clear pinKeyEncryptedUserKeyEphemeral.");
|
||||
|
||||
await this.stateProvider.setUserState(PIN_KEY_ENCRYPTED_USER_KEY_EPHEMERAL, null, userId);
|
||||
@@ -249,7 +249,7 @@ export class PinService implements PinServiceAbstraction {
|
||||
);
|
||||
}
|
||||
|
||||
async clearOldPinKeyEncryptedMasterKey(userId: UserId): Promise<void> {
|
||||
async clearOldPinKeyEncryptedMasterKey(userId?: UserId): Promise<void> {
|
||||
this.validateUserId(userId, "Cannot clear oldPinKeyEncryptedMasterKey.");
|
||||
|
||||
await this.stateProvider.setUserState(OLD_PIN_KEY_ENCRYPTED_MASTER_KEY, null, userId);
|
||||
|
||||
@@ -28,7 +28,7 @@ export abstract class StateService<T extends Account = Account> {
|
||||
/**
|
||||
* Sets the user's auto key
|
||||
*/
|
||||
setUserKeyAutoUnlock: (value: string, options?: StorageOptions) => Promise<void>;
|
||||
setUserKeyAutoUnlock: (value: string | null, options?: StorageOptions) => Promise<void>;
|
||||
/**
|
||||
* Gets the user's biometric key
|
||||
*/
|
||||
@@ -55,7 +55,7 @@ export abstract class StateService<T extends Account = Account> {
|
||||
/**
|
||||
* @deprecated For migration purposes only, use setUserKeyAuto instead
|
||||
*/
|
||||
setCryptoMasterKeyAuto: (value: string, options?: StorageOptions) => Promise<void>;
|
||||
setCryptoMasterKeyAuto: (value: string | null, options?: StorageOptions) => Promise<void>;
|
||||
getDuckDuckGoSharedKey: (options?: StorageOptions) => Promise<string>;
|
||||
setDuckDuckGoSharedKey: (value: string, options?: StorageOptions) => Promise<void>;
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import { ObservableInput, OperatorFunction, map } from "rxjs";
|
||||
*/
|
||||
export function convertValues<TKey extends PropertyKey, TInput, TOutput>(
|
||||
project: (key: TKey, value: TInput) => ObservableInput<TOutput>,
|
||||
): OperatorFunction<Record<TKey, TInput>, Record<TKey, ObservableInput<TOutput>>> {
|
||||
): OperatorFunction<Record<TKey, TInput> | null, Record<TKey, ObservableInput<TOutput>>> {
|
||||
return map((inputRecord) => {
|
||||
if (inputRecord == null) {
|
||||
return null;
|
||||
|
||||
@@ -52,7 +52,7 @@ export class DefaultStateProvider implements StateProvider {
|
||||
|
||||
async setUserState<T>(
|
||||
userKeyDefinition: UserKeyDefinition<T>,
|
||||
value: T,
|
||||
value: T | null,
|
||||
userId?: UserId,
|
||||
): Promise<[UserId, T]> {
|
||||
if (userId) {
|
||||
|
||||
@@ -60,7 +60,7 @@ export abstract class StateProvider {
|
||||
*/
|
||||
abstract setUserState<T>(
|
||||
keyDefinition: UserKeyDefinition<T>,
|
||||
value: T,
|
||||
value: T | null,
|
||||
userId?: UserId,
|
||||
): Promise<[UserId, T]>;
|
||||
|
||||
@@ -68,10 +68,13 @@ export abstract class StateProvider {
|
||||
abstract getActive<T>(userKeyDefinition: UserKeyDefinition<T>): ActiveUserState<T>;
|
||||
|
||||
/** @see{@link SingleUserStateProvider.get} */
|
||||
abstract getUser<T>(userId: UserId, userKeyDefinition: UserKeyDefinition<T>): SingleUserState<T>;
|
||||
abstract getUser<T>(
|
||||
userId: UserId,
|
||||
userKeyDefinition: UserKeyDefinition<T>,
|
||||
): SingleUserState<T | null>;
|
||||
|
||||
/** @see{@link GlobalStateProvider.get} */
|
||||
abstract getGlobal<T>(keyDefinition: KeyDefinition<T>): GlobalState<T>;
|
||||
abstract getGlobal<T>(keyDefinition: KeyDefinition<T>): GlobalState<T | null>;
|
||||
abstract getDerived<TFrom, TTo, TDeps extends DerivedStateDependencies>(
|
||||
parentState$: Observable<TFrom>,
|
||||
deriveDefinition: DeriveDefinition<TFrom, TTo, TDeps>,
|
||||
|
||||
@@ -37,7 +37,7 @@ export interface ActiveUserState<T> extends UserState<T> {
|
||||
* Resolves to the new state. If `shouldUpdate` returns false, the promise will resolve to the current state.
|
||||
*/
|
||||
readonly update: <TCombine>(
|
||||
configureState: (state: T, dependencies: TCombine) => T,
|
||||
configureState: (state: T | null, dependencies: TCombine) => T | null,
|
||||
options?: StateUpdateOptions<T, TCombine>,
|
||||
) => Promise<[UserId, T]>;
|
||||
}
|
||||
@@ -56,7 +56,7 @@ export interface SingleUserState<T> extends UserState<T> {
|
||||
* Resolves to the new state. If `shouldUpdate` returns false, the promise will resolve to the current state.
|
||||
*/
|
||||
readonly update: <TCombine>(
|
||||
configureState: (state: T, dependencies: TCombine) => T,
|
||||
configureState: (state: T | null, dependencies: TCombine) => T | null,
|
||||
options?: StateUpdateOptions<T, TCombine>,
|
||||
) => Promise<T>;
|
||||
) => Promise<T | null>;
|
||||
}
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
import { EncryptedOrganizationKeyData } from "@bitwarden/common/admin-console/models/data/encrypted-organization-key.data";
|
||||
|
||||
import { ProfileOrganizationResponse } from "../../../common/src/admin-console/models/response/profile-organization.response";
|
||||
import { ProfileProviderOrganizationResponse } from "../../../common/src/admin-console/models/response/profile-provider-organization.response";
|
||||
import { ProfileProviderResponse } from "../../../common/src/admin-console/models/response/profile-provider.response";
|
||||
import { KdfConfig } from "../../../common/src/auth/models/domain/kdf-config";
|
||||
import { KeySuffixOptions, HashPurpose } from "../../../common/src/platform/enums";
|
||||
import { EncryptedString, EncString } from "../../../common/src/platform/models/domain/enc-string";
|
||||
import { SymmetricCryptoKey } from "../../../common/src/platform/models/domain/symmetric-crypto-key";
|
||||
import { OrganizationId, UserId } from "../../../common/src/types/guid";
|
||||
import { ProfileOrganizationResponse } from "@bitwarden/common/admin-console/models/response/profile-organization.response";
|
||||
import { ProfileProviderOrganizationResponse } from "@bitwarden/common/admin-console/models/response/profile-provider-organization.response";
|
||||
import { ProfileProviderResponse } from "@bitwarden/common/admin-console/models/response/profile-provider.response";
|
||||
import { KdfConfig } from "@bitwarden/common/auth/models/domain/kdf-config";
|
||||
import { KeySuffixOptions, HashPurpose } from "@bitwarden/common/platform/enums";
|
||||
import { EncryptedString, EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
import { OrganizationId, UserId } from "@bitwarden/common/types/guid";
|
||||
import {
|
||||
UserKey,
|
||||
MasterKey,
|
||||
@@ -18,7 +17,7 @@ import {
|
||||
CipherKey,
|
||||
UserPrivateKey,
|
||||
UserPublicKey,
|
||||
} from "../../../common/src/types/key";
|
||||
} from "@bitwarden/common/types/key";
|
||||
|
||||
export class UserPrivateKeyDecryptionFailedError extends Error {
|
||||
constructor() {
|
||||
@@ -38,7 +37,7 @@ export type CipherDecryptionKeys = {
|
||||
/**
|
||||
* A users decrypted organization keys.
|
||||
*/
|
||||
orgKeys: Record<OrganizationId, OrgKey>;
|
||||
orgKeys: Record<OrganizationId, OrgKey> | null;
|
||||
};
|
||||
|
||||
export abstract class KeyService {
|
||||
@@ -47,7 +46,8 @@ export abstract class KeyService {
|
||||
* is in a locked or logged out state.
|
||||
* @param userId The user id of the user to get the {@see UserKey} for.
|
||||
*/
|
||||
abstract userKey$(userId: UserId): Observable<UserKey>;
|
||||
abstract userKey$(userId: UserId): Observable<UserKey | null>;
|
||||
|
||||
/**
|
||||
* Returns the an observable key for the given user id.
|
||||
*
|
||||
@@ -55,6 +55,7 @@ export abstract class KeyService {
|
||||
* @param userId The desired user
|
||||
*/
|
||||
abstract getInMemoryUserKeyFor$(userId: UserId): Observable<UserKey>;
|
||||
|
||||
/**
|
||||
* Sets the provided user key and stores
|
||||
* any other necessary versions (such as auto, biometrics,
|
||||
@@ -64,7 +65,8 @@ export abstract class KeyService {
|
||||
* @param key The user key to set
|
||||
* @param userId The desired user
|
||||
*/
|
||||
abstract setUserKey(key: UserKey, userId?: string): Promise<void>;
|
||||
abstract setUserKey(key: UserKey | null, userId?: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* Sets the provided user keys and stores any other necessary versions
|
||||
* (such as auto, biometrics, or pin).
|
||||
@@ -79,17 +81,20 @@ export abstract class KeyService {
|
||||
* @param userId The desired user
|
||||
*/
|
||||
abstract setUserKeys(userKey: UserKey, encPrivateKey: string, userId: UserId): Promise<void>;
|
||||
|
||||
/**
|
||||
* Gets the user key from memory and sets it again,
|
||||
* kicking off a refresh of any additional keys
|
||||
* (such as auto, biometrics, or pin)
|
||||
*/
|
||||
abstract refreshAdditionalKeys(): Promise<void>;
|
||||
|
||||
/**
|
||||
* Observable value that returns whether or not the currently active user has ever had auser key,
|
||||
* i.e. has ever been unlocked/decrypted. This is key for differentiating between TDE locked and standard locked states.
|
||||
*/
|
||||
abstract everHadUserKey$: Observable<boolean>;
|
||||
|
||||
/**
|
||||
* Retrieves the user key
|
||||
* @param userId The desired user
|
||||
@@ -121,13 +126,17 @@ export abstract class KeyService {
|
||||
* @param userId The desired user
|
||||
*/
|
||||
abstract getUserKeyWithLegacySupport(userId: UserId): Promise<UserKey>;
|
||||
|
||||
/**
|
||||
* Retrieves the user key from storage
|
||||
* @param keySuffix The desired version of the user's key to retrieve
|
||||
* @param userId The desired user
|
||||
* @returns The user key
|
||||
*/
|
||||
abstract getUserKeyFromStorage(keySuffix: KeySuffixOptions, userId?: string): Promise<UserKey>;
|
||||
abstract getUserKeyFromStorage(
|
||||
keySuffix: KeySuffixOptions,
|
||||
userId?: string,
|
||||
): Promise<UserKey | null>;
|
||||
|
||||
/**
|
||||
* Determines whether the user key is available for the given user.
|
||||
@@ -135,41 +144,48 @@ export abstract class KeyService {
|
||||
* @returns True if the user key is available
|
||||
*/
|
||||
abstract hasUserKey(userId?: UserId): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Determines whether the user key is available for the given user in memory.
|
||||
* @param userId The desired user. If not provided, the active user will be used. If no active user exists, the method will return false.
|
||||
* @returns True if the user key is available
|
||||
*/
|
||||
abstract hasUserKeyInMemory(userId?: string): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* @param keySuffix The desired version of the user's key to check
|
||||
* @param userId The desired user
|
||||
* @returns True if the provided version of the user key is stored
|
||||
*/
|
||||
abstract hasUserKeyStored(keySuffix: KeySuffixOptions, userId?: string): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Generates a new user key
|
||||
* @param masterKey The user's master key
|
||||
* @returns A new user key and the master key protected version of it
|
||||
*/
|
||||
abstract makeUserKey(key: MasterKey): Promise<[UserKey, EncString]>;
|
||||
|
||||
/**
|
||||
* Clears the user's stored version of the user key
|
||||
* @param keySuffix The desired version of the key to clear
|
||||
* @param userId The desired user
|
||||
*/
|
||||
abstract clearStoredUserKey(keySuffix: KeySuffixOptions, userId?: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* Stores the master key encrypted user key
|
||||
* @param userKeyMasterKey The master key encrypted user key to set
|
||||
* @param userId The desired user
|
||||
*/
|
||||
abstract setMasterKeyEncryptedUserKey(UserKeyMasterKey: string, userId: string): Promise<void>;
|
||||
abstract setMasterKeyEncryptedUserKey(UserKeyMasterKey: string, userId?: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* @param password The user's master password that will be used to derive a master key if one isn't found
|
||||
* @param userId The desired user
|
||||
*/
|
||||
abstract getOrDeriveMasterKey(password: string, userId?: string): Promise<MasterKey>;
|
||||
|
||||
/**
|
||||
* Generates a master key from the provided password
|
||||
* @param password The user's master password
|
||||
@@ -178,6 +194,7 @@ export abstract class KeyService {
|
||||
* @returns A master key derived from the provided password
|
||||
*/
|
||||
abstract makeMasterKey(password: string, email: string, KdfConfig: KdfConfig): Promise<MasterKey>;
|
||||
|
||||
/**
|
||||
* Encrypts the existing (or provided) user key with the
|
||||
* provided master key
|
||||
@@ -189,6 +206,7 @@ export abstract class KeyService {
|
||||
masterKey: MasterKey,
|
||||
userKey?: UserKey,
|
||||
): Promise<[UserKey, EncString]>;
|
||||
|
||||
/**
|
||||
* Creates a master password hash from the user's master password. Can
|
||||
* be used for local authentication or for server authentication depending
|
||||
@@ -203,6 +221,7 @@ export abstract class KeyService {
|
||||
key: MasterKey,
|
||||
hashPurpose?: HashPurpose,
|
||||
): Promise<string>;
|
||||
|
||||
/**
|
||||
* Compares the provided master password to the stored password hash and server password hash.
|
||||
* Updates the stored hash if outdated.
|
||||
@@ -212,6 +231,7 @@ export abstract class KeyService {
|
||||
* key hash or the server key hash
|
||||
*/
|
||||
abstract compareAndUpdateKeyHash(masterPassword: string, masterKey: MasterKey): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Stores the encrypted organization keys and clears any decrypted
|
||||
* organization keys currently in memory
|
||||
@@ -224,6 +244,7 @@ export abstract class KeyService {
|
||||
providerOrgs: ProfileProviderOrganizationResponse[],
|
||||
userId: UserId,
|
||||
): Promise<void>;
|
||||
|
||||
/**
|
||||
* Retrieves a stream of the active users organization keys,
|
||||
* will NOT emit any value if there is no active user.
|
||||
@@ -231,6 +252,7 @@ export abstract class KeyService {
|
||||
* @deprecated Use {@link orgKeys$} with a required {@link UserId} instead.
|
||||
*/
|
||||
abstract activeUserOrgKeys$: Observable<Record<OrganizationId, OrgKey>>;
|
||||
|
||||
/**
|
||||
* Returns the organization's symmetric key
|
||||
* @deprecated Use the observable userOrgKeys$ and `map` to the desired {@link OrgKey} instead
|
||||
@@ -238,6 +260,7 @@ export abstract class KeyService {
|
||||
* @returns The organization's symmetric key
|
||||
*/
|
||||
abstract getOrgKey(orgId: string): Promise<OrgKey>;
|
||||
|
||||
/**
|
||||
* Uses the org key to derive a new symmetric key for encrypting data
|
||||
* @param orgKey The organization's symmetric key
|
||||
@@ -252,17 +275,20 @@ export abstract class KeyService {
|
||||
* @param userId The user id of the user for which to store the keys for.
|
||||
*/
|
||||
abstract setProviderKeys(orgs: ProfileProviderResponse[], userId: UserId): Promise<void>;
|
||||
|
||||
/**
|
||||
* @param providerId The desired provider
|
||||
* @returns The provider's symmetric key
|
||||
*/
|
||||
abstract getProviderKey(providerId: string): Promise<ProviderKey>;
|
||||
abstract getProviderKey(providerId: string): Promise<ProviderKey | null>;
|
||||
|
||||
/**
|
||||
* Creates a new organization key and encrypts it with the user's public key.
|
||||
* This method can also return Provider keys for creating new Provider users.
|
||||
* @returns The new encrypted org key and the decrypted key itself
|
||||
*/
|
||||
abstract makeOrgKey<T extends OrgKey | ProviderKey>(): Promise<[EncString, T]>;
|
||||
|
||||
/**
|
||||
* Sets the user's encrypted private key in storage and
|
||||
* clears the decrypted private key from memory
|
||||
@@ -270,6 +296,7 @@ export abstract class KeyService {
|
||||
* @param encPrivateKey An encrypted private key
|
||||
*/
|
||||
abstract setPrivateKey(encPrivateKey: string, userId: UserId): Promise<void>;
|
||||
|
||||
/**
|
||||
* Returns the private key from memory. If not available, decrypts it
|
||||
* from storage and stores it in memory
|
||||
@@ -279,7 +306,7 @@ export abstract class KeyService {
|
||||
*
|
||||
* @deprecated Use {@link userPrivateKey$} instead.
|
||||
*/
|
||||
abstract getPrivateKey(): Promise<Uint8Array>;
|
||||
abstract getPrivateKey(): Promise<Uint8Array | null>;
|
||||
|
||||
/**
|
||||
* Gets an observable stream of the given users decrypted private key, will emit null if the user
|
||||
@@ -288,7 +315,7 @@ export abstract class KeyService {
|
||||
*
|
||||
* @param userId The user id of the user to get the data for.
|
||||
*/
|
||||
abstract userPrivateKey$(userId: UserId): Observable<UserPrivateKey>;
|
||||
abstract userPrivateKey$(userId: UserId): Observable<UserPrivateKey | null>;
|
||||
|
||||
/**
|
||||
* Gets an observable stream of the given users encrypted private key, will emit null if the user
|
||||
@@ -299,7 +326,7 @@ export abstract class KeyService {
|
||||
* @deprecated Temporary function to allow the SDK to be initialized after the login process, it
|
||||
* will be removed when auth has been migrated to the SDK.
|
||||
*/
|
||||
abstract userEncryptedPrivateKey$(userId: UserId): Observable<EncryptedString>;
|
||||
abstract userEncryptedPrivateKey$(userId: UserId): Observable<EncryptedString | null>;
|
||||
|
||||
/**
|
||||
* Gets an observable stream of the given users decrypted private key with legacy support,
|
||||
@@ -308,7 +335,7 @@ export abstract class KeyService {
|
||||
*
|
||||
* @param userId The user id of the user to get the data for.
|
||||
*/
|
||||
abstract userPrivateKeyWithLegacySupport$(userId: UserId): Observable<UserPrivateKey>;
|
||||
abstract userPrivateKeyWithLegacySupport$(userId: UserId): Observable<UserPrivateKey | null>;
|
||||
|
||||
/**
|
||||
* Generates a fingerprint phrase for the user based on their public key
|
||||
@@ -317,6 +344,7 @@ export abstract class KeyService {
|
||||
* @returns The user's fingerprint phrase
|
||||
*/
|
||||
abstract getFingerprint(fingerprintMaterial: string, publicKey?: Uint8Array): Promise<string[]>;
|
||||
|
||||
/**
|
||||
* Generates a new keypair
|
||||
* @param key A key to encrypt the private key with. If not provided,
|
||||
@@ -325,6 +353,7 @@ export abstract class KeyService {
|
||||
* @throws If the provided key is a null-ish value.
|
||||
*/
|
||||
abstract makeKeyPair(key: SymmetricCryptoKey): Promise<[string, EncString]>;
|
||||
|
||||
/**
|
||||
* Clears the user's pin keys from storage
|
||||
* Note: This will remove the stored pin and as a result,
|
||||
@@ -332,17 +361,21 @@ export abstract class KeyService {
|
||||
* @param userId The desired user
|
||||
*/
|
||||
abstract clearPinKeys(userId?: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* @param keyMaterial The key material to derive the send key from
|
||||
* @returns A new send key
|
||||
*/
|
||||
abstract makeSendKey(keyMaterial: Uint8Array): Promise<SymmetricCryptoKey>;
|
||||
|
||||
/**
|
||||
* Clears all of the user's keys from storage
|
||||
* @param userId The user's Id
|
||||
*/
|
||||
abstract clearKeys(userId?: string): Promise<any>;
|
||||
|
||||
abstract randomNumber(min: number, max: number): Promise<number>;
|
||||
|
||||
/**
|
||||
* Generates a new cipher key
|
||||
* @returns A new cipher key
|
||||
@@ -361,6 +394,7 @@ export abstract class KeyService {
|
||||
publicKey: string;
|
||||
privateKey: EncString;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* Previously, the master key was used for any additional key like the biometrics or pin key.
|
||||
* We have switched to using the user key for these purposes. This method is for clearing the state
|
||||
@@ -404,7 +438,7 @@ export abstract class KeyService {
|
||||
*/
|
||||
abstract encryptedOrgKeys$(
|
||||
userId: UserId,
|
||||
): Observable<Record<OrganizationId, EncryptedOrganizationKeyData>>;
|
||||
): Observable<Record<OrganizationId, EncryptedOrganizationKeyData> | null>;
|
||||
|
||||
/**
|
||||
* Gets an observable stream of the users public key. If the user is does not have
|
||||
@@ -414,7 +448,7 @@ export abstract class KeyService {
|
||||
*
|
||||
* @throws If an invalid user id is passed in.
|
||||
*/
|
||||
abstract userPublicKey$(userId: UserId): Observable<UserPublicKey>;
|
||||
abstract userPublicKey$(userId: UserId): Observable<UserPublicKey | null>;
|
||||
|
||||
/**
|
||||
* Validates that a userkey is correct for a given user
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { EncryptedString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
|
||||
import { makeEncString, trackEmissions } from "../../../common/spec";
|
||||
import { makeEncString, trackEmissions } from "@bitwarden/common/spec";
|
||||
import {
|
||||
FakeAccountService,
|
||||
mockAccountServiceWith,
|
||||
} from "../../../common/spec/fake-account-service";
|
||||
import { FakeGlobalState, FakeSingleUserState } from "../../../common/spec/fake-state";
|
||||
import { FakeStateProvider } from "../../../common/spec/fake-state-provider";
|
||||
} from "@bitwarden/common/spec/fake-account-service";
|
||||
import { FakeGlobalState, FakeSingleUserState } from "@bitwarden/common/spec/fake-state";
|
||||
import { FakeStateProvider } from "@bitwarden/common/spec/fake-state-provider";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
|
||||
import { BiometricStateService, DefaultBiometricStateService } from "./biometric-state.service";
|
||||
import {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Observable, firstValueFrom, map, combineLatest } from "rxjs";
|
||||
|
||||
import { EncryptedString, EncString } from "../../../common/src/platform/models/domain/enc-string";
|
||||
import { ActiveUserState, GlobalState, StateProvider } from "../../../common/src/platform/state";
|
||||
import { UserId } from "../../../common/src/types/guid";
|
||||
import { EncryptedString, EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||
import { ActiveUserState, GlobalState, StateProvider } from "@bitwarden/common/platform/state";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
|
||||
import {
|
||||
BIOMETRIC_UNLOCK_ENABLED,
|
||||
@@ -25,7 +25,7 @@ export abstract class BiometricStateService {
|
||||
*
|
||||
* Tracks the currently active user
|
||||
*/
|
||||
abstract encryptedClientKeyHalf$: Observable<EncString | undefined>;
|
||||
abstract encryptedClientKeyHalf$: Observable<EncString | null>;
|
||||
/**
|
||||
* whether or not a password is required on first unlock after opening the application
|
||||
*
|
||||
@@ -62,42 +62,54 @@ export abstract class BiometricStateService {
|
||||
* @param value whether or not a password is required on first unlock after opening the application
|
||||
*/
|
||||
abstract setRequirePasswordOnStart(value: boolean): Promise<void>;
|
||||
|
||||
/**
|
||||
* Updates the biometric unlock enabled state for the currently active user.
|
||||
* @param enabled whether or not to store a biometric key to unlock the vault
|
||||
*/
|
||||
abstract setBiometricUnlockEnabled(enabled: boolean): Promise<void>;
|
||||
|
||||
/**
|
||||
* Gets the biometric unlock enabled state for the given user.
|
||||
* @param userId user Id to check
|
||||
*/
|
||||
abstract getBiometricUnlockEnabled(userId: UserId): Promise<boolean>;
|
||||
|
||||
abstract setEncryptedClientKeyHalf(encryptedKeyHalf: EncString, userId?: UserId): Promise<void>;
|
||||
abstract getEncryptedClientKeyHalf(userId: UserId): Promise<EncString>;
|
||||
|
||||
abstract getEncryptedClientKeyHalf(userId: UserId): Promise<EncString | null>;
|
||||
|
||||
abstract getRequirePasswordOnStart(userId: UserId): Promise<boolean>;
|
||||
|
||||
abstract removeEncryptedClientKeyHalf(userId: UserId): Promise<void>;
|
||||
|
||||
/**
|
||||
* Updates the active user's state to reflect that they've been warned about requiring password on start.
|
||||
*/
|
||||
abstract setDismissedRequirePasswordOnStartCallout(): Promise<void>;
|
||||
|
||||
/**
|
||||
* Updates the active user's state to reflect that they've cancelled the biometric prompt.
|
||||
*/
|
||||
abstract setUserPromptCancelled(): Promise<void>;
|
||||
|
||||
/**
|
||||
* Resets the given user's state to reflect that they haven't cancelled the biometric prompt.
|
||||
* @param userId the user to reset the prompt cancelled state for. If not provided, the currently active user will be used.
|
||||
*/
|
||||
abstract resetUserPromptCancelled(userId?: UserId): Promise<void>;
|
||||
|
||||
/**
|
||||
* Resets all user's state to reflect that they haven't cancelled the biometric prompt.
|
||||
*/
|
||||
abstract resetAllPromptCancelled(): Promise<void>;
|
||||
|
||||
/**
|
||||
* Updates the currently active user's setting for auto prompting for biometrics on application start and lock
|
||||
* @param prompt Whether or not to prompt for biometrics on application start.
|
||||
*/
|
||||
abstract setPromptAutomatically(prompt: boolean): Promise<void>;
|
||||
|
||||
/**
|
||||
* Updates whether or not IPC has been validated by the user this session
|
||||
* @param validated the value to save
|
||||
@@ -110,13 +122,13 @@ export abstract class BiometricStateService {
|
||||
export class DefaultBiometricStateService implements BiometricStateService {
|
||||
private biometricUnlockEnabledState: ActiveUserState<boolean>;
|
||||
private requirePasswordOnStartState: ActiveUserState<boolean>;
|
||||
private encryptedClientKeyHalfState: ActiveUserState<EncryptedString | undefined>;
|
||||
private encryptedClientKeyHalfState: ActiveUserState<EncryptedString>;
|
||||
private dismissedRequirePasswordOnStartCalloutState: ActiveUserState<boolean>;
|
||||
private promptCancelledState: GlobalState<Record<UserId, boolean>>;
|
||||
private promptCancelledState: GlobalState<Record<UserId, boolean> | null>;
|
||||
private promptAutomaticallyState: ActiveUserState<boolean>;
|
||||
private fingerprintValidatedState: GlobalState<boolean>;
|
||||
private fingerprintValidatedState: GlobalState<boolean | null>;
|
||||
biometricUnlockEnabled$: Observable<boolean>;
|
||||
encryptedClientKeyHalf$: Observable<EncString | undefined>;
|
||||
encryptedClientKeyHalf$: Observable<EncString | null>;
|
||||
requirePasswordOnStart$: Observable<boolean>;
|
||||
dismissedRequirePasswordOnStartCallout$: Observable<boolean>;
|
||||
promptCancelled$: Observable<boolean>;
|
||||
@@ -149,7 +161,7 @@ export class DefaultBiometricStateService implements BiometricStateService {
|
||||
this.promptCancelledState.state$,
|
||||
]).pipe(
|
||||
map(([userId, record]) => {
|
||||
return record?.[userId] ?? false;
|
||||
return userId ? (record?.[userId] ?? false) : false;
|
||||
}),
|
||||
);
|
||||
this.promptAutomaticallyState = this.stateProvider.getActive(PROMPT_AUTOMATICALLY);
|
||||
@@ -170,7 +182,7 @@ export class DefaultBiometricStateService implements BiometricStateService {
|
||||
}
|
||||
|
||||
async setRequirePasswordOnStart(value: boolean): Promise<void> {
|
||||
let currentActiveId: UserId;
|
||||
let currentActiveId: UserId | undefined = undefined;
|
||||
await this.requirePasswordOnStartState.update(
|
||||
(_, [userId]) => {
|
||||
currentActiveId = userId;
|
||||
@@ -180,7 +192,7 @@ export class DefaultBiometricStateService implements BiometricStateService {
|
||||
combineLatestWith: this.requirePasswordOnStartState.combinedState$,
|
||||
},
|
||||
);
|
||||
if (!value) {
|
||||
if (!value && currentActiveId) {
|
||||
await this.removeEncryptedClientKeyHalf(currentActiveId);
|
||||
}
|
||||
}
|
||||
@@ -204,7 +216,7 @@ export class DefaultBiometricStateService implements BiometricStateService {
|
||||
));
|
||||
}
|
||||
|
||||
async getEncryptedClientKeyHalf(userId: UserId): Promise<EncString> {
|
||||
async getEncryptedClientKeyHalf(userId: UserId): Promise<EncString | null> {
|
||||
return await firstValueFrom(
|
||||
this.stateProvider
|
||||
.getUser(userId, ENCRYPTED_CLIENT_KEY_HALF)
|
||||
@@ -226,7 +238,9 @@ export class DefaultBiometricStateService implements BiometricStateService {
|
||||
async resetUserPromptCancelled(userId: UserId): Promise<void> {
|
||||
await this.stateProvider.getGlobal(PROMPT_CANCELLED).update(
|
||||
(data, activeUserId) => {
|
||||
delete data[userId ?? activeUserId];
|
||||
if (data) {
|
||||
delete data[userId ?? activeUserId];
|
||||
}
|
||||
return data;
|
||||
},
|
||||
{
|
||||
@@ -239,14 +253,16 @@ export class DefaultBiometricStateService implements BiometricStateService {
|
||||
async setUserPromptCancelled(): Promise<void> {
|
||||
await this.promptCancelledState.update(
|
||||
(record, userId) => {
|
||||
record ??= {};
|
||||
record[userId] = true;
|
||||
if (userId) {
|
||||
record ??= {};
|
||||
record[userId] = true;
|
||||
}
|
||||
return record;
|
||||
},
|
||||
{
|
||||
combineLatestWith: this.stateProvider.activeUserId$,
|
||||
shouldUpdate: (_, userId) => {
|
||||
if (userId == null) {
|
||||
if (!userId) {
|
||||
throw new Error(
|
||||
"Cannot update biometric prompt cancelled state without an active user",
|
||||
);
|
||||
@@ -271,7 +287,7 @@ export class DefaultBiometricStateService implements BiometricStateService {
|
||||
}
|
||||
|
||||
function encryptedClientKeyHalfToEncString(
|
||||
encryptedKeyHalf: EncryptedString | undefined,
|
||||
): EncString {
|
||||
return encryptedKeyHalf == null ? null : new EncString(encryptedKeyHalf);
|
||||
encryptedKeyHalf: EncryptedString | null | undefined,
|
||||
): EncString | null {
|
||||
return !encryptedKeyHalf ? null : new EncString(encryptedKeyHalf);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { EncryptedString } from "../../../common/src/platform/models/domain/enc-string";
|
||||
import { EncryptedString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||
import {
|
||||
KeyDefinition,
|
||||
BIOMETRIC_SETTINGS_DISK,
|
||||
UserKeyDefinition,
|
||||
} from "../../../common/src/platform/state";
|
||||
import { UserId } from "../../../common/src/types/guid";
|
||||
} from "@bitwarden/common/platform/state";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
|
||||
/**
|
||||
* Indicates whether the user elected to store a biometric key to unlock their vault.
|
||||
|
||||
@@ -1,42 +1,48 @@
|
||||
import { mock } from "jest-mock-extended";
|
||||
import { bufferCount, firstValueFrom, lastValueFrom, of, take, tap } from "rxjs";
|
||||
|
||||
import { PinServiceAbstraction } from "../../auth/src/common/abstractions";
|
||||
import { PinServiceAbstraction } from "@bitwarden/auth/common";
|
||||
import {
|
||||
awaitAsync,
|
||||
makeEncString,
|
||||
makeStaticByteArray,
|
||||
makeSymmetricCryptoKey,
|
||||
} from "../../common/spec";
|
||||
import { FakeAccountService, mockAccountServiceWith } from "../../common/spec/fake-account-service";
|
||||
import { FakeActiveUserState, FakeSingleUserState } from "../../common/spec/fake-state";
|
||||
import { FakeStateProvider } from "../../common/spec/fake-state-provider";
|
||||
import { EncryptedOrganizationKeyData } from "../../common/src/admin-console/models/data/encrypted-organization-key.data";
|
||||
import { KdfConfigService } from "../../common/src/auth/abstractions/kdf-config.service";
|
||||
import { FakeMasterPasswordService } from "../../common/src/auth/services/master-password/fake-master-password.service";
|
||||
import { CryptoFunctionService } from "../../common/src/platform/abstractions/crypto-function.service";
|
||||
import { EncryptService } from "../../common/src/platform/abstractions/encrypt.service";
|
||||
import { KeyGenerationService } from "../../common/src/platform/abstractions/key-generation.service";
|
||||
import { LogService } from "../../common/src/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "../../common/src/platform/abstractions/platform-utils.service";
|
||||
import { StateService } from "../../common/src/platform/abstractions/state.service";
|
||||
import { Encrypted } from "../../common/src/platform/interfaces/encrypted";
|
||||
import { Utils } from "../../common/src/platform/misc/utils";
|
||||
import { EncString, EncryptedString } from "../../common/src/platform/models/domain/enc-string";
|
||||
import { SymmetricCryptoKey } from "../../common/src/platform/models/domain/symmetric-crypto-key";
|
||||
import { USER_ENCRYPTED_ORGANIZATION_KEYS } from "../../common/src/platform/services/key-state/org-keys.state";
|
||||
import { USER_ENCRYPTED_PROVIDER_KEYS } from "../../common/src/platform/services/key-state/provider-keys.state";
|
||||
} from "@bitwarden/common/spec";
|
||||
import {
|
||||
FakeAccountService,
|
||||
mockAccountServiceWith,
|
||||
} from "@bitwarden/common/spec/fake-account-service";
|
||||
import { FakeActiveUserState, FakeSingleUserState } from "@bitwarden/common/spec/fake-state";
|
||||
import { FakeStateProvider } from "@bitwarden/common/spec/fake-state-provider";
|
||||
import { EncryptedOrganizationKeyData } from "@bitwarden/common/src/admin-console/models/data/encrypted-organization-key.data";
|
||||
import { KdfConfigService } from "@bitwarden/common/src/auth/abstractions/kdf-config.service";
|
||||
import { FakeMasterPasswordService } from "@bitwarden/common/src/auth/services/master-password/fake-master-password.service";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/src/platform/abstractions/crypto-function.service";
|
||||
import { EncryptService } from "@bitwarden/common/src/platform/abstractions/encrypt.service";
|
||||
import { KeyGenerationService } from "@bitwarden/common/src/platform/abstractions/key-generation.service";
|
||||
import { LogService } from "@bitwarden/common/src/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/src/platform/abstractions/platform-utils.service";
|
||||
import { StateService } from "@bitwarden/common/src/platform/abstractions/state.service";
|
||||
import { Encrypted } from "@bitwarden/common/src/platform/interfaces/encrypted";
|
||||
import { Utils } from "@bitwarden/common/src/platform/misc/utils";
|
||||
import {
|
||||
EncString,
|
||||
EncryptedString,
|
||||
} from "@bitwarden/common/src/platform/models/domain/enc-string";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/src/platform/models/domain/symmetric-crypto-key";
|
||||
import { USER_ENCRYPTED_ORGANIZATION_KEYS } from "@bitwarden/common/src/platform/services/key-state/org-keys.state";
|
||||
import { USER_ENCRYPTED_PROVIDER_KEYS } from "@bitwarden/common/src/platform/services/key-state/provider-keys.state";
|
||||
import {
|
||||
USER_ENCRYPTED_PRIVATE_KEY,
|
||||
USER_EVER_HAD_USER_KEY,
|
||||
USER_KEY,
|
||||
} from "../../common/src/platform/services/key-state/user-key.state";
|
||||
import { UserKeyDefinition } from "../../common/src/platform/state";
|
||||
import { VAULT_TIMEOUT } from "../../common/src/services/vault-timeout/vault-timeout-settings.state";
|
||||
import { CsprngArray } from "../../common/src/types/csprng";
|
||||
import { OrganizationId, UserId } from "../../common/src/types/guid";
|
||||
import { UserKey, MasterKey } from "../../common/src/types/key";
|
||||
import { VaultTimeoutStringType } from "../../common/src/types/vault-timeout.type";
|
||||
} from "@bitwarden/common/src/platform/services/key-state/user-key.state";
|
||||
import { UserKeyDefinition } from "@bitwarden/common/src/platform/state";
|
||||
import { VAULT_TIMEOUT } from "@bitwarden/common/src/services/vault-timeout/vault-timeout-settings.state";
|
||||
import { CsprngArray } from "@bitwarden/common/src/types/csprng";
|
||||
import { OrganizationId, UserId } from "@bitwarden/common/src/types/guid";
|
||||
import { UserKey, MasterKey } from "@bitwarden/common/src/types/key";
|
||||
import { VaultTimeoutStringType } from "@bitwarden/common/src/types/vault-timeout.type";
|
||||
|
||||
import { UserPrivateKeyDecryptionFailedError } from "./abstractions/key.service";
|
||||
import { DefaultKeyService } from "./key.service";
|
||||
|
||||
@@ -1,58 +1,58 @@
|
||||
import * as bigInt from "big-integer";
|
||||
import {
|
||||
NEVER,
|
||||
Observable,
|
||||
combineLatest,
|
||||
firstValueFrom,
|
||||
forkJoin,
|
||||
map,
|
||||
NEVER,
|
||||
Observable,
|
||||
of,
|
||||
switchMap,
|
||||
} from "rxjs";
|
||||
|
||||
import { PinServiceAbstraction } from "../../auth/src/common/abstractions";
|
||||
import { EncryptedOrganizationKeyData } from "../../common/src/admin-console/models/data/encrypted-organization-key.data";
|
||||
import { BaseEncryptedOrganizationKey } from "../../common/src/admin-console/models/domain/encrypted-organization-key";
|
||||
import { ProfileOrganizationResponse } from "../../common/src/admin-console/models/response/profile-organization.response";
|
||||
import { ProfileProviderOrganizationResponse } from "../../common/src/admin-console/models/response/profile-provider-organization.response";
|
||||
import { ProfileProviderResponse } from "../../common/src/admin-console/models/response/profile-provider.response";
|
||||
import { AccountService } from "../../common/src/auth/abstractions/account.service";
|
||||
import { KdfConfigService } from "../../common/src/auth/abstractions/kdf-config.service";
|
||||
import { InternalMasterPasswordServiceAbstraction } from "../../common/src/auth/abstractions/master-password.service.abstraction";
|
||||
import { KdfConfig } from "../../common/src/auth/models/domain/kdf-config";
|
||||
import { CryptoFunctionService } from "../../common/src/platform/abstractions/crypto-function.service";
|
||||
import { EncryptService } from "../../common/src/platform/abstractions/encrypt.service";
|
||||
import { KeyGenerationService } from "../../common/src/platform/abstractions/key-generation.service";
|
||||
import { LogService } from "../../common/src/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "../../common/src/platform/abstractions/platform-utils.service";
|
||||
import { StateService } from "../../common/src/platform/abstractions/state.service";
|
||||
import { KeySuffixOptions, HashPurpose } from "../../common/src/platform/enums";
|
||||
import { convertValues } from "../../common/src/platform/misc/convert-values";
|
||||
import { Utils } from "../../common/src/platform/misc/utils";
|
||||
import { EFFLongWordList } from "../../common/src/platform/misc/wordlist";
|
||||
import { EncString, EncryptedString } from "../../common/src/platform/models/domain/enc-string";
|
||||
import { SymmetricCryptoKey } from "../../common/src/platform/models/domain/symmetric-crypto-key";
|
||||
import { USER_ENCRYPTED_ORGANIZATION_KEYS } from "../../common/src/platform/services/key-state/org-keys.state";
|
||||
import { USER_ENCRYPTED_PROVIDER_KEYS } from "../../common/src/platform/services/key-state/provider-keys.state";
|
||||
import { PinServiceAbstraction } from "@bitwarden/auth/common";
|
||||
import { EncryptedOrganizationKeyData } from "@bitwarden/common/admin-console/models/data/encrypted-organization-key.data";
|
||||
import { BaseEncryptedOrganizationKey } from "@bitwarden/common/admin-console/models/domain/encrypted-organization-key";
|
||||
import { ProfileOrganizationResponse } from "@bitwarden/common/admin-console/models/response/profile-organization.response";
|
||||
import { ProfileProviderOrganizationResponse } from "@bitwarden/common/admin-console/models/response/profile-provider-organization.response";
|
||||
import { ProfileProviderResponse } from "@bitwarden/common/admin-console/models/response/profile-provider.response";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service";
|
||||
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
|
||||
import { KdfConfig } from "@bitwarden/common/auth/models/domain/kdf-config";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
|
||||
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
|
||||
import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/key-generation.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { HashPurpose, KeySuffixOptions } from "@bitwarden/common/platform/enums";
|
||||
import { convertValues } from "@bitwarden/common/platform/misc/convert-values";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { EFFLongWordList } from "@bitwarden/common/platform/misc/wordlist";
|
||||
import { EncryptedString, EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
import { USER_ENCRYPTED_ORGANIZATION_KEYS } from "@bitwarden/common/platform/services/key-state/org-keys.state";
|
||||
import { USER_ENCRYPTED_PROVIDER_KEYS } from "@bitwarden/common/platform/services/key-state/provider-keys.state";
|
||||
import {
|
||||
USER_ENCRYPTED_PRIVATE_KEY,
|
||||
USER_EVER_HAD_USER_KEY,
|
||||
USER_KEY,
|
||||
} from "../../common/src/platform/services/key-state/user-key.state";
|
||||
import { ActiveUserState, StateProvider } from "../../common/src/platform/state";
|
||||
import { VAULT_TIMEOUT } from "../../common/src/services/vault-timeout/vault-timeout-settings.state";
|
||||
import { CsprngArray } from "../../common/src/types/csprng";
|
||||
import { OrganizationId, ProviderId, UserId } from "../../common/src/types/guid";
|
||||
} from "@bitwarden/common/platform/services/key-state/user-key.state";
|
||||
import { ActiveUserState, StateProvider } from "@bitwarden/common/platform/state";
|
||||
import { VAULT_TIMEOUT } from "@bitwarden/common/services/vault-timeout/vault-timeout-settings.state";
|
||||
import { CsprngArray } from "@bitwarden/common/types/csprng";
|
||||
import { OrganizationId, ProviderId, UserId } from "@bitwarden/common/types/guid";
|
||||
import {
|
||||
OrgKey,
|
||||
UserKey,
|
||||
MasterKey,
|
||||
ProviderKey,
|
||||
CipherKey,
|
||||
MasterKey,
|
||||
OrgKey,
|
||||
ProviderKey,
|
||||
UserKey,
|
||||
UserPrivateKey,
|
||||
UserPublicKey,
|
||||
} from "../../common/src/types/key";
|
||||
import { VaultTimeoutStringType } from "../../common/src/types/vault-timeout.type";
|
||||
} from "@bitwarden/common/types/key";
|
||||
import { VaultTimeoutStringType } from "@bitwarden/common/types/vault-timeout.type";
|
||||
|
||||
import {
|
||||
CipherDecryptionKeys,
|
||||
@@ -85,15 +85,15 @@ export class DefaultKeyService implements KeyServiceAbstraction {
|
||||
this.everHadUserKey$ = this.activeUserEverHadUserKey.state$.pipe(map((x) => x ?? false));
|
||||
|
||||
this.activeUserOrgKeys$ = this.stateProvider.activeUserId$.pipe(
|
||||
switchMap((userId) => (userId != null ? this.orgKeys$(userId) : NEVER)),
|
||||
);
|
||||
switchMap((userId) => (userId ? this.orgKeys$(userId) : NEVER)),
|
||||
) as Observable<Record<OrganizationId, OrgKey>>;
|
||||
}
|
||||
|
||||
async setUserKey(key: UserKey, userId: UserId): Promise<void> {
|
||||
if (key == null) {
|
||||
async setUserKey(key: UserKey | null, userId?: UserId): Promise<void> {
|
||||
if (!key) {
|
||||
throw new Error("No key provided. Lock the user to clear the key");
|
||||
}
|
||||
if (userId == null) {
|
||||
if (!userId) {
|
||||
throw new Error("No userId provided.");
|
||||
}
|
||||
|
||||
@@ -144,12 +144,15 @@ export class DefaultKeyService implements KeyServiceAbstraction {
|
||||
}
|
||||
|
||||
async getUserKey(userId?: UserId): Promise<UserKey> {
|
||||
const userKey = await firstValueFrom(this.stateProvider.getUserState$(USER_KEY, userId));
|
||||
return userKey;
|
||||
return await firstValueFrom(this.stateProvider.getUserState$(USER_KEY, userId));
|
||||
}
|
||||
|
||||
async isLegacyUser(masterKey?: MasterKey, userId?: UserId): Promise<boolean> {
|
||||
userId ??= await firstValueFrom(this.stateProvider.activeUserId$);
|
||||
if (!userId) {
|
||||
throw new Error("User ID is required.");
|
||||
}
|
||||
|
||||
masterKey ??= await firstValueFrom(this.masterPasswordService.masterKey$(userId));
|
||||
|
||||
return await this.validateUserKey(masterKey as unknown as UserKey, userId);
|
||||
@@ -159,6 +162,10 @@ export class DefaultKeyService implements KeyServiceAbstraction {
|
||||
async getUserKeyWithLegacySupport(userId?: UserId): Promise<UserKey> {
|
||||
userId ??= await firstValueFrom(this.stateProvider.activeUserId$);
|
||||
|
||||
if (!userId) {
|
||||
throw new Error("User ID is required.");
|
||||
}
|
||||
|
||||
const userKey = await this.getUserKey(userId);
|
||||
if (userKey) {
|
||||
return userKey;
|
||||
@@ -170,8 +177,15 @@ export class DefaultKeyService implements KeyServiceAbstraction {
|
||||
return masterKey as unknown as UserKey;
|
||||
}
|
||||
|
||||
async getUserKeyFromStorage(keySuffix: KeySuffixOptions, userId?: UserId): Promise<UserKey> {
|
||||
async getUserKeyFromStorage(
|
||||
keySuffix: KeySuffixOptions,
|
||||
userId?: UserId,
|
||||
): Promise<UserKey | null> {
|
||||
userId ??= await firstValueFrom(this.stateProvider.activeUserId$);
|
||||
if (!userId) {
|
||||
throw new Error("User ID is required.");
|
||||
}
|
||||
|
||||
const userKey = await this.getKeyFromStorage(keySuffix, userId);
|
||||
if (userKey) {
|
||||
if (!(await this.validateUserKey(userKey, userId))) {
|
||||
@@ -180,6 +194,8 @@ export class DefaultKeyService implements KeyServiceAbstraction {
|
||||
}
|
||||
return userKey;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
async hasUserKey(userId?: UserId): Promise<boolean> {
|
||||
@@ -206,6 +222,10 @@ export class DefaultKeyService implements KeyServiceAbstraction {
|
||||
async makeUserKey(masterKey: MasterKey): Promise<[UserKey, EncString]> {
|
||||
if (!masterKey) {
|
||||
const userId = await firstValueFrom(this.stateProvider.activeUserId$);
|
||||
if (!userId) {
|
||||
throw new Error("User ID is required.");
|
||||
}
|
||||
|
||||
masterKey = await firstValueFrom(this.masterPasswordService.masterKey$(userId));
|
||||
}
|
||||
if (masterKey == null) {
|
||||
@@ -249,8 +269,12 @@ export class DefaultKeyService implements KeyServiceAbstraction {
|
||||
}
|
||||
}
|
||||
|
||||
async setMasterKeyEncryptedUserKey(userKeyMasterKey: string, userId: UserId): Promise<void> {
|
||||
async setMasterKeyEncryptedUserKey(userKeyMasterKey: string, userId?: UserId): Promise<void> {
|
||||
userId ??= await firstValueFrom(this.stateProvider.activeUserId$);
|
||||
if (!userId) {
|
||||
throw new Error("User ID is required.");
|
||||
}
|
||||
|
||||
await this.masterPasswordService.setMasterKeyEncryptedUserKey(
|
||||
new EncString(userKeyMasterKey),
|
||||
userId,
|
||||
@@ -263,6 +287,10 @@ export class DefaultKeyService implements KeyServiceAbstraction {
|
||||
combineLatest([this.accountService.activeAccount$, this.accountService.accounts$]).pipe(
|
||||
map(([activeAccount, accounts]) => {
|
||||
userId ??= activeAccount?.id;
|
||||
if (!userId) {
|
||||
throw new Error("User ID is required");
|
||||
}
|
||||
|
||||
return [userId, accounts[userId]?.email];
|
||||
}),
|
||||
),
|
||||
@@ -306,6 +334,9 @@ export class DefaultKeyService implements KeyServiceAbstraction {
|
||||
): Promise<string> {
|
||||
if (!key) {
|
||||
const userId = await firstValueFrom(this.stateProvider.activeUserId$);
|
||||
if (!userId) {
|
||||
throw new Error("User ID is required.");
|
||||
}
|
||||
key = await firstValueFrom(this.masterPasswordService.masterKey$(userId));
|
||||
}
|
||||
|
||||
@@ -321,6 +352,10 @@ export class DefaultKeyService implements KeyServiceAbstraction {
|
||||
// TODO: move to MasterPasswordService
|
||||
async compareAndUpdateKeyHash(masterPassword: string, masterKey: MasterKey): Promise<boolean> {
|
||||
const userId = await firstValueFrom(this.stateProvider.activeUserId$);
|
||||
if (!userId) {
|
||||
throw new Error("User ID is required.");
|
||||
}
|
||||
|
||||
const storedPasswordHash = await firstValueFrom(
|
||||
this.masterPasswordService.masterKeyHash$(userId),
|
||||
);
|
||||
@@ -378,10 +413,13 @@ export class DefaultKeyService implements KeyServiceAbstraction {
|
||||
|
||||
async getOrgKey(orgId: OrganizationId): Promise<OrgKey> {
|
||||
const activeUserId = await firstValueFrom(this.stateProvider.activeUserId$);
|
||||
if (activeUserId == null) {
|
||||
if (!activeUserId) {
|
||||
throw new Error("A user must be active to retrieve an org key");
|
||||
}
|
||||
const orgKeys = await firstValueFrom(this.orgKeys$(activeUserId));
|
||||
if (!orgKeys) {
|
||||
throw new Error("No org keys found");
|
||||
}
|
||||
return orgKeys[orgId];
|
||||
}
|
||||
|
||||
@@ -417,13 +455,20 @@ export class DefaultKeyService implements KeyServiceAbstraction {
|
||||
}
|
||||
|
||||
// TODO: Deprecate in favor of observable
|
||||
async getProviderKey(providerId: ProviderId): Promise<ProviderKey> {
|
||||
async getProviderKey(providerId: ProviderId): Promise<ProviderKey | null> {
|
||||
if (providerId == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const activeUserId = await firstValueFrom(this.stateProvider.activeUserId$);
|
||||
if (!activeUserId) {
|
||||
throw new Error("A user must be active to retrieve a provider key");
|
||||
}
|
||||
|
||||
const providerKeys = await firstValueFrom(this.providerKeys$(activeUserId));
|
||||
if (!providerKeys) {
|
||||
throw new Error("No provider keys found");
|
||||
}
|
||||
|
||||
return providerKeys[providerId] ?? null;
|
||||
}
|
||||
@@ -440,7 +485,15 @@ export class DefaultKeyService implements KeyServiceAbstraction {
|
||||
async makeOrgKey<T extends OrgKey | ProviderKey>(userId?: UserId): Promise<[EncString, T]> {
|
||||
const shareKey = await this.keyGenerationService.createKey(512);
|
||||
userId ??= await firstValueFrom(this.stateProvider.activeUserId$);
|
||||
if (!userId) {
|
||||
throw new Error("User ID is required.");
|
||||
}
|
||||
|
||||
const publicKey = await firstValueFrom(this.userPublicKey$(userId));
|
||||
if (!publicKey) {
|
||||
throw new Error("No public key found");
|
||||
}
|
||||
|
||||
const encShareKey = await this.encryptService.rsaEncrypt(shareKey.key, publicKey);
|
||||
return [encShareKey, shareKey as T];
|
||||
}
|
||||
@@ -455,10 +508,10 @@ export class DefaultKeyService implements KeyServiceAbstraction {
|
||||
.update(() => encPrivateKey);
|
||||
}
|
||||
|
||||
async getPrivateKey(): Promise<Uint8Array> {
|
||||
async getPrivateKey(): Promise<Uint8Array | null> {
|
||||
const activeUserId = await firstValueFrom(this.stateProvider.activeUserId$);
|
||||
|
||||
if (activeUserId == null) {
|
||||
if (!activeUserId) {
|
||||
throw new Error("User must be active while attempting to retrieve private key.");
|
||||
}
|
||||
|
||||
@@ -466,15 +519,23 @@ export class DefaultKeyService implements KeyServiceAbstraction {
|
||||
}
|
||||
|
||||
// TODO: Make public key required
|
||||
async getFingerprint(fingerprintMaterial: string, publicKey?: Uint8Array): Promise<string[]> {
|
||||
if (publicKey == null) {
|
||||
async getFingerprint(
|
||||
fingerprintMaterial: string,
|
||||
publicKey?: Uint8Array | null,
|
||||
): Promise<string[]> {
|
||||
if (!publicKey) {
|
||||
const activeUserId = await firstValueFrom(this.stateProvider.activeUserId$);
|
||||
if (!activeUserId) {
|
||||
throw new Error("A user must be active to retrieve a fingerprint");
|
||||
}
|
||||
|
||||
publicKey = await firstValueFrom(this.userPublicKey$(activeUserId));
|
||||
}
|
||||
|
||||
if (publicKey === null) {
|
||||
if (!publicKey) {
|
||||
throw new Error("No public key available.");
|
||||
}
|
||||
|
||||
const keyFingerprint = await this.cryptoFunctionService.hash(publicKey, "sha256");
|
||||
const userFingerprint = await this.cryptoFunctionService.hkdfExpand(
|
||||
keyFingerprint,
|
||||
@@ -500,8 +561,8 @@ export class DefaultKeyService implements KeyServiceAbstraction {
|
||||
* Clears the user's key pair
|
||||
* @param userId The desired user
|
||||
*/
|
||||
private async clearKeyPair(userId: UserId): Promise<void[]> {
|
||||
if (userId == null) {
|
||||
private async clearKeyPair(userId?: UserId): Promise<void> {
|
||||
if (!userId) {
|
||||
// nothing to do
|
||||
return;
|
||||
}
|
||||
@@ -633,7 +694,7 @@ export class DefaultKeyService implements KeyServiceAbstraction {
|
||||
}> {
|
||||
const activeUserId = await firstValueFrom(this.stateProvider.activeUserId$);
|
||||
|
||||
if (activeUserId == null) {
|
||||
if (!activeUserId) {
|
||||
throw new Error("Cannot initilize an account if one is not active.");
|
||||
}
|
||||
|
||||
@@ -650,7 +711,7 @@ export class DefaultKeyService implements KeyServiceAbstraction {
|
||||
await this.setUserKey(userKey, activeUserId);
|
||||
await this.stateProvider
|
||||
.getUser(activeUserId, USER_ENCRYPTED_PRIVATE_KEY)
|
||||
.update(() => privateKey.encryptedString);
|
||||
.update(() => privateKey.encryptedString!);
|
||||
|
||||
return {
|
||||
userKey,
|
||||
@@ -721,6 +782,9 @@ export class DefaultKeyService implements KeyServiceAbstraction {
|
||||
break;
|
||||
}
|
||||
case KeySuffixOptions.Pin: {
|
||||
if (!userId) {
|
||||
throw new Error("User ID is required.");
|
||||
}
|
||||
const userKeyEncryptedPin = await this.pinService.getUserKeyEncryptedPin(userId);
|
||||
shouldStoreKey = !!userKeyEncryptedPin;
|
||||
break;
|
||||
@@ -732,7 +796,7 @@ export class DefaultKeyService implements KeyServiceAbstraction {
|
||||
protected async getKeyFromStorage(
|
||||
keySuffix: KeySuffixOptions,
|
||||
userId?: UserId,
|
||||
): Promise<UserKey> {
|
||||
): Promise<UserKey | null> {
|
||||
if (keySuffix === KeySuffixOptions.Auto) {
|
||||
const userKey = await this.stateService.getUserKeyAutoUnlock({ userId: userId });
|
||||
if (userKey) {
|
||||
@@ -771,7 +835,7 @@ export class DefaultKeyService implements KeyServiceAbstraction {
|
||||
encryptionKey: SymmetricCryptoKey,
|
||||
newSymKey: Uint8Array,
|
||||
): Promise<[T, EncString]> {
|
||||
let protectedSymKey: EncString = null;
|
||||
let protectedSymKey: EncString;
|
||||
if (encryptionKey.key.byteLength === 32) {
|
||||
const stretchedEncryptionKey = await this.keyGenerationService.stretchKey(encryptionKey);
|
||||
protectedSymKey = await this.encryptService.encrypt(newSymKey, stretchedEncryptionKey);
|
||||
@@ -796,7 +860,7 @@ export class DefaultKeyService implements KeyServiceAbstraction {
|
||||
}
|
||||
}
|
||||
|
||||
userKey$(userId: UserId): Observable<UserKey> {
|
||||
userKey$(userId: UserId): Observable<UserKey | null> {
|
||||
return this.stateProvider.getUser(userId, USER_KEY).state$;
|
||||
}
|
||||
|
||||
@@ -823,30 +887,34 @@ export class DefaultKeyService implements KeyServiceAbstraction {
|
||||
);
|
||||
}
|
||||
|
||||
userPublicKey$(userId: UserId) {
|
||||
userPublicKey$(userId: UserId): Observable<UserPublicKey | null> {
|
||||
return this.userPrivateKey$(userId).pipe(
|
||||
switchMap(async (pk) => await this.derivePublicKey(pk)),
|
||||
);
|
||||
}
|
||||
|
||||
private async derivePublicKey(privateKey: UserPrivateKey) {
|
||||
if (privateKey == null) {
|
||||
private async derivePublicKey(privateKey: UserPrivateKey | null) {
|
||||
if (!privateKey) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (await this.cryptoFunctionService.rsaExtractPublicKey(privateKey)) as UserPublicKey;
|
||||
}
|
||||
|
||||
userPrivateKey$(userId: UserId): Observable<UserPrivateKey> {
|
||||
return this.userPrivateKeyHelper$(userId, false).pipe(map((keys) => keys?.userPrivateKey));
|
||||
userPrivateKey$(userId: UserId): Observable<UserPrivateKey | null> {
|
||||
return this.userPrivateKeyHelper$(userId, false).pipe(
|
||||
map((keys) => keys?.userPrivateKey ?? null),
|
||||
);
|
||||
}
|
||||
|
||||
userEncryptedPrivateKey$(userId: UserId): Observable<EncryptedString> {
|
||||
userEncryptedPrivateKey$(userId: UserId): Observable<EncryptedString | null> {
|
||||
return this.stateProvider.getUser(userId, USER_ENCRYPTED_PRIVATE_KEY).state$;
|
||||
}
|
||||
|
||||
userPrivateKeyWithLegacySupport$(userId: UserId): Observable<UserPrivateKey> {
|
||||
return this.userPrivateKeyHelper$(userId, true).pipe(map((keys) => keys?.userPrivateKey));
|
||||
userPrivateKeyWithLegacySupport$(userId: UserId): Observable<UserPrivateKey | null> {
|
||||
return this.userPrivateKeyHelper$(userId, true).pipe(
|
||||
map((keys) => keys?.userPrivateKey ?? null),
|
||||
);
|
||||
}
|
||||
|
||||
private userPrivateKeyHelper$(userId: UserId, legacySupport: boolean) {
|
||||
@@ -872,8 +940,11 @@ export class DefaultKeyService implements KeyServiceAbstraction {
|
||||
);
|
||||
}
|
||||
|
||||
private async decryptPrivateKey(encryptedPrivateKey: EncryptedString, key: SymmetricCryptoKey) {
|
||||
if (encryptedPrivateKey == null) {
|
||||
private async decryptPrivateKey(
|
||||
encryptedPrivateKey: EncryptedString | null,
|
||||
key: SymmetricCryptoKey,
|
||||
) {
|
||||
if (!encryptedPrivateKey) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -903,7 +974,7 @@ export class DefaultKeyService implements KeyServiceAbstraction {
|
||||
private providerKeysHelper$(
|
||||
userId: UserId,
|
||||
userPrivateKey: UserPrivateKey,
|
||||
): Observable<Record<ProviderId, ProviderKey>> {
|
||||
): Observable<Record<ProviderId, ProviderKey> | null> {
|
||||
return this.stateProvider.getUser(userId, USER_ENCRYPTED_PROVIDER_KEYS).state$.pipe(
|
||||
// Convert each value in the record to it's own decryption observable
|
||||
convertValues(async (_, value) => {
|
||||
@@ -930,12 +1001,12 @@ export class DefaultKeyService implements KeyServiceAbstraction {
|
||||
}
|
||||
|
||||
orgKeys$(userId: UserId): Observable<Record<OrganizationId, OrgKey> | null> {
|
||||
return this.cipherDecryptionKeys$(userId, true).pipe(map((keys) => keys?.orgKeys));
|
||||
return this.cipherDecryptionKeys$(userId, true).pipe(map((keys) => keys?.orgKeys ?? null));
|
||||
}
|
||||
|
||||
encryptedOrgKeys$(
|
||||
userId: UserId,
|
||||
): Observable<Record<OrganizationId, EncryptedOrganizationKeyData>> {
|
||||
): Observable<Record<OrganizationId, EncryptedOrganizationKeyData> | null> {
|
||||
return this.stateProvider.getUser(userId, USER_ENCRYPTED_ORGANIZATION_KEYS).state$;
|
||||
}
|
||||
|
||||
@@ -961,16 +1032,24 @@ export class DefaultKeyService implements KeyServiceAbstraction {
|
||||
this.providerKeysHelper$(userId, userPrivateKey),
|
||||
]).pipe(
|
||||
switchMap(async ([encryptedOrgKeys, providerKeys]) => {
|
||||
encryptedOrgKeys = encryptedOrgKeys ?? {};
|
||||
|
||||
const result: Record<OrganizationId, OrgKey> = {};
|
||||
for (const orgId of Object.keys(encryptedOrgKeys ?? {}) as OrganizationId[]) {
|
||||
if (result[orgId] != null) {
|
||||
for (const orgId of Object.keys(encryptedOrgKeys) as OrganizationId[]) {
|
||||
if (result[orgId] !== undefined) {
|
||||
continue;
|
||||
}
|
||||
const encrypted = BaseEncryptedOrganizationKey.fromData(encryptedOrgKeys[orgId]);
|
||||
if (encrypted === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let decrypted: OrgKey;
|
||||
|
||||
if (BaseEncryptedOrganizationKey.isProviderEncrypted(encrypted)) {
|
||||
if (!providerKeys) {
|
||||
throw new Error("No provider keys found.");
|
||||
}
|
||||
decrypted = await encrypted.decrypt(this.encryptService, providerKeys);
|
||||
} else {
|
||||
decrypted = await encrypted.decrypt(this.encryptService, userPrivateKey);
|
||||
|
||||
Reference in New Issue
Block a user