1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-06 00:13:28 +00:00
Files
browser/libs/common/src/key-management/pin/pin-state.service.implementation.ts
Bernd Schoolmann a860f218bd [PM-24128] New Pin service, using PasswordProtectedKeyEnvelope (#15863)
* 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>
2025-10-17 16:30:29 +02:00

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);
}
}