[SSO] New user provision flow (#173)

* Initial commit of new user sso flow

* Adjusted stateSplit conditional per review
This commit is contained in:
Vincent Salucci 2020-10-13 15:21:03 -05:00 committed by GitHub
parent 595215a9da
commit d84d6da7f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 63 additions and 11 deletions

View File

@ -1,4 +1,7 @@
import { Router } from '@angular/router'; import {
ActivatedRoute,
Router
} from '@angular/router';
import { ApiService } from '../../abstractions/api.service'; import { ApiService } from '../../abstractions/api.service';
import { CryptoService } from '../../abstractions/crypto.service'; import { CryptoService } from '../../abstractions/crypto.service';
@ -24,6 +27,7 @@ export class SetPasswordComponent extends BaseChangePasswordComponent {
syncLoading: boolean = true; syncLoading: boolean = true;
showPassword: boolean = false; showPassword: boolean = false;
hint: string = ''; hint: string = '';
identifier: string = null;
onSuccessfulChangePassword: () => Promise<any>; onSuccessfulChangePassword: () => Promise<any>;
successRoute = 'vault'; successRoute = 'vault';
@ -31,7 +35,7 @@ export class SetPasswordComponent extends BaseChangePasswordComponent {
constructor(i18nService: I18nService, cryptoService: CryptoService, messagingService: MessagingService, constructor(i18nService: I18nService, cryptoService: CryptoService, messagingService: MessagingService,
userService: UserService, passwordGenerationService: PasswordGenerationService, userService: UserService, passwordGenerationService: PasswordGenerationService,
platformUtilsService: PlatformUtilsService, policyService: PolicyService, private router: Router, 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, super(i18nService, cryptoService, messagingService, userService, passwordGenerationService,
platformUtilsService, policyService); platformUtilsService, policyService);
} }
@ -39,6 +43,17 @@ export class SetPasswordComponent extends BaseChangePasswordComponent {
async ngOnInit() { async ngOnInit() {
await this.syncService.fullSync(true); await this.syncService.fullSync(true);
this.syncLoading = false; 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(); super.ngOnInit();
} }
@ -57,6 +72,7 @@ export class SetPasswordComponent extends BaseChangePasswordComponent {
request.masterPasswordHint = this.hint; request.masterPasswordHint = this.hint;
request.kdf = this.kdf; request.kdf = this.kdf;
request.kdfIterations = this.kdfIterations; request.kdfIterations = this.kdfIterations;
request.orgIdentifier = this.identifier;
const keys = await this.cryptoService.makeKeyPair(encKey[0]); const keys = await this.cryptoService.makeKeyPair(encKey[0]);
request.keys = new KeysRequest(keys[0], keys[1].encryptedString); request.keys = new KeysRequest(keys[0], keys[1].encryptedString);

View File

@ -52,7 +52,7 @@ export class SsoComponent {
await this.storageService.remove(ConstantsService.ssoCodeVerifierKey); await this.storageService.remove(ConstantsService.ssoCodeVerifierKey);
await this.storageService.remove(ConstantsService.ssoStateKey); await this.storageService.remove(ConstantsService.ssoStateKey);
if (qParams.code != null && codeVerifier != null && state != null && state === qParams.state) { 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 && } else if (qParams.clientId != null && qParams.redirectUri != null && qParams.state != null &&
qParams.codeChallenge != null) { qParams.codeChallenge != null) {
@ -109,10 +109,14 @@ export class SsoComponent {
if (returnUri) { if (returnUri) {
state += `_returnUri='${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?' + let authorizeUrl = this.apiService.identityBaseUrl + '/connect/authorize?' +
'client_id=' + this.clientId + '&redirect_uri=' + encodeURIComponent(this.redirectUri) + '&' + 'client_id=' + this.clientId + '&redirect_uri=' + encodeURIComponent(this.redirectUri) + '&' +
'response_type=code&scope=api offline_access&' + 'response_type=code&scope=api offline_access&' +
@ -128,7 +132,7 @@ export class SsoComponent {
return authorizeUrl; return authorizeUrl;
} }
private async logIn(code: string, codeVerifier: string) { private async logIn(code: string, codeVerifier: string, orgIdFromState: string) {
this.loggingIn = true; this.loggingIn = true;
try { try {
this.formPromise = this.authService.logInSso(code, codeVerifier, this.redirectUri); this.formPromise = this.authService.logInSso(code, codeVerifier, this.redirectUri);
@ -140,7 +144,7 @@ export class SsoComponent {
} else { } else {
this.router.navigate([this.twoFactorRoute], { this.router.navigate([this.twoFactorRoute], {
queryParams: { queryParams: {
resetMasterPassword: response.resetMasterPassword, identifier: orgIdFromState,
}, },
}); });
} }
@ -149,7 +153,11 @@ export class SsoComponent {
if (this.onSuccessfulLoginChangePasswordNavigate != null) { if (this.onSuccessfulLoginChangePasswordNavigate != null) {
this.onSuccessfulLoginChangePasswordNavigate(); this.onSuccessfulLoginChangePasswordNavigate();
} else { } else {
this.router.navigate([this.changePasswordRoute]); this.router.navigate([this.changePasswordRoute], {
queryParams: {
identifier: orgIdFromState,
},
});
} }
} else { } else {
const disableFavicon = await this.storageService.get<boolean>(ConstantsService.disableFaviconKey); const disableFavicon = await this.storageService.get<boolean>(ConstantsService.disableFaviconKey);
@ -167,4 +175,13 @@ export class SsoComponent {
} catch { } } catch { }
this.loggingIn = false; this.loggingIn = false;
} }
private getOrgIdentiferFromState(state: string): string {
if (!state) {
return null;
}
const stateSplit = state.split('_identifier=');
return stateSplit.length > 1 ? stateSplit[1] : null;
}
} }

View File

@ -3,7 +3,10 @@ import {
OnInit, OnInit,
} from '@angular/core'; } from '@angular/core';
import { Router } from '@angular/router'; import {
ActivatedRoute,
Router,
} from '@angular/router';
import { DeviceType } from '../../enums/deviceType'; import { DeviceType } from '../../enums/deviceType';
import { TwoFactorProviderType } from '../../enums/twoFactorProviderType'; import { TwoFactorProviderType } from '../../enums/twoFactorProviderType';
@ -40,6 +43,7 @@ export class TwoFactorComponent implements OnInit, OnDestroy {
twoFactorEmail: string = null; twoFactorEmail: string = null;
formPromise: Promise<any>; formPromise: Promise<any>;
emailPromise: Promise<any>; emailPromise: Promise<any>;
identifier: string = null;
onSuccessfulLogin: () => Promise<any>; onSuccessfulLogin: () => Promise<any>;
onSuccessfulLoginNavigate: () => Promise<any>; onSuccessfulLoginNavigate: () => Promise<any>;
@ -50,7 +54,7 @@ export class TwoFactorComponent implements OnInit, OnDestroy {
protected i18nService: I18nService, protected apiService: ApiService, protected i18nService: I18nService, protected apiService: ApiService,
protected platformUtilsService: PlatformUtilsService, protected win: Window, protected platformUtilsService: PlatformUtilsService, protected win: Window,
protected environmentService: EnvironmentService, protected stateService: StateService, protected environmentService: EnvironmentService, protected stateService: StateService,
protected storageService: StorageService) { protected storageService: StorageService, protected route: ActivatedRoute) {
this.u2fSupported = this.platformUtilsService.supportsU2f(win); this.u2fSupported = this.platformUtilsService.supportsU2f(win);
} }
@ -61,6 +65,16 @@ export class TwoFactorComponent implements OnInit, OnDestroy {
return; 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()) { if (this.authService.authingWithSso()) {
this.successRoute = 'lock'; this.successRoute = 'lock';
} }
@ -191,7 +205,11 @@ export class TwoFactorComponent implements OnInit, OnDestroy {
if (response.resetMasterPassword) { if (response.resetMasterPassword) {
this.successRoute = 'set-password'; this.successRoute = 'set-password';
} }
this.router.navigate([this.successRoute]); this.router.navigate([this.successRoute], {
queryParams: {
identifier: this.identifier,
},
});
} }
} catch { } catch {
if (this.selectedProviderType === TwoFactorProviderType.U2f && this.u2f != null) { if (this.selectedProviderType === TwoFactorProviderType.U2f && this.u2f != null) {

View File

@ -9,4 +9,5 @@ export class SetPasswordRequest {
keys: KeysRequest; keys: KeysRequest;
kdf: KdfType; kdf: KdfType;
kdfIterations: number; kdfIterations: number;
orgIdentifier: string;
} }