1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-10 21:33:27 +00:00

feat(new SendPasswordService): [Auth/PM-23700] Create KM SendPasswordService (#15570)

* PM-23700 - SendPasswordService - create and test

* PM-23700 - SendPassword Service comment clean up

* PM-23700 - Use barrel file exports and register default service.

* PM-23700 - DefaultSendPasswordService - work with Bernd to deliver better service
This commit is contained in:
Jared Snider
2025-07-11 13:05:31 -04:00
committed by GitHub
parent 3c6f763233
commit c9f642e491
9 changed files with 126 additions and 0 deletions

View File

@@ -169,6 +169,10 @@ import {
MasterPasswordServiceAbstraction,
} from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
import { MasterPasswordService } from "@bitwarden/common/key-management/master-password/services/master-password.service";
import {
SendPasswordService,
DefaultSendPasswordService,
} from "@bitwarden/common/key-management/sends";
import {
DefaultVaultTimeoutService,
DefaultVaultTimeoutSettingsService,
@@ -1502,6 +1506,11 @@ const safeProviders: SafeProvider[] = [
useClass: DefaultCipherAuthorizationService,
deps: [CollectionService, OrganizationServiceAbstraction, AccountServiceAbstraction],
}),
safeProvider({
provide: SendPasswordService,
useClass: DefaultSendPasswordService,
deps: [CryptoFunctionServiceAbstraction],
}),
safeProvider({
provide: LoginApprovalComponentServiceAbstraction,
useClass: DefaultLoginApprovalComponentService,

View File

@@ -0,0 +1 @@
export * from "./send-password.service";

View File

@@ -0,0 +1,17 @@
import { SendHashedPassword, SendPasswordKeyMaterial } from "../types/send-hashed-password.type";
/**
* Service for managing passwords for sends.
*/
export abstract class SendPasswordService {
/**
* Hashes a raw send password using the provided key material
* @param password - the raw password to hash
* @param keyMaterial - the key material
* @returns a promise that resolves to the hashed password as a SendHashedPassword
*/
abstract hashPassword(
password: string,
keyMaterial: SendPasswordKeyMaterial,
): Promise<SendHashedPassword>;
}

View File

@@ -0,0 +1,3 @@
export * from "./abstractions";
export * from "./services";
export * from "./types";

View File

@@ -0,0 +1,63 @@
import { mock, MockProxy } from "jest-mock-extended";
import { SEND_KDF_ITERATIONS } from "../../../tools/send/send-kdf";
import { CryptoFunctionService } from "../../crypto/abstractions/crypto-function.service";
import { SendPasswordKeyMaterial } from "../types";
import { DefaultSendPasswordService } from "./default-send-password.service";
describe("DefaultSendPasswordService", () => {
let sendPasswordService: DefaultSendPasswordService;
let mockCryptoFunctionService: MockProxy<CryptoFunctionService>;
beforeEach(() => {
mockCryptoFunctionService = mock<CryptoFunctionService>();
sendPasswordService = new DefaultSendPasswordService(mockCryptoFunctionService);
});
it("instantiates", () => {
expect(sendPasswordService).not.toBeFalsy();
});
it("hashes a password with the provided key material", async () => {
// Arrange
const password = "testPassword";
const keyMaterial = new Uint8Array([1, 2, 3, 4, 5]) as SendPasswordKeyMaterial;
const expectedHash = new Uint8Array([1, 2, 3, 4, 5]); // Mocked hash output
mockCryptoFunctionService.pbkdf2.mockResolvedValue(expectedHash);
// Act
const result = await sendPasswordService.hashPassword(password, keyMaterial);
// Assert
expect(mockCryptoFunctionService.pbkdf2).toHaveBeenCalledWith(
password,
keyMaterial,
"sha256",
SEND_KDF_ITERATIONS,
);
expect(result).toEqual(expectedHash);
});
it("throws an error if a password isn't provided", async () => {
// Arrange
const keyMaterial = new Uint8Array([1, 2, 3, 4, 5]) as SendPasswordKeyMaterial;
const expectedError = new Error("Password and key material are required.");
// Act & Assert
await expect(sendPasswordService.hashPassword("", keyMaterial)).rejects.toThrow(expectedError);
});
it("throws an error if key material isn't provided", async () => {
// Arrange
const password = "testPassword";
const expectedError = new Error("Password and key material are required.");
// Act & Assert
await expect(
sendPasswordService.hashPassword(password, undefined as unknown as SendPasswordKeyMaterial),
).rejects.toThrow(expectedError);
});
});

View File

@@ -0,0 +1,27 @@
import { SEND_KDF_ITERATIONS } from "../../../tools/send/send-kdf";
import { CryptoFunctionService } from "../../crypto/abstractions/crypto-function.service";
import { SendPasswordService } from "../abstractions/send-password.service";
import { SendHashedPassword, SendPasswordKeyMaterial } from "../types/send-hashed-password.type";
export class DefaultSendPasswordService implements SendPasswordService {
constructor(private cryptoFunctionService: CryptoFunctionService) {}
async hashPassword(
password: string,
keyMaterial: SendPasswordKeyMaterial,
): Promise<SendHashedPassword> {
if (!password || !keyMaterial) {
throw new Error("Password and key material are required.");
}
// Derive a password hash using the key material.
const passwordHash = await this.cryptoFunctionService.pbkdf2(
password,
keyMaterial,
"sha256",
SEND_KDF_ITERATIONS,
);
return passwordHash as SendHashedPassword;
}
}

View File

@@ -0,0 +1 @@
export * from "./default-send-password.service";

View File

@@ -0,0 +1 @@
export * from "./send-hashed-password.type";

View File

@@ -0,0 +1,4 @@
import { Opaque } from "type-fest";
export type SendHashedPassword = Opaque<Uint8Array, "SendHashedPassword">;
export type SendPasswordKeyMaterial = Opaque<Uint8Array, "SendPasswordKeyMaterial">;