1
0
mirror of https://github.com/bitwarden/browser synced 2026-01-31 00:33:33 +00:00

Resolve conflicts / cleanup

This commit is contained in:
Bernd Schoolmann
2026-01-13 13:47:40 +01:00
parent 053199f6ec
commit 9b4af5e802
6 changed files with 22 additions and 126 deletions

View File

@@ -13,16 +13,9 @@
## Standard Auth Request Flows
### Flow 1: Unauthed user requests approval from device; Approving device has a masterKey in memory
### Flow 1: This flow was removed
1. Unauthed user clicks "Login with device"
2. Navigates to `/login-with-device` which creates a `StandardAuthRequest`
3. Receives approval from a device with authRequestPublicKey(masterKey)
4. Decrypts masterKey
5. Decrypts userKey
6. Proceeds to vault
### Flow 2: Unauthed user requests approval from device; Approving device does NOT have a masterKey in memory
### Flow 2: Unauthed user requests approval from device; Approving device does NOT need to have a masterKey in memory
1. Unauthed user clicks "Login with device"
2. Navigates to `/login-with-device` which creates a `StandardAuthRequest`
@@ -33,28 +26,18 @@
**Note:** This flow is an uncommon scenario and relates to TDE off-boarding. The following describes how a user could
get into this flow:
1. An SSO TD user logs into a device via an Admin auth request approval, therefore this device does NOT have a masterKey
1. An SSO TD user logs into a device via an Admin auth request approval, therefore this device does NOT need to have a masterKey
in memory
2. The org admin:
- Changes the member decryption options from "Trusted devices" to "Master password" AND
- Turns off the "Require single sign-on authentication" policy
3. On another device, the user clicks "Login with device", which they can do because the org no longer requires SSO
4. The user approves from the device they had previously logged into with SSO TD, which does NOT have a masterKey in
4. The user approves from the device they had previously logged into with SSO TD, which does NOT need to have a masterKey in
memory
### Flow 3: Authed SSO TD user requests approval from device; Approving device has a masterKey in memory
### Flow 3: This flow was removed
1. SSO TD user authenticates via SSO
2. Navigates to `/login-initiated`
3. Clicks "Approve from your other device"
4. Navigates to `/login-with-device` which creates a `StandardAuthRequest`
5. Receives approval from device with authRequestPublicKey(masterKey)
6. Decrypts masterKey
7. Decrypts userKey
8. Establishes trust (if required)
9. Proceeds to vault
### Flow 4: Authed SSO TD user requests approval from device; Approving device does NOT have a masterKey in memory
### Flow 4: Authed SSO TD user requests approval from device; Approving device does NOT need to have a masterKey in memory
1. SSO TD user authenticates via SSO
2. Navigates to `/login-initiated`
@@ -89,9 +72,7 @@ userKey. This is how admins are able to send over the authRequestPublicKey(userK
| Flow | Auth Status | Clicks Button [active route] | Navigates to | Approving device has masterKey in memory\* |
| --------------- | ----------- | ----------------------------------------------------- | --------------------------- | ------------------------------------------------- |
| Standard Flow 1 | unauthed | "Login with device" [`/login`] | `/login-with-device` | yes |
| Standard Flow 2 | unauthed | "Login with device" [`/login`] | `/login-with-device` | no |
| Standard Flow 3 | authed | "Approve from your other device" [`/login-initiated`] | `/login-with-device` | yes |
| Standard Flow 4 | authed | "Approve from your other device" [`/login-initiated`] | `/login-with-device` | no |
| Admin Flow | authed | "Request admin approval"<br>[`/login-initiated`] | `/admin-approval-requested` | NA - admin requests always send encrypted userKey |

View File

@@ -605,10 +605,10 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy {
if (authRequestResponse.requestApproved) {
const userHasAuthenticatedViaSSO = this.authStatus === AuthenticationStatus.Locked;
if (userHasAuthenticatedViaSSO) {
// [Standard Flow 3-4] Handle authenticated SSO TD user flows
// [Standard Flow 4] Handle authenticated SSO TD user flows
return await this.handleAuthenticatedFlows(authRequestResponse);
} else {
// [Standard Flow 1-2] Handle unauthenticated user flows
// [Standard Flow 2] Handle unauthenticated user flows
return await this.handleUnauthenticatedFlows(authRequestResponse, requestId);
}
}
@@ -629,7 +629,7 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy {
}
private async handleAuthenticatedFlows(authRequestResponse: AuthRequestResponse) {
// [Standard Flow 3-4] Handle authenticated SSO TD user flows
// [Standard Flow 4] Handle authenticated SSO TD user flows
const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id;
if (!userId) {
this.logService.error(
@@ -654,7 +654,7 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy {
authRequestResponse: AuthRequestResponse,
requestId: string,
) {
// [Standard Flow 1-2] Handle unauthenticated user flows
// [Standard Flow 2] Handle unauthenticated user flows
const authRequestLoginCredentials = await this.buildAuthRequestLoginCredentials(
requestId,
authRequestResponse,
@@ -743,43 +743,18 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy {
/**
* See verifyAndHandleApprovedAuthReq() for flow details.
*
* We determine the type of `key` based on the presence or absence of `masterPasswordHash`:
* - If `masterPasswordHash` has a value, we receive the `key` as an authRequestPublicKey(masterKey) [plus we have authRequestPublicKey(masterPasswordHash)]
* - If `masterPasswordHash` does not have a value, we receive the `key` as an authRequestPublicKey(userKey)
*/
if (authRequestResponse.masterPasswordHash) {
// ...in Standard Auth Request Flow 1
const { masterKey, masterKeyHash } =
await this.authRequestService.decryptPubKeyEncryptedMasterKeyAndHash(
authRequestResponse.key,
authRequestResponse.masterPasswordHash,
this.authRequestKeyPair.privateKey,
);
return new AuthRequestLoginCredentials(
this.email,
this.accessCode,
requestId,
null, // no userKey
masterKey,
masterKeyHash,
);
} else {
// ...in Standard Auth Request Flow 2
const userKey = await this.authRequestService.decryptPubKeyEncryptedUserKey(
authRequestResponse.key,
this.authRequestKeyPair.privateKey,
);
return new AuthRequestLoginCredentials(
this.email,
this.accessCode,
requestId,
userKey,
null, // no masterKey
null, // no masterKeyHash
);
}
// ...in Standard Auth Request Flow 2
const userKey = await this.authRequestService.decryptPubKeyEncryptedUserKey(
authRequestResponse.key,
this.authRequestKeyPair.privateKey,
);
return new AuthRequestLoginCredentials(
this.email,
this.accessCode,
requestId,
userKey,
);
}
private async clearExistingAdminAuthRequestAndStartNewRequest(userId: UserId) {

View File

@@ -73,11 +73,7 @@ describe("AuthRequestLoginStrategy", () => {
const email = "EMAIL";
const accessCode = "ACCESS_CODE";
const authRequestId = "AUTH_REQUEST_ID";
const decMasterKey = new SymmetricCryptoKey(
new Uint8Array(64).buffer as CsprngArray,
) as MasterKey;
const decUserKey = new SymmetricCryptoKey(new Uint8Array(64).buffer as CsprngArray) as UserKey;
const decMasterKeyHash = "LOCAL_PASSWORD_HASH";
beforeEach(async () => {
keyService = mock<KeyService>();
@@ -150,39 +146,6 @@ describe("AuthRequestLoginStrategy", () => {
);
});
it("sets keys after a successful authentication when masterKey and masterKeyHash provided in login credentials", async () => {
credentials = new AuthRequestLoginCredentials(
email,
accessCode,
authRequestId,
null,
decMasterKey,
decMasterKeyHash,
);
const masterKey = new SymmetricCryptoKey(new Uint8Array(64).buffer as CsprngArray) as MasterKey;
const userKey = new SymmetricCryptoKey(new Uint8Array(64).buffer as CsprngArray) as UserKey;
masterPasswordService.masterKeySubject.next(masterKey);
masterPasswordService.mock.decryptUserKeyWithMasterKey.mockResolvedValue(userKey);
tokenService.decodeAccessToken.mockResolvedValue({ sub: mockUserId });
await authRequestLoginStrategy.logIn(credentials);
expect(masterPasswordService.mock.setMasterKey).toHaveBeenCalledWith(masterKey, mockUserId);
expect(masterPasswordService.mock.setMasterKeyHash).toHaveBeenCalledWith(
decMasterKeyHash,
mockUserId,
);
expect(masterPasswordService.mock.setMasterKeyEncryptedUserKey).toHaveBeenCalledWith(
tokenResponse.key,
mockUserId,
);
expect(keyService.setUserKey).toHaveBeenCalledWith(userKey, mockUserId);
expect(deviceTrustService.trustDeviceIfRequired).toHaveBeenCalled();
expect(keyService.setPrivateKey).toHaveBeenCalledWith(tokenResponse.privateKey, mockUserId);
});
it("sets keys after a successful authentication when only userKey provided in login credentials", async () => {
// Initialize credentials with only userKey
credentials = new AuthRequestLoginCredentials(
@@ -190,8 +153,6 @@ describe("AuthRequestLoginStrategy", () => {
accessCode,
authRequestId,
decUserKey, // Pass userKey
null, // No masterKey
null, // No masterKeyHash
);
// Call logIn
@@ -234,7 +195,6 @@ describe("AuthRequestLoginStrategy", () => {
};
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
masterPasswordService.masterKeySubject.next(decMasterKey);
masterPasswordService.mock.decryptUserKeyWithMasterKey.mockResolvedValue(decUserKey);
await authRequestLoginStrategy.logIn(credentials);

View File

@@ -72,20 +72,7 @@ export class AuthRequestLoginStrategy extends LoginStrategy {
}
protected override async setMasterKey(response: IdentityTokenResponse, userId: UserId) {
const authRequestCredentials = this.cache.value.authRequestCredentials;
if (
authRequestCredentials.decryptedMasterKey &&
authRequestCredentials.decryptedMasterKeyHash
) {
await this.masterPasswordService.setMasterKey(
authRequestCredentials.decryptedMasterKey,
userId,
);
await this.masterPasswordService.setMasterKeyHash(
authRequestCredentials.decryptedMasterKeyHash,
userId,
);
}
// This login strategy does not use a master key
}
protected override async setUserKey(

View File

@@ -54,8 +54,6 @@ export class AuthRequestLoginCredentials {
public accessCode: string,
public authRequestId: string,
public decryptedUserKey: UserKey | null,
public decryptedMasterKey: MasterKey | null,
public decryptedMasterKeyHash: string | null,
public twoFactor?: TokenTwoFactorRequest,
) {}
@@ -66,8 +64,6 @@ export class AuthRequestLoginCredentials {
json.accessCode,
json.authRequestId,
null,
null,
json.decryptedMasterKeyHash,
json.twoFactor
? new TokenTwoFactorRequest(
json.twoFactor.provider,
@@ -78,7 +74,6 @@ export class AuthRequestLoginCredentials {
),
{
decryptedUserKey: SymmetricCryptoKey.fromJSON(json.decryptedUserKey) as UserKey,
decryptedMasterKey: SymmetricCryptoKey.fromJSON(json.decryptedMasterKey) as MasterKey,
},
);
}

View File

@@ -93,8 +93,6 @@ describe("LOGIN_STRATEGY_CACHE_KEY", () => {
"ACCESS_CODE",
"AUTH_REQUEST_ID",
new SymmetricCryptoKey(new Uint8Array(64)) as UserKey,
new SymmetricCryptoKey(new Uint8Array(64)) as MasterKey,
"MASTER_KEY_HASH",
);
const result = sut.deserializer(JSON.parse(JSON.stringify(actual)));