mirror of
https://github.com/bitwarden/browser
synced 2026-02-08 20:50:28 +00:00
[PM-27086] create setInitialPasswordV2()
This commit is contained in:
@@ -19,13 +19,20 @@ import { AccountCryptographicStateService } from "@bitwarden/common/key-manageme
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
|
||||
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
|
||||
import { MasterPasswordSalt } from "@bitwarden/common/key-management/master-password/types/master-password.types";
|
||||
import {
|
||||
MasterPasswordSalt,
|
||||
MasterPasswordAuthenticationData,
|
||||
MasterPasswordAuthenticationHash,
|
||||
MasterPasswordUnlockData,
|
||||
} from "@bitwarden/common/key-management/master-password/types/master-password.types";
|
||||
import { KeysRequest } from "@bitwarden/common/models/request/keys.request";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { MasterKey, UserKey } from "@bitwarden/common/types/key";
|
||||
import { KdfConfigService, KeyService, KdfConfig } from "@bitwarden/key-management";
|
||||
import { PureCrypto } from "@bitwarden/sdk-internal";
|
||||
|
||||
import {
|
||||
SetInitialPasswordService,
|
||||
@@ -49,6 +56,9 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi
|
||||
protected accountCryptographicStateService: AccountCryptographicStateService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @deprecated To be removed in PM-28143
|
||||
*/
|
||||
async setInitialPassword(
|
||||
credentials: SetInitialPasswordCredentials,
|
||||
userType: SetInitialPasswordUserType,
|
||||
@@ -199,6 +209,145 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi
|
||||
}
|
||||
}
|
||||
|
||||
async setInitialPasswordV2(
|
||||
credentials: SetInitialPasswordCredentials,
|
||||
userType: SetInitialPasswordUserType,
|
||||
userId: UserId,
|
||||
): Promise<void> {
|
||||
const {
|
||||
newPassword,
|
||||
newPasswordHint,
|
||||
kdfConfig,
|
||||
salt,
|
||||
orgSsoIdentifier,
|
||||
orgId,
|
||||
resetPasswordAutoEnroll,
|
||||
} = credentials;
|
||||
|
||||
for (const [key, value] of Object.entries(credentials)) {
|
||||
if (value == null) {
|
||||
throw new Error(`${key} not found. Could not set password.`);
|
||||
}
|
||||
}
|
||||
if (userId == null) {
|
||||
throw new Error("userId not found. Could not set password.");
|
||||
}
|
||||
if (userType == null) {
|
||||
throw new Error("userType not found. Could not set password.");
|
||||
}
|
||||
|
||||
let userKey = await firstValueFrom(this.keyService.userKey$(userId));
|
||||
|
||||
if (userKey == null) {
|
||||
userKey = new SymmetricCryptoKey(PureCrypto.make_user_key_aes256_cbc_hmac()) as UserKey;
|
||||
}
|
||||
|
||||
const authenticationData: MasterPasswordAuthenticationData =
|
||||
await this.masterPasswordService.makeMasterPasswordAuthenticationData(
|
||||
newPassword,
|
||||
kdfConfig,
|
||||
salt,
|
||||
);
|
||||
|
||||
const unlockData: MasterPasswordUnlockData =
|
||||
await this.masterPasswordService.makeMasterPasswordUnlockData(
|
||||
newPassword,
|
||||
kdfConfig,
|
||||
salt,
|
||||
userKey,
|
||||
);
|
||||
|
||||
if (unlockData.masterKeyWrappedUserKey == null) {
|
||||
throw new Error("masterKeyEncryptedUserKey not found. Could not set password.");
|
||||
}
|
||||
|
||||
let keyPair: [string, EncString] | null = null;
|
||||
let keysRequest: KeysRequest | null = null;
|
||||
|
||||
if (userType === SetInitialPasswordUserType.JIT_PROVISIONED_MP_ORG_USER) {
|
||||
/**
|
||||
* A user being JIT provisioned into a MP encryption org does not yet have a user
|
||||
* asymmetric key pair, so we create it for them here.
|
||||
*
|
||||
* Sidenote:
|
||||
* In the case of a TDE user whose permissions require that they have a MP - that user
|
||||
* will already have a user asymmetric key pair by this point, so we skip this if-block
|
||||
* so that we don't create a new key pair for them.
|
||||
*/
|
||||
|
||||
// Extra safety check (see description on https://github.com/bitwarden/clients/pull/10180):
|
||||
// In case we have have a local private key and are not sure whether it has been posted to the server,
|
||||
// we post the local private key instead of generating a new one
|
||||
const existingUserPrivateKey = (await firstValueFrom(
|
||||
this.keyService.userPrivateKey$(userId),
|
||||
)) as Uint8Array;
|
||||
|
||||
const existingUserPublicKey = await firstValueFrom(this.keyService.userPublicKey$(userId));
|
||||
|
||||
if (existingUserPrivateKey != null && existingUserPublicKey != null) {
|
||||
const existingUserPublicKeyB64 = Utils.fromBufferToB64(existingUserPublicKey);
|
||||
|
||||
// Existing key pair
|
||||
keyPair = [
|
||||
existingUserPublicKeyB64,
|
||||
await this.encryptService.wrapDecapsulationKey(existingUserPrivateKey, userKey),
|
||||
];
|
||||
} else {
|
||||
// New key pair
|
||||
keyPair = await this.keyService.makeKeyPair(userKey);
|
||||
}
|
||||
|
||||
if (keyPair == null) {
|
||||
throw new Error("keyPair not found. Could not set password.");
|
||||
}
|
||||
if (!keyPair[1].encryptedString) {
|
||||
throw new Error("encrypted private key not found. Could not set password.");
|
||||
}
|
||||
|
||||
keysRequest = new KeysRequest(keyPair[0], keyPair[1].encryptedString);
|
||||
}
|
||||
|
||||
const request = SetPasswordRequest.newConstructor(
|
||||
authenticationData,
|
||||
unlockData,
|
||||
newPasswordHint,
|
||||
orgSsoIdentifier,
|
||||
keysRequest,
|
||||
);
|
||||
|
||||
await this.masterPasswordApiService.setPassword(request);
|
||||
|
||||
// Clear force set password reason to allow navigation back to vault.
|
||||
await this.masterPasswordService.setForceSetPasswordReason(ForceSetPasswordReason.None, userId);
|
||||
|
||||
// User now has a password so update account decryption options in state
|
||||
await this.updateAccountDecryptionPropertiesV2(unlockData, userId);
|
||||
|
||||
/**
|
||||
* Set the private key only for new JIT provisioned users in MP encryption orgs.
|
||||
* (Existing TDE users will have their private key set on sync or on login.)
|
||||
*/
|
||||
if (keyPair != null && userType === SetInitialPasswordUserType.JIT_PROVISIONED_MP_ORG_USER) {
|
||||
if (!keyPair[1].encryptedString) {
|
||||
throw new Error("encrypted private key not found. Could not set private key in state.");
|
||||
}
|
||||
await this.keyService.setPrivateKey(keyPair[1].encryptedString, userId);
|
||||
}
|
||||
|
||||
// await this.masterPasswordService.setMasterKeyHash(newLocalMasterKeyHash, userId); // TODO-rr-bw: how to handle local key hash?
|
||||
|
||||
if (resetPasswordAutoEnroll) {
|
||||
await this.handleResetPasswordAutoEnroll(
|
||||
authenticationData.masterPasswordAuthenticationHash,
|
||||
orgId,
|
||||
userId,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated To be removed in PM-28143
|
||||
*/
|
||||
private async makeMasterKeyEncryptedUserKey(
|
||||
masterKey: MasterKey,
|
||||
userId: UserId,
|
||||
@@ -244,6 +393,23 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi
|
||||
await this.keyService.setUserKey(masterKeyEncryptedUserKey[0], userId);
|
||||
}
|
||||
|
||||
private async updateAccountDecryptionPropertiesV2(
|
||||
unlockData: MasterPasswordUnlockData,
|
||||
userId: UserId,
|
||||
) {
|
||||
const userDecryptionOpts = await firstValueFrom(
|
||||
this.userDecryptionOptionsService.userDecryptionOptionsById$(userId),
|
||||
);
|
||||
userDecryptionOpts.hasMasterPassword = true;
|
||||
await this.userDecryptionOptionsService.setUserDecryptionOptionsById(
|
||||
userId,
|
||||
userDecryptionOpts,
|
||||
);
|
||||
|
||||
await this.masterPasswordService.setMasterPasswordUnlockData(unlockData, userId);
|
||||
// await this.masterPasswordService.setMasterKey(masterKey, userId); // TODO-rr-bw: how to handle this? remove this?
|
||||
}
|
||||
|
||||
/**
|
||||
* As part of [PM-28494], adding this setting path to accommodate the changes that are
|
||||
* emerging with pm-23246-unlock-with-master-password-unlock-data.
|
||||
@@ -269,7 +435,7 @@ export class DefaultSetInitialPasswordService implements SetInitialPasswordServi
|
||||
}
|
||||
|
||||
private async handleResetPasswordAutoEnroll(
|
||||
masterKeyHash: string,
|
||||
masterKeyHash: MasterPasswordAuthenticationHash | string, // In PM-28143, remove `| string`; should only accept MasterPasswordAuthenticationHash.
|
||||
orgId: string,
|
||||
userId: UserId,
|
||||
) {
|
||||
|
||||
@@ -22,7 +22,9 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv
|
||||
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
|
||||
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
|
||||
import { assertTruthy, assertNonNullish } from "@bitwarden/common/auth/utils";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
@@ -71,6 +73,7 @@ export class SetInitialPasswordComponent implements OnInit {
|
||||
private accountService: AccountService,
|
||||
private activatedRoute: ActivatedRoute,
|
||||
private anonLayoutWrapperDataService: AnonLayoutWrapperDataService,
|
||||
private configService: ConfigService,
|
||||
private dialogService: DialogService,
|
||||
private i18nService: I18nService,
|
||||
private logoutService: LogoutService,
|
||||
@@ -194,9 +197,19 @@ export class SetInitialPasswordComponent implements OnInit {
|
||||
|
||||
switch (this.userType) {
|
||||
case SetInitialPasswordUserType.JIT_PROVISIONED_MP_ORG_USER:
|
||||
case SetInitialPasswordUserType.TDE_ORG_USER_RESET_PASSWORD_PERMISSION_REQUIRES_MP:
|
||||
case SetInitialPasswordUserType.TDE_ORG_USER_RESET_PASSWORD_PERMISSION_REQUIRES_MP: {
|
||||
const newApisFlagEnabled = await this.configService.getFeatureFlag(
|
||||
FeatureFlag.PM27086_UpdateAuthenticationApisForInputPassword,
|
||||
);
|
||||
|
||||
if (newApisFlagEnabled) {
|
||||
await this.setInitialPasswordV2(passwordInputResult);
|
||||
return;
|
||||
}
|
||||
|
||||
await this.setInitialPassword(passwordInputResult);
|
||||
break;
|
||||
}
|
||||
case SetInitialPasswordUserType.OFFBOARDED_TDE_ORG_USER:
|
||||
await this.setInitialPasswordTdeOffboarding(passwordInputResult);
|
||||
break;
|
||||
@@ -208,6 +221,9 @@ export class SetInitialPasswordComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated To be removed in PM-28143
|
||||
*/
|
||||
private async setInitialPassword(passwordInputResult: PasswordInputResult) {
|
||||
const ctx = "Could not set initial password.";
|
||||
assertTruthy(passwordInputResult.newMasterKey, "newMasterKey", ctx);
|
||||
@@ -254,6 +270,47 @@ export class SetInitialPasswordComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
private async setInitialPasswordV2(passwordInputResult: PasswordInputResult) {
|
||||
const ctx = "Could not set initial password.";
|
||||
|
||||
assertTruthy(passwordInputResult.newPassword, "newPassword", ctx);
|
||||
assertTruthy(passwordInputResult.kdfConfig, "kdfConfig", ctx);
|
||||
assertTruthy(passwordInputResult.salt, "salt", ctx);
|
||||
assertTruthy(this.orgSsoIdentifier, "orgSsoIdentifier", ctx);
|
||||
assertTruthy(this.orgId, "orgId", ctx);
|
||||
assertTruthy(this.userType, "userType", ctx);
|
||||
assertTruthy(this.userId, "userId", ctx);
|
||||
assertNonNullish(passwordInputResult.newPasswordHint, "newPasswordHint", ctx); // can have an empty string as a valid value, so check non-nullish
|
||||
assertNonNullish(this.resetPasswordAutoEnroll, "resetPasswordAutoEnroll", ctx); // can have `false` as a valid value, so check non-nullish
|
||||
|
||||
try {
|
||||
const credentials: SetInitialPasswordCredentials = {
|
||||
newPassword: passwordInputResult.newPassword,
|
||||
newPasswordHint: passwordInputResult.newPasswordHint,
|
||||
kdfConfig: passwordInputResult.kdfConfig,
|
||||
salt: passwordInputResult.salt,
|
||||
orgSsoIdentifier: this.orgSsoIdentifier,
|
||||
orgId: this.orgId,
|
||||
resetPasswordAutoEnroll: this.resetPasswordAutoEnroll,
|
||||
};
|
||||
|
||||
await this.setInitialPasswordService.setInitialPasswordV2(
|
||||
credentials,
|
||||
this.userType,
|
||||
this.userId,
|
||||
);
|
||||
|
||||
this.showSuccessToastByUserType();
|
||||
|
||||
this.submitting = false;
|
||||
await this.router.navigate(["vault"]);
|
||||
} catch (e) {
|
||||
this.logService.error("Error setting initial password", e);
|
||||
this.validationService.showError(e);
|
||||
this.submitting = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async setInitialPasswordTdeOffboarding(passwordInputResult: PasswordInputResult) {
|
||||
const ctx = "Could not set initial password.";
|
||||
assertTruthy(passwordInputResult.newMasterKey, "newMasterKey", ctx);
|
||||
|
||||
@@ -43,16 +43,21 @@ export const SetInitialPasswordUserType: Readonly<{
|
||||
}> = Object.freeze(_SetInitialPasswordUserType);
|
||||
|
||||
export interface SetInitialPasswordCredentials {
|
||||
newMasterKey: MasterKey;
|
||||
newServerMasterKeyHash: string;
|
||||
newLocalMasterKeyHash: string;
|
||||
newPassword?: string; // Make required in PM-28143 (remove `?`)
|
||||
newPasswordHint: string;
|
||||
kdfConfig: KdfConfig;
|
||||
salt?: MasterPasswordSalt; // Make required in PM-28143 (remove `?`)
|
||||
orgSsoIdentifier: string;
|
||||
orgId: string;
|
||||
resetPasswordAutoEnroll: boolean;
|
||||
newPassword: string;
|
||||
salt: MasterPasswordSalt;
|
||||
|
||||
// The deprecated properties below will be removed in PM-28143
|
||||
/** @deprecated */
|
||||
newMasterKey?: MasterKey;
|
||||
/** @deprecated */
|
||||
newServerMasterKeyHash?: string;
|
||||
/** @deprecated */
|
||||
newLocalMasterKeyHash?: string;
|
||||
}
|
||||
|
||||
export interface SetInitialPasswordTdeOffboardingCredentials {
|
||||
@@ -69,6 +74,8 @@ export interface SetInitialPasswordTdeOffboardingCredentials {
|
||||
*/
|
||||
export abstract class SetInitialPasswordService {
|
||||
/**
|
||||
* @deprecated To be removed in PM-28143
|
||||
*
|
||||
* Sets an initial password for an existing authed user who is either:
|
||||
* - {@link SetInitialPasswordUserType.JIT_PROVISIONED_MP_ORG_USER}
|
||||
* - {@link SetInitialPasswordUserType.TDE_ORG_USER_RESET_PASSWORD_PERMISSION_REQUIRES_MP}
|
||||
@@ -95,4 +102,19 @@ export abstract class SetInitialPasswordService {
|
||||
credentials: SetInitialPasswordTdeOffboardingCredentials,
|
||||
userId: UserId,
|
||||
) => Promise<void>;
|
||||
|
||||
/**
|
||||
* Sets an initial password for an existing authed user who is either:
|
||||
* - {@link SetInitialPasswordUserType.JIT_PROVISIONED_MP_ORG_USER}
|
||||
* - {@link SetInitialPasswordUserType.TDE_ORG_USER_RESET_PASSWORD_PERMISSION_REQUIRES_MP}
|
||||
*
|
||||
* @param credentials An object of the credentials needed to set the initial password
|
||||
* @throws If any property on the `credentials` object is null or undefined, or if a
|
||||
* masterKeyEncryptedUserKey or newKeyPair could not be created.
|
||||
*/
|
||||
abstract setInitialPasswordV2: (
|
||||
credentials: SetInitialPasswordCredentials,
|
||||
userType: SetInitialPasswordUserType,
|
||||
userId: UserId,
|
||||
) => Promise<void>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user