mirror of
https://github.com/bitwarden/browser
synced 2025-12-06 00:13:28 +00:00
* fix: broken SDK interface * Fix all compile errors related to uuids * Update usages of sdk to type-safe SDK type * Update sdk version * Update to "toSdk" * Move pin service to km ownership * Run format * Eslint * Fix tsconfig * Fix imports and test * Clean up imports * Pin tmp * Initial version of updated pin service * Add tests * Rename function * Clean up logging * Fix imports * Fix cli build * Fix browser desktop * Fix tests * Attempt to fix * Fix build * Fix tests * Fix browser build * Add missing empty line * Fix linting * Remove non-required change * Missing newline * Re-add comment * Undo change to file * Fix missing empty line * Cleanup * Cleanup * Cleanup * Cleanup * Switch to replaysubject * Add comments * Fix tests * Run prettier * Undo change * Fix browser * Fix circular dependency on browser * Add missing clear ephemeral pin * Address feedback * Update docs * Simplify sdk usage in pin service * Replace with mock sdk * Update sdk * Initialize pin service via unlock instead of listening to keyservice * Cleanup * Fix test * Prevent race condition with userkey not being set * Filter null userkeys * [PM-24124] Pin State Service (#16641) * add pin-state.service * add remaining tests * improve description for clearEphemeralPinState * rename getUserKeyWrappedPin$ to userKeyWrappedPin$ * drop temp variable in setPinState * add new test and remove copied one * Fix dep cycle * Fix tests and remaining build issues * Fix cli build * Add comments about functions not being public API --------- Co-authored-by: Andreas Coroiu <andreas.coroiu@gmail.com> Co-authored-by: Hinton <hinton@users.noreply.github.com> Co-authored-by: Jake Fink <jfink@bitwarden.com>
130 lines
4.5 KiB
TypeScript
130 lines
4.5 KiB
TypeScript
import { firstValueFrom, map, Observable } from "rxjs";
|
|
|
|
import { PasswordProtectedKeyEnvelope } from "@bitwarden/sdk-internal";
|
|
import { StateProvider } from "@bitwarden/state";
|
|
import { UserId } from "@bitwarden/user-core";
|
|
|
|
import { assertNonNullish } from "../../auth/utils";
|
|
import { EncryptedString, EncString } from "../crypto/models/enc-string";
|
|
|
|
import { PinLockType } from "./pin-lock-type";
|
|
import { PinStateServiceAbstraction } from "./pin-state.service.abstraction";
|
|
import {
|
|
PIN_PROTECTED_USER_KEY_ENVELOPE_PERSISTENT,
|
|
PIN_PROTECTED_USER_KEY_ENVELOPE_EPHEMERAL,
|
|
USER_KEY_ENCRYPTED_PIN,
|
|
PIN_KEY_ENCRYPTED_USER_KEY_PERSISTENT,
|
|
} from "./pin.state";
|
|
|
|
export class PinStateService implements PinStateServiceAbstraction {
|
|
constructor(private stateProvider: StateProvider) {}
|
|
|
|
userKeyEncryptedPin$(userId: UserId): Observable<EncString | null> {
|
|
assertNonNullish(userId, "userId");
|
|
|
|
return this.stateProvider
|
|
.getUserState$(USER_KEY_ENCRYPTED_PIN, userId)
|
|
.pipe(map((value) => (value ? new EncString(value) : null)));
|
|
}
|
|
|
|
async isPinSet(userId: UserId): Promise<boolean> {
|
|
assertNonNullish(userId, "userId");
|
|
return (await this.getPinLockType(userId)) !== "DISABLED";
|
|
}
|
|
|
|
async getPinLockType(userId: UserId): Promise<PinLockType> {
|
|
assertNonNullish(userId, "userId");
|
|
|
|
const isPersistentPinSet =
|
|
(await this.getPinProtectedUserKeyEnvelope(userId, "PERSISTENT")) != null ||
|
|
// Deprecated
|
|
(await this.getLegacyPinKeyEncryptedUserKeyPersistent(userId)) != null;
|
|
const isPinSet =
|
|
(await firstValueFrom(this.stateProvider.getUserState$(USER_KEY_ENCRYPTED_PIN, userId))) !=
|
|
null;
|
|
|
|
if (isPersistentPinSet) {
|
|
return "PERSISTENT";
|
|
} else if (isPinSet) {
|
|
return "EPHEMERAL";
|
|
} else {
|
|
return "DISABLED";
|
|
}
|
|
}
|
|
|
|
async getPinProtectedUserKeyEnvelope(
|
|
userId: UserId,
|
|
pinLockType: PinLockType,
|
|
): Promise<PasswordProtectedKeyEnvelope | null> {
|
|
assertNonNullish(userId, "userId");
|
|
|
|
if (pinLockType === "EPHEMERAL") {
|
|
return await firstValueFrom(
|
|
this.stateProvider.getUserState$(PIN_PROTECTED_USER_KEY_ENVELOPE_EPHEMERAL, userId),
|
|
);
|
|
} else if (pinLockType === "PERSISTENT") {
|
|
return await firstValueFrom(
|
|
this.stateProvider.getUserState$(PIN_PROTECTED_USER_KEY_ENVELOPE_PERSISTENT, userId),
|
|
);
|
|
} else {
|
|
throw new Error(`Unsupported PinLockType: ${pinLockType}`);
|
|
}
|
|
}
|
|
|
|
async getLegacyPinKeyEncryptedUserKeyPersistent(userId: UserId): Promise<EncString | null> {
|
|
assertNonNullish(userId, "userId");
|
|
|
|
return await firstValueFrom(
|
|
this.stateProvider
|
|
.getUserState$(PIN_KEY_ENCRYPTED_USER_KEY_PERSISTENT, userId)
|
|
.pipe(map((value) => (value ? new EncString(value) : null))),
|
|
);
|
|
}
|
|
|
|
async setPinState(
|
|
userId: UserId,
|
|
pinProtectedUserKeyEnvelope: PasswordProtectedKeyEnvelope,
|
|
userKeyEncryptedPin: EncryptedString,
|
|
pinLockType: PinLockType,
|
|
): Promise<void> {
|
|
assertNonNullish(userId, "userId");
|
|
assertNonNullish(pinProtectedUserKeyEnvelope, "pinProtectedUserKeyEnvelope");
|
|
assertNonNullish(pinLockType, "pinLockType");
|
|
|
|
if (pinLockType === "EPHEMERAL") {
|
|
await this.stateProvider.setUserState(
|
|
PIN_PROTECTED_USER_KEY_ENVELOPE_EPHEMERAL,
|
|
pinProtectedUserKeyEnvelope,
|
|
userId,
|
|
);
|
|
} else if (pinLockType === "PERSISTENT") {
|
|
await this.stateProvider.setUserState(
|
|
PIN_PROTECTED_USER_KEY_ENVELOPE_PERSISTENT,
|
|
pinProtectedUserKeyEnvelope,
|
|
userId,
|
|
);
|
|
} else {
|
|
throw new Error(`Cannot set up PIN with pin lock type ${pinLockType}`);
|
|
}
|
|
|
|
await this.stateProvider.setUserState(USER_KEY_ENCRYPTED_PIN, userKeyEncryptedPin, userId);
|
|
}
|
|
|
|
async clearPinState(userId: UserId): Promise<void> {
|
|
assertNonNullish(userId, "userId");
|
|
|
|
await this.stateProvider.setUserState(USER_KEY_ENCRYPTED_PIN, null, userId);
|
|
await this.stateProvider.setUserState(PIN_PROTECTED_USER_KEY_ENVELOPE_EPHEMERAL, null, userId);
|
|
await this.stateProvider.setUserState(PIN_PROTECTED_USER_KEY_ENVELOPE_PERSISTENT, null, userId);
|
|
|
|
// Note: This can be deleted after sufficiently many PINs are migrated and the state is removed.
|
|
await this.stateProvider.setUserState(PIN_KEY_ENCRYPTED_USER_KEY_PERSISTENT, null, userId);
|
|
}
|
|
|
|
async clearEphemeralPinState(userId: UserId): Promise<void> {
|
|
assertNonNullish(userId, "userId");
|
|
|
|
await this.stateProvider.setUserState(PIN_PROTECTED_USER_KEY_ENVELOPE_EPHEMERAL, null, userId);
|
|
}
|
|
}
|