mirror of
https://github.com/bitwarden/browser
synced 2026-02-08 04:33:38 +00:00
show key connector domain for new sso users
This commit is contained in:
@@ -43,7 +43,7 @@ import {
|
||||
UserLockIcon,
|
||||
VaultIcon,
|
||||
} from "@bitwarden/auth/angular";
|
||||
import { LockComponent } from "@bitwarden/key-management-ui";
|
||||
import { ConfirmKeyConnectorDomainComponent, LockComponent } from "@bitwarden/key-management-ui";
|
||||
import {
|
||||
NewDeviceVerificationNoticePageOneComponent,
|
||||
NewDeviceVerificationNoticePageTwoComponent,
|
||||
@@ -225,6 +225,12 @@ const routes: Routes = [
|
||||
canActivate: [authGuard],
|
||||
data: { elevation: 1 } satisfies RouteDataProperties,
|
||||
},
|
||||
{
|
||||
path: "confirm-key-connector-domain",
|
||||
component: ConfirmKeyConnectorDomainComponent,
|
||||
canActivate: [authGuard],
|
||||
data: { elevation: 1 } satisfies RouteDataProperties,
|
||||
},
|
||||
{
|
||||
path: "view-cipher",
|
||||
component: ViewV2Component,
|
||||
|
||||
@@ -42,7 +42,7 @@ import {
|
||||
NewDeviceVerificationComponent,
|
||||
DeviceVerificationIcon,
|
||||
} from "@bitwarden/auth/angular";
|
||||
import { LockComponent } from "@bitwarden/key-management-ui";
|
||||
import { ConfirmKeyConnectorDomainComponent, LockComponent } from "@bitwarden/key-management-ui";
|
||||
import {
|
||||
NewDeviceVerificationNoticePageOneComponent,
|
||||
NewDeviceVerificationNoticePageTwoComponent,
|
||||
@@ -178,6 +178,11 @@ const routes: Routes = [
|
||||
component: RemovePasswordComponent,
|
||||
canActivate: [authGuard],
|
||||
},
|
||||
{
|
||||
path: "confirm-key-connector-domain",
|
||||
component: ConfirmKeyConnectorDomainComponent,
|
||||
canActivate: [authGuard],
|
||||
},
|
||||
{
|
||||
path: "passkeys",
|
||||
component: Fido2PlaceholderComponent,
|
||||
|
||||
@@ -41,7 +41,7 @@ import {
|
||||
NewDeviceVerificationComponent,
|
||||
DeviceVerificationIcon,
|
||||
} from "@bitwarden/auth/angular";
|
||||
import { LockComponent } from "@bitwarden/key-management-ui";
|
||||
import { ConfirmKeyConnectorDomainComponent, LockComponent } from "@bitwarden/key-management-ui";
|
||||
import {
|
||||
NewDeviceVerificationNoticePageOneComponent,
|
||||
NewDeviceVerificationNoticePageTwoComponent,
|
||||
@@ -570,6 +570,17 @@ const routes: Routes = [
|
||||
titleId: "removeMasterPassword",
|
||||
} satisfies RouteDataProperties & AnonLayoutWrapperData,
|
||||
},
|
||||
{
|
||||
path: "confirm-key-connector-domain",
|
||||
component: ConfirmKeyConnectorDomainComponent,
|
||||
canActivate: [authGuard],
|
||||
data: {
|
||||
pageTitle: {
|
||||
key: "confirmKeyConnectorDomain",
|
||||
},
|
||||
titleId: "confirmKeyConnectorDomain",
|
||||
} satisfies RouteDataProperties & AnonLayoutWrapperData,
|
||||
},
|
||||
{
|
||||
path: "trial-initiation",
|
||||
canActivate: [unauthGuardFn()],
|
||||
|
||||
@@ -27,8 +27,10 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { ToastService } from "@bitwarden/components";
|
||||
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
|
||||
import { KdfType } from "@bitwarden/key-management";
|
||||
|
||||
@Directive()
|
||||
export class SsoComponent implements OnInit {
|
||||
@@ -219,6 +221,13 @@ export class SsoComponent implements OnInit {
|
||||
return await this.handleTwoFactorRequired(orgSsoIdentifier);
|
||||
}
|
||||
|
||||
if (authResult.requiresKeyConnectorDomainConfirmation != null) {
|
||||
return await this.handleKeyConnectorDomainConfirmation(
|
||||
authResult.requiresKeyConnectorDomainConfirmation,
|
||||
authResult.userId,
|
||||
);
|
||||
}
|
||||
|
||||
// Everything after the 2FA check is considered a successful login
|
||||
// Just have to figure out where to send the user
|
||||
|
||||
@@ -419,4 +428,22 @@ export class SsoComponent implements OnInit {
|
||||
const checkStateSplit = checkState.split("_identifier=");
|
||||
return stateSplit[0] === checkStateSplit[0];
|
||||
}
|
||||
|
||||
private async handleKeyConnectorDomainConfirmation(
|
||||
request: {
|
||||
kdf: KdfType;
|
||||
kdfIterations: number;
|
||||
kdfMemory?: number;
|
||||
kdfParallelism?: number;
|
||||
keyConnectorUrl: string;
|
||||
},
|
||||
userId: UserId,
|
||||
) {
|
||||
await this.router.navigate(["confirm-key-connector-domain"], {
|
||||
state: {
|
||||
...request,
|
||||
userId,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +77,10 @@ export class AuthRequestLoginStrategy extends LoginStrategy {
|
||||
return super.logInTwoFactor(twoFactor);
|
||||
}
|
||||
|
||||
protected override async setMasterKey(response: IdentityTokenResponse, userId: UserId) {
|
||||
protected override async setMasterKey(
|
||||
response: IdentityTokenResponse,
|
||||
userId: UserId,
|
||||
): Promise<null> {
|
||||
const authRequestCredentials = this.cache.value.authRequestCredentials;
|
||||
if (
|
||||
authRequestCredentials.decryptedMasterKey &&
|
||||
@@ -92,6 +95,8 @@ export class AuthRequestLoginStrategy extends LoginStrategy {
|
||||
userId,
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected override async setUserKey(
|
||||
|
||||
@@ -296,7 +296,12 @@ export abstract class LoginStrategy {
|
||||
await this.tokenService.setTwoFactorToken(userEmail, response.twoFactorToken);
|
||||
}
|
||||
|
||||
await this.setMasterKey(response, userId);
|
||||
const masterKeyResult = await this.setMasterKey(response, userId);
|
||||
if (masterKeyResult != null) {
|
||||
result.requiresKeyConnectorDomainConfirmation =
|
||||
masterKeyResult.requiresKeyConnectorDomainConfirmation;
|
||||
}
|
||||
|
||||
await this.setUserKey(response, userId);
|
||||
await this.setPrivateKey(response, userId);
|
||||
|
||||
@@ -306,7 +311,19 @@ export abstract class LoginStrategy {
|
||||
}
|
||||
|
||||
// The keys comes from different sources depending on the login strategy
|
||||
protected abstract setMasterKey(response: IdentityTokenResponse, userId: UserId): Promise<void>;
|
||||
protected abstract setMasterKey(
|
||||
response: IdentityTokenResponse,
|
||||
userId: UserId,
|
||||
): Promise<{
|
||||
requiresKeyConnectorDomainConfirmation: {
|
||||
kdf: KdfType;
|
||||
kdfIterations: number;
|
||||
kdfMemory?: number;
|
||||
kdfParallelism?: number;
|
||||
keyConnectorUrl: string;
|
||||
organizationId: string;
|
||||
};
|
||||
} | null>;
|
||||
|
||||
protected abstract setUserKey(response: IdentityTokenResponse, userId: UserId): Promise<void>;
|
||||
|
||||
|
||||
@@ -168,10 +168,15 @@ export class PasswordLoginStrategy extends LoginStrategy {
|
||||
return result;
|
||||
}
|
||||
|
||||
protected override async setMasterKey(response: IdentityTokenResponse, userId: UserId) {
|
||||
protected override async setMasterKey(
|
||||
response: IdentityTokenResponse,
|
||||
userId: UserId,
|
||||
): Promise<null> {
|
||||
const { masterKey, localMasterKeyHash } = this.cache.value;
|
||||
await this.masterPasswordService.setMasterKey(masterKey, userId);
|
||||
await this.masterPasswordService.setMasterKeyHash(localMasterKeyHash, userId);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected override async setUserKey(
|
||||
|
||||
@@ -125,16 +125,23 @@ export class SsoLoginStrategy extends LoginStrategy {
|
||||
// The presence of a masterKeyEncryptedUserKey indicates that the user has already been provisioned in Key Connector.
|
||||
const newSsoUser = tokenResponse.key == null;
|
||||
if (newSsoUser) {
|
||||
await this.keyConnectorService.convertNewSsoUserToKeyConnector(
|
||||
tokenResponse,
|
||||
this.cache.value.orgId,
|
||||
userId,
|
||||
);
|
||||
return {
|
||||
requiresKeyConnectorDomainConfirmation: {
|
||||
kdf: tokenResponse.kdf,
|
||||
kdfIterations: tokenResponse.kdfIterations,
|
||||
kdfMemory: tokenResponse.kdfMemory,
|
||||
kdfParallelism: tokenResponse.kdfParallelism,
|
||||
keyConnectorUrl: this.getKeyConnectorUrl(tokenResponse),
|
||||
organizationId: this.cache.value.orgId,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
const keyConnectorUrl = this.getKeyConnectorUrl(tokenResponse);
|
||||
await this.keyConnectorService.setMasterKeyFromUrl(keyConnectorUrl, userId);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -211,7 +218,7 @@ export class SsoLoginStrategy extends LoginStrategy {
|
||||
this.getKeyConnectorUrl(tokenResponse) != null
|
||||
) {
|
||||
// Key connector enabled for user
|
||||
await this.trySetUserKeyWithMasterKey(userId);
|
||||
await this.trySetUserKeyWithMasterKey(tokenResponse, userId);
|
||||
}
|
||||
|
||||
// Note: In the traditional SSO flow with MP without key connector, the lock component
|
||||
@@ -321,7 +328,17 @@ export class SsoLoginStrategy extends LoginStrategy {
|
||||
}
|
||||
}
|
||||
|
||||
private async trySetUserKeyWithMasterKey(userId: UserId): Promise<void> {
|
||||
private async trySetUserKeyWithMasterKey(
|
||||
tokenResponse: IdentityTokenResponse,
|
||||
userId: UserId,
|
||||
): Promise<void> {
|
||||
const newSsoUser = tokenResponse.key == null;
|
||||
// For new users with Key Connector, we will not have a master key yet, since Key Connector
|
||||
// domain have to be confirmed first.
|
||||
if (newSsoUser) {
|
||||
return;
|
||||
}
|
||||
|
||||
const masterKey = await firstValueFrom(this.masterPasswordService.masterKey$(userId));
|
||||
|
||||
// There is a scenario in which the master key is not set here. That will occur if the user
|
||||
|
||||
@@ -52,12 +52,16 @@ export class UserApiLoginStrategy extends LoginStrategy {
|
||||
return authResult;
|
||||
}
|
||||
|
||||
protected override async setMasterKey(response: IdentityTokenResponse, userId: UserId) {
|
||||
protected override async setMasterKey(
|
||||
response: IdentityTokenResponse,
|
||||
userId: UserId,
|
||||
): Promise<null> {
|
||||
if (response.apiUseKeyConnector) {
|
||||
const env = await firstValueFrom(this.environmentService.environment$);
|
||||
const keyConnectorUrl = env.getKeyConnectorUrl();
|
||||
await this.keyConnectorService.setMasterKeyFromUrl(keyConnectorUrl, userId);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected override async setUserKey(
|
||||
|
||||
@@ -59,8 +59,11 @@ export class WebAuthnLoginStrategy extends LoginStrategy {
|
||||
throw new Error("2FA not supported yet for WebAuthn Login.");
|
||||
}
|
||||
|
||||
protected override async setMasterKey(response: IdentityTokenResponse, userId: UserId) {
|
||||
return Promise.resolve();
|
||||
protected override async setMasterKey(
|
||||
response: IdentityTokenResponse,
|
||||
userId: UserId,
|
||||
): Promise<null> {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected override async setUserKey(idTokenResponse: IdentityTokenResponse, userId: UserId) {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { KdfType } from "@bitwarden/key-management";
|
||||
|
||||
import { Utils } from "../../../platform/misc/utils";
|
||||
import { UserId } from "../../../types/guid";
|
||||
import { TwoFactorProviderType } from "../../enums/two-factor-provider-type";
|
||||
@@ -23,6 +25,14 @@ export class AuthResult {
|
||||
email: string;
|
||||
requiresEncryptionKeyMigration: boolean;
|
||||
requiresDeviceVerification: boolean;
|
||||
requiresKeyConnectorDomainConfirmation?: {
|
||||
kdf: KdfType;
|
||||
kdfIterations: number;
|
||||
kdfMemory?: number;
|
||||
kdfParallelism?: number;
|
||||
keyConnectorUrl: string;
|
||||
organizationId: string;
|
||||
};
|
||||
|
||||
get requiresCaptcha() {
|
||||
return !Utils.isNullOrWhitespace(this.captchaSiteKey);
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
import { KdfType } from "@bitwarden/key-management";
|
||||
|
||||
import { Organization } from "../../../admin-console/models/domain/organization";
|
||||
import { IdentityTokenResponse } from "../../../auth/models/response/identity-token.response";
|
||||
import { UserId } from "../../../types/guid";
|
||||
|
||||
export abstract class KeyConnectorService {
|
||||
@@ -14,9 +15,13 @@ export abstract class KeyConnectorService {
|
||||
abstract migrateUser(userId: UserId): Promise<void>;
|
||||
|
||||
abstract convertNewSsoUserToKeyConnector(
|
||||
tokenResponse: IdentityTokenResponse,
|
||||
orgId: string,
|
||||
userId: UserId,
|
||||
keyConnectorUrl: string,
|
||||
kdf: KdfType,
|
||||
kdfIterations: number,
|
||||
kdfMemory?: number,
|
||||
kdfParallelism?: number,
|
||||
): Promise<void>;
|
||||
|
||||
abstract setUsesKeyConnector(enabled: boolean, userId: UserId): Promise<void>;
|
||||
|
||||
@@ -17,7 +17,6 @@ import { OrganizationService } from "../../../admin-console/abstractions/organiz
|
||||
import { OrganizationUserType } from "../../../admin-console/enums";
|
||||
import { Organization } from "../../../admin-console/models/domain/organization";
|
||||
import { TokenService } from "../../../auth/abstractions/token.service";
|
||||
import { IdentityTokenResponse } from "../../../auth/models/response/identity-token.response";
|
||||
import { KeysRequest } from "../../../models/request/keys.request";
|
||||
import { KeyGenerationService } from "../../../platform/abstractions/key-generation.service";
|
||||
import { LogService } from "../../../platform/abstractions/log.service";
|
||||
@@ -133,19 +132,14 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction {
|
||||
}
|
||||
|
||||
async convertNewSsoUserToKeyConnector(
|
||||
tokenResponse: IdentityTokenResponse,
|
||||
orgId: string,
|
||||
userId: UserId,
|
||||
keyConnectorUrl: string,
|
||||
kdf: KdfType,
|
||||
kdfIterations: number,
|
||||
kdfMemory?: number,
|
||||
kdfParallelism?: number,
|
||||
) {
|
||||
// TODO: Remove after tokenResponse.keyConnectorUrl is deprecated in 2023.10 release (https://bitwarden.atlassian.net/browse/PM-3537)
|
||||
const {
|
||||
kdf,
|
||||
kdfIterations,
|
||||
kdfMemory,
|
||||
kdfParallelism,
|
||||
keyConnectorUrl: legacyKeyConnectorUrl,
|
||||
userDecryptionOptions,
|
||||
} = tokenResponse;
|
||||
const password = await this.keyGenerationService.createKey(512);
|
||||
const kdfConfig: KdfConfig =
|
||||
kdf === KdfType.PBKDF2_SHA256
|
||||
@@ -167,8 +161,6 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction {
|
||||
const [pubKey, privKey] = await this.keyService.makeKeyPair(userKey[0]);
|
||||
|
||||
try {
|
||||
const keyConnectorUrl =
|
||||
legacyKeyConnectorUrl ?? userDecryptionOptions?.keyConnectorOption?.keyConnectorUrl;
|
||||
await this.apiService.postUserKeyToKeyConnector(keyConnectorUrl, keyConnectorRequest);
|
||||
} catch (e) {
|
||||
this.handleKeyConnectorError(e);
|
||||
|
||||
@@ -5,3 +5,4 @@
|
||||
export { LockComponent } from "./lock/components/lock.component";
|
||||
export { LockComponentService, UnlockOptions } from "./lock/services/lock-component.service";
|
||||
export { RemovePasswordComponent } from "./key-connector/remove-password.component";
|
||||
export { ConfirmKeyConnectorDomainComponent } from "./key-connector/confirm-key-connector-domain.component";
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<div></div>
|
||||
@@ -0,0 +1,58 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||
import { ReactiveFormsModule } from "@angular/forms";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { Subject } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service";
|
||||
import {
|
||||
AsyncActionsModule,
|
||||
ButtonModule,
|
||||
FormFieldModule,
|
||||
IconButtonModule,
|
||||
} from "@bitwarden/components";
|
||||
|
||||
@Component({
|
||||
selector: "confirm-key-connector-domain",
|
||||
templateUrl: "confirm-key-connector-domain.component.html",
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule,
|
||||
JslibModule,
|
||||
ReactiveFormsModule,
|
||||
ButtonModule,
|
||||
FormFieldModule,
|
||||
AsyncActionsModule,
|
||||
IconButtonModule,
|
||||
],
|
||||
})
|
||||
export class ConfirmKeyConnectorDomainComponent implements OnInit, OnDestroy {
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private accountService: AccountService,
|
||||
private keyConnectorService: KeyConnectorService,
|
||||
) {
|
||||
// TODO
|
||||
// this.accountService.activeAccount$.pipe(takeUntil(this.destroy$)).subscribe((account) => {
|
||||
// console.log("[confirm-key-connector-domain]: account", account);
|
||||
// });
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
confirm = async () => {
|
||||
// this.keyConnectorService.convertNewSsoUserToKeyConnector();
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user