mirror of
https://github.com/bitwarden/clients.git
synced 2025-12-10 10:27:10 -06:00
refactor(IdentityTokenResponse): [Auth/PM-3537] Remove deprecated KeyConnectorUrl from of IdentityTokenResponse + misc TDE cleanup (#17593)
* PM-3537 - Remove KeyConnectorUrl from IdentityTokenResponse and clean up other flagged behavior * PM-3537 - SSO Login Strategy tests - remove key connector url * PM-3537 - Update LoginStrategyService tests to pass
This commit is contained in:
parent
dab1a37bfe
commit
d581f06b32
@ -259,7 +259,7 @@ describe("LoginStrategy", () => {
|
|||||||
|
|
||||||
expect(userDecryptionOptionsService.setUserDecryptionOptionsById).toHaveBeenCalledWith(
|
expect(userDecryptionOptionsService.setUserDecryptionOptionsById).toHaveBeenCalledWith(
|
||||||
userId,
|
userId,
|
||||||
UserDecryptionOptions.fromResponse(idTokenResponse),
|
UserDecryptionOptions.fromIdentityTokenResponse(idTokenResponse),
|
||||||
);
|
);
|
||||||
expect(masterPasswordService.mock.setMasterPasswordUnlockData).toHaveBeenCalledWith(
|
expect(masterPasswordService.mock.setMasterPasswordUnlockData).toHaveBeenCalledWith(
|
||||||
new MasterPasswordUnlockData(
|
new MasterPasswordUnlockData(
|
||||||
|
|||||||
@ -199,7 +199,7 @@ export abstract class LoginStrategy {
|
|||||||
// as the user decryption options help determine the available timeout actions.
|
// as the user decryption options help determine the available timeout actions.
|
||||||
await this.userDecryptionOptionsService.setUserDecryptionOptionsById(
|
await this.userDecryptionOptionsService.setUserDecryptionOptionsById(
|
||||||
userId,
|
userId,
|
||||||
UserDecryptionOptions.fromResponse(tokenResponse),
|
UserDecryptionOptions.fromIdentityTokenResponse(tokenResponse),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (tokenResponse.userDecryptionOptions?.masterPasswordUnlock != null) {
|
if (tokenResponse.userDecryptionOptions?.masterPasswordUnlock != null) {
|
||||||
|
|||||||
@ -503,67 +503,6 @@ describe("SsoLoginStrategy", () => {
|
|||||||
HasMasterPassword: false,
|
HasMasterPassword: false,
|
||||||
KeyConnectorOption: { KeyConnectorUrl: keyConnectorUrl },
|
KeyConnectorOption: { KeyConnectorUrl: keyConnectorUrl },
|
||||||
});
|
});
|
||||||
tokenResponse.keyConnectorUrl = keyConnectorUrl;
|
|
||||||
});
|
|
||||||
|
|
||||||
it("gets and sets the master key if Key Connector is enabled and the user doesn't have a master password", async () => {
|
|
||||||
const masterKey = new SymmetricCryptoKey(
|
|
||||||
new Uint8Array(64).buffer as CsprngArray,
|
|
||||||
) as MasterKey;
|
|
||||||
|
|
||||||
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
|
|
||||||
masterPasswordService.masterKeySubject.next(masterKey);
|
|
||||||
|
|
||||||
await ssoLoginStrategy.logIn(credentials);
|
|
||||||
|
|
||||||
expect(keyConnectorService.setMasterKeyFromUrl).toHaveBeenCalledWith(keyConnectorUrl, userId);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("converts new SSO user with no master password to Key Connector on first login", async () => {
|
|
||||||
tokenResponse.key = undefined;
|
|
||||||
tokenResponse.kdfConfig = new Argon2KdfConfig(10, 64, 4);
|
|
||||||
|
|
||||||
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
|
|
||||||
|
|
||||||
await ssoLoginStrategy.logIn(credentials);
|
|
||||||
|
|
||||||
expect(keyConnectorService.setNewSsoUserKeyConnectorConversionData).toHaveBeenCalledWith(
|
|
||||||
{
|
|
||||||
kdfConfig: new Argon2KdfConfig(10, 64, 4),
|
|
||||||
keyConnectorUrl: keyConnectorUrl,
|
|
||||||
organizationId: ssoOrgId,
|
|
||||||
},
|
|
||||||
userId,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("decrypts and sets the user key if Key Connector is enabled and the user doesn't have a master password", async () => {
|
|
||||||
const userKey = new SymmetricCryptoKey(new Uint8Array(64).buffer as CsprngArray) as UserKey;
|
|
||||||
const masterKey = new SymmetricCryptoKey(
|
|
||||||
new Uint8Array(64).buffer as CsprngArray,
|
|
||||||
) as MasterKey;
|
|
||||||
|
|
||||||
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
|
|
||||||
masterPasswordService.masterKeySubject.next(masterKey);
|
|
||||||
masterPasswordService.mock.decryptUserKeyWithMasterKey.mockResolvedValue(userKey);
|
|
||||||
|
|
||||||
await ssoLoginStrategy.logIn(credentials);
|
|
||||||
|
|
||||||
expect(masterPasswordService.mock.decryptUserKeyWithMasterKey).toHaveBeenCalledWith(
|
|
||||||
masterKey,
|
|
||||||
userId,
|
|
||||||
undefined,
|
|
||||||
);
|
|
||||||
expect(keyService.setUserKey).toHaveBeenCalledWith(userKey, userId);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Key Connector Pre-TDE", () => {
|
|
||||||
let tokenResponse: IdentityTokenResponse;
|
|
||||||
beforeEach(() => {
|
|
||||||
tokenResponse = identityTokenResponseFactory();
|
|
||||||
tokenResponse.userDecryptionOptions = null;
|
|
||||||
tokenResponse.keyConnectorUrl = keyConnectorUrl;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("gets and sets the master key if Key Connector is enabled and the user doesn't have a master password", async () => {
|
it("gets and sets the master key if Key Connector is enabled and the user doesn't have a master password", async () => {
|
||||||
|
|||||||
@ -157,22 +157,12 @@ export class SsoLoginStrategy extends LoginStrategy {
|
|||||||
// In order for us to set the master key from Key Connector, we need to have a Key Connector URL
|
// In order for us to set the master key from Key Connector, we need to have a Key Connector URL
|
||||||
// and the user must not have a master password.
|
// and the user must not have a master password.
|
||||||
return userHasKeyConnectorUrl && !userHasMasterPassword;
|
return userHasKeyConnectorUrl && !userHasMasterPassword;
|
||||||
} else {
|
|
||||||
// In pre-TDE versions of the server, the userDecryptionOptions will not be present.
|
|
||||||
// In this case, we can determine if the user has a master password and has a Key Connector URL by
|
|
||||||
// just checking the keyConnectorUrl property. This is because the server short-circuits on the response
|
|
||||||
// and will not pass back the URL in the response if the user has a master password.
|
|
||||||
// TODO: remove compatibility check after 2023.10 release (https://bitwarden.atlassian.net/browse/PM-3537)
|
|
||||||
return tokenResponse.keyConnectorUrl != null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private getKeyConnectorUrl(tokenResponse: IdentityTokenResponse): string {
|
private getKeyConnectorUrl(tokenResponse: IdentityTokenResponse): string {
|
||||||
// TODO: remove tokenResponse.keyConnectorUrl reference after 2023.10 release (https://bitwarden.atlassian.net/browse/PM-3537)
|
|
||||||
const userDecryptionOptions = tokenResponse?.userDecryptionOptions;
|
const userDecryptionOptions = tokenResponse?.userDecryptionOptions;
|
||||||
return (
|
return userDecryptionOptions?.keyConnectorOption?.keyConnectorUrl;
|
||||||
tokenResponse.keyConnectorUrl ?? userDecryptionOptions?.keyConnectorOption?.keyConnectorUrl
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: future passkey login strategy will need to support setting user key (decrypting via TDE or admin approval request)
|
// TODO: future passkey login strategy will need to support setting user key (decrypting via TDE or admin approval request)
|
||||||
|
|||||||
@ -112,10 +112,11 @@ export class UserDecryptionOptions {
|
|||||||
* @throws If the response is nullish, this method will throw an error. User decryption options
|
* @throws If the response is nullish, this method will throw an error. User decryption options
|
||||||
* are required for client initialization.
|
* are required for client initialization.
|
||||||
*/
|
*/
|
||||||
// TODO: Change response type to `UserDecryptionOptionsResponse` after 2023.10 release (https://bitwarden.atlassian.net/browse/PM-3537)
|
static fromIdentityTokenResponse(response: IdentityTokenResponse): UserDecryptionOptions {
|
||||||
static fromResponse(response: IdentityTokenResponse): UserDecryptionOptions {
|
|
||||||
if (response == null) {
|
if (response == null) {
|
||||||
throw new Error("User Decryption Options are required for client initialization.");
|
throw new Error(
|
||||||
|
"User Decryption Options are required for client initialization. Response is nullish.",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const decryptionOptions = new UserDecryptionOptions();
|
const decryptionOptions = new UserDecryptionOptions();
|
||||||
@ -134,17 +135,9 @@ export class UserDecryptionOptions {
|
|||||||
responseOptions.keyConnectorOption,
|
responseOptions.keyConnectorOption,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// If the response does not have userDecryptionOptions, this means it's on a pre-TDE server version and so
|
throw new Error(
|
||||||
// we must base our decryption options on the presence of the keyConnectorUrl.
|
"User Decryption Options are required for client initialization. userDecryptionOptions is missing in response.",
|
||||||
// Note that the presence of keyConnectorUrl implies that the user does not have a master password, as in pre-TDE
|
);
|
||||||
// server versions, a master password short-circuited the addition of the keyConnectorUrl to the response.
|
|
||||||
// TODO: remove this check after 2023.10 release (https://bitwarden.atlassian.net/browse/PM-3537)
|
|
||||||
const usingKeyConnector = response.keyConnectorUrl != null;
|
|
||||||
decryptionOptions.hasMasterPassword = !usingKeyConnector;
|
|
||||||
if (usingKeyConnector) {
|
|
||||||
decryptionOptions.keyConnectorOption = new KeyConnectorUserDecryptionOption();
|
|
||||||
decryptionOptions.keyConnectorOption.keyConnectorUrl = response.keyConnectorUrl;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return decryptionOptions;
|
return decryptionOptions;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/ide
|
|||||||
import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response";
|
import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response";
|
||||||
import { IdentityTwoFactorResponse } from "@bitwarden/common/auth/models/response/identity-two-factor.response";
|
import { IdentityTwoFactorResponse } from "@bitwarden/common/auth/models/response/identity-two-factor.response";
|
||||||
import { PreloginResponse } from "@bitwarden/common/auth/models/response/prelogin.response";
|
import { PreloginResponse } from "@bitwarden/common/auth/models/response/prelogin.response";
|
||||||
|
import { UserDecryptionOptionsResponse } from "@bitwarden/common/auth/models/response/user-decryption-options/user-decryption-options.response";
|
||||||
import { TwoFactorService } from "@bitwarden/common/auth/two-factor";
|
import { TwoFactorService } from "@bitwarden/common/auth/two-factor";
|
||||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||||
@ -496,6 +497,7 @@ describe("LoginStrategyService", () => {
|
|||||||
refresh_token: "REFRESH_TOKEN",
|
refresh_token: "REFRESH_TOKEN",
|
||||||
scope: "api offline_access",
|
scope: "api offline_access",
|
||||||
token_type: "Bearer",
|
token_type: "Bearer",
|
||||||
|
userDecryptionOptions: new UserDecryptionOptionsResponse({ HasMasterPassword: true }),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
apiService.postPrelogin.mockResolvedValue(
|
apiService.postPrelogin.mockResolvedValue(
|
||||||
@ -563,6 +565,7 @@ describe("LoginStrategyService", () => {
|
|||||||
refresh_token: "REFRESH_TOKEN",
|
refresh_token: "REFRESH_TOKEN",
|
||||||
scope: "api offline_access",
|
scope: "api offline_access",
|
||||||
token_type: "Bearer",
|
token_type: "Bearer",
|
||||||
|
userDecryptionOptions: new UserDecryptionOptionsResponse({ HasMasterPassword: true }),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -692,6 +695,7 @@ describe("LoginStrategyService", () => {
|
|||||||
refresh_token: "REFRESH_TOKEN",
|
refresh_token: "REFRESH_TOKEN",
|
||||||
scope: "api offline_access",
|
scope: "api offline_access",
|
||||||
token_type: "Bearer",
|
token_type: "Bearer",
|
||||||
|
userDecryptionOptions: new UserDecryptionOptionsResponse({ HasMasterPassword: true }),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -26,7 +26,6 @@ export class IdentityTokenResponse extends BaseResponse {
|
|||||||
forcePasswordReset: boolean;
|
forcePasswordReset: boolean;
|
||||||
masterPasswordPolicy: MasterPasswordPolicyResponse;
|
masterPasswordPolicy: MasterPasswordPolicyResponse;
|
||||||
apiUseKeyConnector: boolean;
|
apiUseKeyConnector: boolean;
|
||||||
keyConnectorUrl: string;
|
|
||||||
|
|
||||||
userDecryptionOptions?: UserDecryptionOptionsResponse;
|
userDecryptionOptions?: UserDecryptionOptionsResponse;
|
||||||
|
|
||||||
@ -70,7 +69,7 @@ export class IdentityTokenResponse extends BaseResponse {
|
|||||||
: new Argon2KdfConfig(kdfIterations, kdfMemory, kdfParallelism);
|
: new Argon2KdfConfig(kdfIterations, kdfMemory, kdfParallelism);
|
||||||
this.forcePasswordReset = this.getResponseProperty("ForcePasswordReset");
|
this.forcePasswordReset = this.getResponseProperty("ForcePasswordReset");
|
||||||
this.apiUseKeyConnector = this.getResponseProperty("ApiUseKeyConnector");
|
this.apiUseKeyConnector = this.getResponseProperty("ApiUseKeyConnector");
|
||||||
this.keyConnectorUrl = this.getResponseProperty("KeyConnectorUrl");
|
|
||||||
this.masterPasswordPolicy = new MasterPasswordPolicyResponse(
|
this.masterPasswordPolicy = new MasterPasswordPolicyResponse(
|
||||||
this.getResponseProperty("MasterPasswordPolicy"),
|
this.getResponseProperty("MasterPasswordPolicy"),
|
||||||
);
|
);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user