mirror of
https://github.com/bitwarden/browser
synced 2026-01-31 00:33:33 +00:00
Resolve conflicts / cleanup
This commit is contained in:
@@ -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 |
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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)));
|
||||
|
||||
Reference in New Issue
Block a user