From d84d6da7f77ec89ba69299b1ff982f083fd77203 Mon Sep 17 00:00:00 2001 From: Vincent Salucci <26154748+vincentsalucci@users.noreply.github.com> Date: Tue, 13 Oct 2020 15:21:03 -0500 Subject: [PATCH] [SSO] New user provision flow (#173) * Initial commit of new user sso flow * Adjusted stateSplit conditional per review --- .../components/set-password.component.ts | 20 +++++++++++-- src/angular/components/sso.component.ts | 29 +++++++++++++++---- .../components/two-factor.component.ts | 24 +++++++++++++-- src/models/request/setPasswordRequest.ts | 1 + 4 files changed, 63 insertions(+), 11 deletions(-) diff --git a/src/angular/components/set-password.component.ts b/src/angular/components/set-password.component.ts index 9db3941f08..6c29be00b7 100644 --- a/src/angular/components/set-password.component.ts +++ b/src/angular/components/set-password.component.ts @@ -1,4 +1,7 @@ -import { Router } from '@angular/router'; +import { + ActivatedRoute, + Router +} from '@angular/router'; import { ApiService } from '../../abstractions/api.service'; import { CryptoService } from '../../abstractions/crypto.service'; @@ -24,6 +27,7 @@ export class SetPasswordComponent extends BaseChangePasswordComponent { syncLoading: boolean = true; showPassword: boolean = false; hint: string = ''; + identifier: string = null; onSuccessfulChangePassword: () => Promise; successRoute = 'vault'; @@ -31,7 +35,7 @@ export class SetPasswordComponent extends BaseChangePasswordComponent { constructor(i18nService: I18nService, cryptoService: CryptoService, messagingService: MessagingService, userService: UserService, passwordGenerationService: PasswordGenerationService, platformUtilsService: PlatformUtilsService, policyService: PolicyService, private router: Router, - private apiService: ApiService, private syncService: SyncService) { + private apiService: ApiService, private syncService: SyncService, private route: ActivatedRoute) { super(i18nService, cryptoService, messagingService, userService, passwordGenerationService, platformUtilsService, policyService); } @@ -39,6 +43,17 @@ export class SetPasswordComponent extends BaseChangePasswordComponent { async ngOnInit() { await this.syncService.fullSync(true); this.syncLoading = false; + + const queryParamsSub = this.route.queryParams.subscribe(async (qParams) => { + if (qParams.identifier != null) { + this.identifier = qParams.identifier; + } + + if (queryParamsSub != null) { + queryParamsSub.unsubscribe(); + } + }); + super.ngOnInit(); } @@ -57,6 +72,7 @@ export class SetPasswordComponent extends BaseChangePasswordComponent { request.masterPasswordHint = this.hint; request.kdf = this.kdf; request.kdfIterations = this.kdfIterations; + request.orgIdentifier = this.identifier; const keys = await this.cryptoService.makeKeyPair(encKey[0]); request.keys = new KeysRequest(keys[0], keys[1].encryptedString); diff --git a/src/angular/components/sso.component.ts b/src/angular/components/sso.component.ts index 1b6bdb22ae..f5dbaab7dd 100644 --- a/src/angular/components/sso.component.ts +++ b/src/angular/components/sso.component.ts @@ -52,7 +52,7 @@ export class SsoComponent { await this.storageService.remove(ConstantsService.ssoCodeVerifierKey); await this.storageService.remove(ConstantsService.ssoStateKey); if (qParams.code != null && codeVerifier != null && state != null && state === qParams.state) { - await this.logIn(qParams.code, codeVerifier); + await this.logIn(qParams.code, codeVerifier, this.getOrgIdentiferFromState(state)); } } else if (qParams.clientId != null && qParams.redirectUri != null && qParams.state != null && qParams.codeChallenge != null) { @@ -109,10 +109,14 @@ export class SsoComponent { if (returnUri) { state += `_returnUri='${returnUri}'`; } - - await this.storageService.save(ConstantsService.ssoStateKey, state); } + // Add Organization Identifier to state + state += `_identifier=${this.identifier}`; + + // Save state (regardless of new or existing) + await this.storageService.save(ConstantsService.ssoStateKey, state); + let authorizeUrl = this.apiService.identityBaseUrl + '/connect/authorize?' + 'client_id=' + this.clientId + '&redirect_uri=' + encodeURIComponent(this.redirectUri) + '&' + 'response_type=code&scope=api offline_access&' + @@ -128,7 +132,7 @@ export class SsoComponent { return authorizeUrl; } - private async logIn(code: string, codeVerifier: string) { + private async logIn(code: string, codeVerifier: string, orgIdFromState: string) { this.loggingIn = true; try { this.formPromise = this.authService.logInSso(code, codeVerifier, this.redirectUri); @@ -140,7 +144,7 @@ export class SsoComponent { } else { this.router.navigate([this.twoFactorRoute], { queryParams: { - resetMasterPassword: response.resetMasterPassword, + identifier: orgIdFromState, }, }); } @@ -149,7 +153,11 @@ export class SsoComponent { if (this.onSuccessfulLoginChangePasswordNavigate != null) { this.onSuccessfulLoginChangePasswordNavigate(); } else { - this.router.navigate([this.changePasswordRoute]); + this.router.navigate([this.changePasswordRoute], { + queryParams: { + identifier: orgIdFromState, + }, + }); } } else { const disableFavicon = await this.storageService.get(ConstantsService.disableFaviconKey); @@ -167,4 +175,13 @@ export class SsoComponent { } catch { } this.loggingIn = false; } + + private getOrgIdentiferFromState(state: string): string { + if (!state) { + return null; + } + + const stateSplit = state.split('_identifier='); + return stateSplit.length > 1 ? stateSplit[1] : null; + } } diff --git a/src/angular/components/two-factor.component.ts b/src/angular/components/two-factor.component.ts index 72c92bdf89..4fe4e0c4b1 100644 --- a/src/angular/components/two-factor.component.ts +++ b/src/angular/components/two-factor.component.ts @@ -3,7 +3,10 @@ import { OnInit, } from '@angular/core'; -import { Router } from '@angular/router'; +import { + ActivatedRoute, + Router, +} from '@angular/router'; import { DeviceType } from '../../enums/deviceType'; import { TwoFactorProviderType } from '../../enums/twoFactorProviderType'; @@ -40,6 +43,7 @@ export class TwoFactorComponent implements OnInit, OnDestroy { twoFactorEmail: string = null; formPromise: Promise; emailPromise: Promise; + identifier: string = null; onSuccessfulLogin: () => Promise; onSuccessfulLoginNavigate: () => Promise; @@ -50,7 +54,7 @@ export class TwoFactorComponent implements OnInit, OnDestroy { protected i18nService: I18nService, protected apiService: ApiService, protected platformUtilsService: PlatformUtilsService, protected win: Window, protected environmentService: EnvironmentService, protected stateService: StateService, - protected storageService: StorageService) { + protected storageService: StorageService, protected route: ActivatedRoute) { this.u2fSupported = this.platformUtilsService.supportsU2f(win); } @@ -61,6 +65,16 @@ export class TwoFactorComponent implements OnInit, OnDestroy { return; } + const queryParamsSub = this.route.queryParams.subscribe(async (qParams) => { + if (qParams.identifier != null) { + this.identifier = qParams.identifier; + } + + if (queryParamsSub != null) { + queryParamsSub.unsubscribe(); + } + }); + if (this.authService.authingWithSso()) { this.successRoute = 'lock'; } @@ -191,7 +205,11 @@ export class TwoFactorComponent implements OnInit, OnDestroy { if (response.resetMasterPassword) { this.successRoute = 'set-password'; } - this.router.navigate([this.successRoute]); + this.router.navigate([this.successRoute], { + queryParams: { + identifier: this.identifier, + }, + }); } } catch { if (this.selectedProviderType === TwoFactorProviderType.U2f && this.u2f != null) { diff --git a/src/models/request/setPasswordRequest.ts b/src/models/request/setPasswordRequest.ts index 4907375121..fab35c4136 100644 --- a/src/models/request/setPasswordRequest.ts +++ b/src/models/request/setPasswordRequest.ts @@ -9,4 +9,5 @@ export class SetPasswordRequest { keys: KeysRequest; kdf: KdfType; kdfIterations: number; + orgIdentifier: string; }