mirror of
https://github.com/bitwarden/browser
synced 2026-01-27 14:53:44 +00:00
Add unlock service
This commit is contained in:
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
@@ -234,3 +234,4 @@ libs/pricing @bitwarden/team-billing-dev
|
||||
.github/workflows/respond.yml @bitwarden/team-ai-sme
|
||||
.github/workflows/review-code.yml @bitwarden/team-ai-sme
|
||||
libs/subscription @bitwarden/team-billing-dev
|
||||
libs/unlock @bitwarden/team-key-management-dev
|
||||
|
||||
@@ -61,6 +61,7 @@ module.exports = {
|
||||
"<rootDir>/libs/vault/jest.config.js",
|
||||
"<rootDir>/libs/auto-confirm/jest.config.js",
|
||||
"<rootDir>/libs/subscription/jest.config.js",
|
||||
"<rootDir>/libs/unlock/jest.config.js",
|
||||
],
|
||||
|
||||
// Workaround for a memory leak that crashes tests in CI:
|
||||
|
||||
@@ -35,7 +35,7 @@ export class MasterPasswordUnlockData {
|
||||
readonly salt: MasterPasswordSalt,
|
||||
readonly kdf: KdfConfig,
|
||||
readonly masterKeyWrappedUserKey: MasterKeyWrappedUserKey,
|
||||
) {}
|
||||
) { }
|
||||
|
||||
static fromSdk(sdkData: SdkMasterPasswordUnlockData): MasterPasswordUnlockData {
|
||||
return new MasterPasswordUnlockData(
|
||||
@@ -45,6 +45,14 @@ export class MasterPasswordUnlockData {
|
||||
);
|
||||
}
|
||||
|
||||
toSdk(): SdkMasterPasswordUnlockData {
|
||||
return {
|
||||
salt: this.salt,
|
||||
kdf: this.kdf.toSdkConfig(),
|
||||
masterKeyWrappedUserKey: this.masterKeyWrappedUserKey,
|
||||
};
|
||||
}
|
||||
|
||||
toJSON(): any {
|
||||
return {
|
||||
salt: this.salt,
|
||||
|
||||
5
libs/unlock/README.md
Normal file
5
libs/unlock/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# unlock
|
||||
|
||||
Owned by: key-management
|
||||
|
||||
Unlock the account of a user
|
||||
3
libs/unlock/eslint.config.mjs
Normal file
3
libs/unlock/eslint.config.mjs
Normal file
@@ -0,0 +1,3 @@
|
||||
import baseConfig from "../../eslint.config.mjs";
|
||||
|
||||
export default [...baseConfig];
|
||||
10
libs/unlock/jest.config.js
Normal file
10
libs/unlock/jest.config.js
Normal file
@@ -0,0 +1,10 @@
|
||||
module.exports = {
|
||||
displayName: "unlock",
|
||||
preset: "../../jest.preset.js",
|
||||
testEnvironment: "node",
|
||||
transform: {
|
||||
"^.+\\.[tj]s$": ["ts-jest", { tsconfig: "<rootDir>/tsconfig.spec.json" }],
|
||||
},
|
||||
moduleFileExtensions: ["ts", "js", "html"],
|
||||
coverageDirectory: "../../coverage/libs/unlock",
|
||||
};
|
||||
11
libs/unlock/package.json
Normal file
11
libs/unlock/package.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "@bitwarden/unlock",
|
||||
"version": "0.0.1",
|
||||
"description": "Unlock the account of a user",
|
||||
"private": true,
|
||||
"type": "commonjs",
|
||||
"main": "index.js",
|
||||
"types": "index.d.ts",
|
||||
"license": "GPL-3.0",
|
||||
"author": "key-management"
|
||||
}
|
||||
34
libs/unlock/project.json
Normal file
34
libs/unlock/project.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "unlock",
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"sourceRoot": "libs/unlock/src",
|
||||
"projectType": "library",
|
||||
"tags": [],
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "@nx/js:tsc",
|
||||
"outputs": ["{options.outputPath}"],
|
||||
"options": {
|
||||
"outputPath": "dist/libs/unlock",
|
||||
"main": "libs/unlock/src/index.ts",
|
||||
"tsConfig": "libs/unlock/tsconfig.lib.json",
|
||||
"assets": ["libs/unlock/*.md"],
|
||||
"rootDir": "libs/unlock/src"
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/eslint:lint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
"options": {
|
||||
"lintFilePatterns": ["libs/unlock/**/*.ts"]
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"executor": "@nx/jest:jest",
|
||||
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
|
||||
"options": {
|
||||
"jestConfig": "libs/unlock/jest.config.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1
libs/unlock/src/abstractions/index.ts
Normal file
1
libs/unlock/src/abstractions/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { UnlockService } from "./unlock.service";
|
||||
19
libs/unlock/src/abstractions/unlock.service.ts
Normal file
19
libs/unlock/src/abstractions/unlock.service.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
|
||||
import { PinLockType } from "@bitwarden/common/key-management/pin/pin-lock-type";
|
||||
|
||||
/**
|
||||
* Service for unlocking a user's account with various methods.
|
||||
*/
|
||||
export abstract class UnlockService {
|
||||
/**
|
||||
* Unlocks the user's account using their PIN.
|
||||
*
|
||||
* @param userId - The user's id
|
||||
* @param pin - The user's PIN
|
||||
* @param pinLockType - The type of PIN lock (PERSISTENT or EPHEMERAL)
|
||||
* @throws If the SDK is not available
|
||||
* @throws If the PIN is invalid or decryption fails
|
||||
*/
|
||||
abstract unlockWithPin(userId: UserId, pin: string, pinLockType: PinLockType): Promise<void>;
|
||||
}
|
||||
5
libs/unlock/src/index.ts
Normal file
5
libs/unlock/src/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export { UnlockService } from "./abstractions/unlock.service";
|
||||
export { DefaultUnlockService } from "./unlock.service";
|
||||
|
||||
// Re-export abstractions
|
||||
export * from "./abstractions";
|
||||
201
libs/unlock/src/unlock.service.ts
Normal file
201
libs/unlock/src/unlock.service.ts
Normal file
@@ -0,0 +1,201 @@
|
||||
import { first, firstValueFrom, map } from "rxjs";
|
||||
|
||||
import { AccountCryptographicStateService } from "@bitwarden/common/key-management/account-cryptography/account-cryptographic-state.service";
|
||||
import { PinLockType } from "@bitwarden/common/key-management/pin/pin-lock-type";
|
||||
import { PinStateServiceAbstraction } from "@bitwarden/common/key-management/pin/pin-state.service.abstraction";
|
||||
import { RegisterSdkService } from "@bitwarden/common/platform/abstractions/sdk/register-sdk.service";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
|
||||
import { UnlockService } from "./abstractions/unlock.service";
|
||||
import { KdfConfig, KdfConfigService } from "@bitwarden/key-management";
|
||||
import { EncString, Kdf, MasterPasswordUnlockData, PasswordProtectedKeyEnvelope, UnsignedSharedKey, WrappedAccountCryptographicState } from "@bitwarden/sdk-internal";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
|
||||
import {
|
||||
asUuid,
|
||||
} from "@bitwarden/common/platform/abstractions/sdk/sdk.service";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { DeviceKey } from "@bitwarden/common/types/key";
|
||||
|
||||
export class DefaultUnlockService implements UnlockService {
|
||||
constructor(
|
||||
private registerSdkService: RegisterSdkService,
|
||||
private accountCryptographicStateService: AccountCryptographicStateService,
|
||||
private pinStateService: PinStateServiceAbstraction,
|
||||
private kdfService: KdfConfigService,
|
||||
private accountService: AccountService,
|
||||
private masterPasswordService: InternalMasterPasswordServiceAbstraction,
|
||||
private apiService: ApiService,
|
||||
) { }
|
||||
|
||||
private async getAccountCryptographicState(userId: UserId): Promise<WrappedAccountCryptographicState> {
|
||||
return firstValueFrom(
|
||||
this.accountCryptographicStateService.accountCryptographicState$(userId),
|
||||
);
|
||||
}
|
||||
|
||||
private async getKdfParams(userId: UserId): Promise<Kdf> {
|
||||
return firstValueFrom(
|
||||
this.kdfService.getKdfConfig$(userId).pipe(
|
||||
map((config: KdfConfig) => {
|
||||
return config.toSdkConfig();
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
private async getEmail(userId: UserId): Promise<string> {
|
||||
return await firstValueFrom(
|
||||
this.accountService.activeAccount$.pipe(map((a) => a?.email)),
|
||||
);
|
||||
}
|
||||
|
||||
private async getPinProtectedUserKeyEnvelope(userId: UserId): Promise<PasswordProtectedKeyEnvelope | null> {
|
||||
const pinLockType = await this.pinStateService.getPinLockType(userId);
|
||||
return this.pinStateService.getPinProtectedUserKeyEnvelope(
|
||||
userId,
|
||||
pinLockType,
|
||||
);
|
||||
}
|
||||
|
||||
private async getMasterPasswordUnlockData(userId: UserId): Promise<MasterPasswordUnlockData | null> {
|
||||
const unlockData = await firstValueFrom(this.masterPasswordService.masterPasswordUnlockData$(userId));
|
||||
return unlockData.toSdk();
|
||||
}
|
||||
|
||||
async unlockWithDeviceKey(userId: UserId,
|
||||
encryptedDevicePrivateKey: EncString,
|
||||
encryptedUserKey: UnsignedSharedKey,
|
||||
deviceKey: DeviceKey,
|
||||
): Promise<void> {
|
||||
await firstValueFrom(
|
||||
this.registerSdkService.registerClient$(userId).pipe(
|
||||
map(async (sdk) => {
|
||||
if (!sdk) {
|
||||
throw new Error("SDK not available");
|
||||
}
|
||||
using ref = sdk.take();
|
||||
return ref.value.crypto().initialize_user_crypto({
|
||||
userId: asUuid(userId),
|
||||
kdfParams: await this.getKdfParams(userId),
|
||||
email: await this.getEmail(userId),
|
||||
accountCryptographicState: await this.getAccountCryptographicState(userId),
|
||||
method: {
|
||||
deviceKey: {
|
||||
device_key: deviceKey.toBase64(),
|
||||
protected_device_private_key: encryptedDevicePrivateKey,
|
||||
device_protected_user_key: encryptedUserKey,
|
||||
},
|
||||
},
|
||||
});
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
async unlockWithAuthRequest(userId: UserId, privateKey: string, protectedUserKey: UnsignedSharedKey): Promise<void> {
|
||||
await firstValueFrom(
|
||||
this.registerSdkService.registerClient$(userId).pipe(
|
||||
map(async (sdk) => {
|
||||
if (!sdk) {
|
||||
throw new Error("SDK not available");
|
||||
}
|
||||
using ref = sdk.take();
|
||||
return ref.value.crypto().initialize_user_crypto({
|
||||
userId: asUuid(userId),
|
||||
kdfParams: await this.getKdfParams(userId),
|
||||
email: await this.getEmail(userId),
|
||||
accountCryptographicState: await this.getAccountCryptographicState(userId),
|
||||
method: {
|
||||
authRequest: {
|
||||
request_private_key: privateKey,
|
||||
method: {
|
||||
userKey: {
|
||||
protected_user_key: protectedUserKey,
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
async unlockWithKeyConnector(userId: UserId, keyConnectorUrl: string): Promise<void> {
|
||||
const keyConnectorKey = (await this.apiService.getMasterKeyFromKeyConnector(keyConnectorUrl)).key;
|
||||
const keyConnectorKeyWrappedUserKey = await this.masterPasswordService.getMasterKeyEncryptedUserKey(userId);
|
||||
await firstValueFrom(
|
||||
this.registerSdkService.registerClient$(userId).pipe(
|
||||
map(async (sdk) => {
|
||||
if (!sdk) {
|
||||
throw new Error("SDK not available");
|
||||
}
|
||||
using ref = sdk.take();
|
||||
return ref.value.crypto().initialize_user_crypto({
|
||||
userId: asUuid(userId),
|
||||
kdfParams: await this.getKdfParams(userId),
|
||||
email: await this.getEmail(userId),
|
||||
accountCryptographicState: await this.getAccountCryptographicState(userId),
|
||||
method: {
|
||||
keyConnector: {
|
||||
master_key: keyConnectorKey,
|
||||
user_key: keyConnectorKeyWrappedUserKey.toSdk(),
|
||||
},
|
||||
},
|
||||
});
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
async unlockWithPin(userId: UserId, pin: string): Promise<void> {
|
||||
await firstValueFrom(
|
||||
this.registerSdkService.registerClient$(userId).pipe(
|
||||
map(async (sdk) => {
|
||||
if (!sdk) {
|
||||
throw new Error("SDK not available");
|
||||
}
|
||||
using ref = sdk.take();
|
||||
return ref.value.crypto().initialize_user_crypto({
|
||||
userId: asUuid(userId),
|
||||
kdfParams: await this.getKdfParams(userId),
|
||||
email: await this.getEmail(userId),
|
||||
accountCryptographicState: await this.getAccountCryptographicState(userId),
|
||||
method: {
|
||||
pinEnvelope: {
|
||||
pin: pin,
|
||||
pin_protected_user_key_envelope: await this.getPinProtectedUserKeyEnvelope(userId),
|
||||
},
|
||||
},
|
||||
});
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
async unlockWithMasterPassword(userId: UserId, masterPassword: string): Promise<void> {
|
||||
await firstValueFrom(
|
||||
this.registerSdkService.registerClient$(userId).pipe(
|
||||
map(async (sdk) => {
|
||||
if (!sdk) {
|
||||
throw new Error("SDK not available");
|
||||
}
|
||||
using ref = sdk.take();
|
||||
return ref.value.crypto().initialize_user_crypto({
|
||||
userId: asUuid(userId),
|
||||
kdfParams: await this.getKdfParams(userId),
|
||||
email: await this.getEmail(userId),
|
||||
accountCryptographicState: await this.getAccountCryptographicState(userId),
|
||||
method: {
|
||||
masterPasswordUnlock: {
|
||||
password: masterPassword,
|
||||
master_password_unlock: await this.getMasterPasswordUnlockData(userId),
|
||||
},
|
||||
},
|
||||
});
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
8
libs/unlock/src/unlock.spec.ts
Normal file
8
libs/unlock/src/unlock.spec.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import * as lib from "./index";
|
||||
|
||||
describe("unlock", () => {
|
||||
// This test will fail until something is exported from index.ts
|
||||
it("should work", () => {
|
||||
expect(lib).toBeDefined();
|
||||
});
|
||||
});
|
||||
6
libs/unlock/tsconfig.eslint.json
Normal file
6
libs/unlock/tsconfig.eslint.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"files": [],
|
||||
"include": ["src/**/*.ts", "src/**/*.js"],
|
||||
"exclude": ["**/build", "**/dist"]
|
||||
}
|
||||
13
libs/unlock/tsconfig.json
Normal file
13
libs/unlock/tsconfig.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.spec.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
10
libs/unlock/tsconfig.lib.json
Normal file
10
libs/unlock/tsconfig.lib.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"declaration": true,
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": ["jest.config.js", "src/**/*.spec.ts"]
|
||||
}
|
||||
10
libs/unlock/tsconfig.spec.json
Normal file
10
libs/unlock/tsconfig.spec.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node10",
|
||||
"types": ["jest", "node"]
|
||||
},
|
||||
"include": ["jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts", "src/**/*.d.ts"]
|
||||
}
|
||||
9
package-lock.json
generated
9
package-lock.json
generated
@@ -683,6 +683,11 @@
|
||||
"version": "0.0.0",
|
||||
"license": "GPL-3.0"
|
||||
},
|
||||
"libs/unlock": {
|
||||
"name": "@bitwarden/unlock",
|
||||
"version": "0.0.1",
|
||||
"license": "GPL-3.0"
|
||||
},
|
||||
"libs/user-core": {
|
||||
"name": "@bitwarden/user-core",
|
||||
"version": "0.0.0",
|
||||
@@ -5143,6 +5148,10 @@
|
||||
"resolved": "libs/ui/common",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@bitwarden/unlock": {
|
||||
"resolved": "libs/unlock",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@bitwarden/user-core": {
|
||||
"resolved": "libs/user-core",
|
||||
"link": true
|
||||
|
||||
@@ -30,8 +30,8 @@
|
||||
"@bitwarden/browser/*": ["./apps/browser/src/*"],
|
||||
"@bitwarden/cli/*": ["./apps/cli/src/*"],
|
||||
"@bitwarden/client-type": ["./libs/client-type/src/index.ts"],
|
||||
"@bitwarden/common/spec": ["./libs/common/spec"],
|
||||
"@bitwarden/common/*": ["./libs/common/src/*"],
|
||||
"@bitwarden/common/spec": ["./libs/common/spec"],
|
||||
"@bitwarden/components": ["./libs/components/src"],
|
||||
"@bitwarden/core-test-utils": ["./libs/core-test-utils/src/index.ts"],
|
||||
"@bitwarden/dirt-card": ["./libs/dirt/card/src"],
|
||||
@@ -62,6 +62,7 @@
|
||||
"@bitwarden/subscription": ["./libs/subscription/src/index.ts"],
|
||||
"@bitwarden/ui-common": ["./libs/ui/common/src"],
|
||||
"@bitwarden/ui-common/setup-jest": ["./libs/ui/common/src/setup-jest"],
|
||||
"@bitwarden/unlock": ["./libs/unlock/src/index.ts"],
|
||||
"@bitwarden/user-core": ["./libs/user-core/src/index.ts"],
|
||||
"@bitwarden/vault": ["./libs/vault/src"],
|
||||
"@bitwarden/vault-export-core": ["./libs/tools/export/vault-export/vault-export-core/src"],
|
||||
|
||||
Reference in New Issue
Block a user