2020-08-01 15:42:24 +02:00
|
|
|
import {
|
|
|
|
ActivatedRoute,
|
|
|
|
Router,
|
|
|
|
} from '@angular/router';
|
|
|
|
|
|
|
|
import { ApiService } from '../../abstractions/api.service';
|
|
|
|
import { AuthService } from '../../abstractions/auth.service';
|
|
|
|
import { CryptoFunctionService } from '../../abstractions/cryptoFunction.service';
|
|
|
|
import { I18nService } from '../../abstractions/i18n.service';
|
|
|
|
import { PasswordGenerationService } from '../../abstractions/passwordGeneration.service';
|
|
|
|
import { PlatformUtilsService } from '../../abstractions/platformUtils.service';
|
|
|
|
import { StateService } from '../../abstractions/state.service';
|
|
|
|
import { StorageService } from '../../abstractions/storage.service';
|
|
|
|
|
|
|
|
import { ConstantsService } from '../../services/constants.service';
|
|
|
|
|
|
|
|
import { Utils } from '../../misc/utils';
|
|
|
|
|
|
|
|
import { AuthResult } from '../../models/domain/authResult';
|
|
|
|
|
|
|
|
export class SsoComponent {
|
|
|
|
identifier: string;
|
|
|
|
loggingIn = false;
|
|
|
|
|
|
|
|
formPromise: Promise<AuthResult>;
|
2020-09-08 16:36:22 +02:00
|
|
|
initiateSsoFormPromise: Promise<any>;
|
2020-08-01 15:42:24 +02:00
|
|
|
onSuccessfulLogin: () => Promise<any>;
|
|
|
|
onSuccessfulLoginNavigate: () => Promise<any>;
|
|
|
|
onSuccessfulLoginTwoFactorNavigate: () => Promise<any>;
|
|
|
|
onSuccessfulLoginChangePasswordNavigate: () => Promise<any>;
|
|
|
|
|
|
|
|
protected twoFactorRoute = '2fa';
|
|
|
|
protected successRoute = 'lock';
|
2020-08-19 16:57:35 +02:00
|
|
|
protected changePasswordRoute = 'set-password';
|
2020-08-03 21:24:26 +02:00
|
|
|
protected clientId: string;
|
2020-08-01 15:42:24 +02:00
|
|
|
protected redirectUri: string;
|
2020-08-03 21:24:26 +02:00
|
|
|
protected state: string;
|
|
|
|
protected codeChallenge: string;
|
2020-08-01 15:42:24 +02:00
|
|
|
|
|
|
|
constructor(protected authService: AuthService, protected router: Router,
|
|
|
|
protected i18nService: I18nService, protected route: ActivatedRoute,
|
|
|
|
protected storageService: StorageService, protected stateService: StateService,
|
|
|
|
protected platformUtilsService: PlatformUtilsService, protected apiService: ApiService,
|
|
|
|
protected cryptoFunctionService: CryptoFunctionService,
|
|
|
|
protected passwordGenerationService: PasswordGenerationService) { }
|
|
|
|
|
|
|
|
async ngOnInit() {
|
2021-02-04 16:49:23 +01:00
|
|
|
const queryParamsSub = this.route.queryParams.subscribe(async qParams => {
|
2020-08-01 15:42:24 +02:00
|
|
|
if (qParams.code != null && qParams.state != null) {
|
|
|
|
const codeVerifier = await this.storageService.get<string>(ConstantsService.ssoCodeVerifierKey);
|
|
|
|
const state = await this.storageService.get<string>(ConstantsService.ssoStateKey);
|
|
|
|
await this.storageService.remove(ConstantsService.ssoCodeVerifierKey);
|
|
|
|
await this.storageService.remove(ConstantsService.ssoStateKey);
|
2020-11-23 22:12:28 +01:00
|
|
|
if (qParams.code != null && codeVerifier != null && state != null && this.checkState(state, qParams.state)) {
|
|
|
|
await this.logIn(qParams.code, codeVerifier, this.getOrgIdentiferFromState(qParams.state));
|
2020-08-01 15:42:24 +02:00
|
|
|
}
|
2020-08-03 21:24:26 +02:00
|
|
|
} else if (qParams.clientId != null && qParams.redirectUri != null && qParams.state != null &&
|
|
|
|
qParams.codeChallenge != null) {
|
|
|
|
this.redirectUri = qParams.redirectUri;
|
|
|
|
this.state = qParams.state;
|
|
|
|
this.codeChallenge = qParams.codeChallenge;
|
|
|
|
this.clientId = qParams.clientId;
|
2020-08-01 15:42:24 +02:00
|
|
|
}
|
|
|
|
if (queryParamsSub != null) {
|
|
|
|
queryParamsSub.unsubscribe();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-08-27 17:00:05 +02:00
|
|
|
async submit(returnUri?: string, includeUserIdentifier?: boolean) {
|
2020-09-08 16:36:22 +02:00
|
|
|
this.initiateSsoFormPromise = this.preValidate();
|
|
|
|
if (await this.initiateSsoFormPromise) {
|
|
|
|
const authorizeUrl = await this.buildAuthorizeUrl(returnUri, includeUserIdentifier);
|
|
|
|
this.platformUtilsService.launchUri(authorizeUrl, { sameWindow: true });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async preValidate(): Promise<boolean> {
|
|
|
|
if (this.identifier == null || this.identifier === '') {
|
|
|
|
this.platformUtilsService.showToast('error', this.i18nService.t('ssoValidationFailed'),
|
|
|
|
this.i18nService.t('ssoIdentifierRequired'));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return await this.apiService.preValidateSso(this.identifier);
|
2020-08-27 17:00:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
protected async buildAuthorizeUrl(returnUri?: string, includeUserIdentifier?: boolean): Promise<string> {
|
|
|
|
let codeChallenge = this.codeChallenge;
|
|
|
|
let state = this.state;
|
|
|
|
|
2020-08-01 15:42:24 +02:00
|
|
|
const passwordOptions: any = {
|
|
|
|
type: 'password',
|
2020-09-04 22:01:54 +02:00
|
|
|
length: 64,
|
2020-08-01 15:42:24 +02:00
|
|
|
uppercase: true,
|
|
|
|
lowercase: true,
|
|
|
|
numbers: true,
|
|
|
|
special: false,
|
|
|
|
};
|
2020-08-27 17:00:05 +02:00
|
|
|
|
2020-08-03 21:24:26 +02:00
|
|
|
if (codeChallenge == null) {
|
|
|
|
const codeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions);
|
|
|
|
const codeVerifierHash = await this.cryptoFunctionService.hash(codeVerifier, 'sha256');
|
|
|
|
codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash);
|
|
|
|
await this.storageService.save(ConstantsService.ssoCodeVerifierKey, codeVerifier);
|
|
|
|
}
|
2020-08-27 17:00:05 +02:00
|
|
|
|
2020-08-03 21:24:26 +02:00
|
|
|
if (state == null) {
|
|
|
|
state = await this.passwordGenerationService.generatePassword(passwordOptions);
|
2020-08-27 17:00:05 +02:00
|
|
|
if (returnUri) {
|
|
|
|
state += `_returnUri='${returnUri}'`;
|
|
|
|
}
|
2020-08-03 21:24:26 +02:00
|
|
|
}
|
2020-08-01 15:42:24 +02:00
|
|
|
|
2020-10-13 22:21:03 +02:00
|
|
|
// Add Organization Identifier to state
|
|
|
|
state += `_identifier=${this.identifier}`;
|
|
|
|
|
|
|
|
// Save state (regardless of new or existing)
|
|
|
|
await this.storageService.save(ConstantsService.ssoStateKey, state);
|
|
|
|
|
2020-08-27 17:00:05 +02:00
|
|
|
let authorizeUrl = this.apiService.identityBaseUrl + '/connect/authorize?' +
|
2020-08-03 21:24:26 +02:00
|
|
|
'client_id=' + this.clientId + '&redirect_uri=' + encodeURIComponent(this.redirectUri) + '&' +
|
2020-08-01 15:42:24 +02:00
|
|
|
'response_type=code&scope=api offline_access&' +
|
|
|
|
'state=' + state + '&code_challenge=' + codeChallenge + '&' +
|
|
|
|
'code_challenge_method=S256&response_mode=query&' +
|
|
|
|
'domain_hint=' + encodeURIComponent(this.identifier);
|
2020-08-27 17:00:05 +02:00
|
|
|
|
|
|
|
if (includeUserIdentifier) {
|
|
|
|
const userIdentifier = await this.apiService.getSsoUserIdentifier();
|
|
|
|
authorizeUrl += `&user_identifier=${encodeURIComponent(userIdentifier)}`;
|
|
|
|
}
|
|
|
|
|
|
|
|
return authorizeUrl;
|
2020-08-01 15:42:24 +02:00
|
|
|
}
|
|
|
|
|
2020-10-13 22:21:03 +02:00
|
|
|
private async logIn(code: string, codeVerifier: string, orgIdFromState: string) {
|
2020-08-01 15:42:24 +02:00
|
|
|
this.loggingIn = true;
|
|
|
|
try {
|
|
|
|
this.formPromise = this.authService.logInSso(code, codeVerifier, this.redirectUri);
|
|
|
|
const response = await this.formPromise;
|
|
|
|
if (response.twoFactor) {
|
|
|
|
if (this.onSuccessfulLoginTwoFactorNavigate != null) {
|
|
|
|
this.onSuccessfulLoginTwoFactorNavigate();
|
|
|
|
} else {
|
|
|
|
this.router.navigate([this.twoFactorRoute], {
|
|
|
|
queryParams: {
|
2020-10-13 22:21:03 +02:00
|
|
|
identifier: orgIdFromState,
|
2021-02-08 21:11:44 +01:00
|
|
|
sso: 'true',
|
2020-08-01 15:42:24 +02:00
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
|
|
|
} else if (response.resetMasterPassword) {
|
|
|
|
if (this.onSuccessfulLoginChangePasswordNavigate != null) {
|
|
|
|
this.onSuccessfulLoginChangePasswordNavigate();
|
|
|
|
} else {
|
2020-10-13 22:21:03 +02:00
|
|
|
this.router.navigate([this.changePasswordRoute], {
|
|
|
|
queryParams: {
|
|
|
|
identifier: orgIdFromState,
|
|
|
|
},
|
|
|
|
});
|
2020-08-01 15:42:24 +02:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
const disableFavicon = await this.storageService.get<boolean>(ConstantsService.disableFaviconKey);
|
|
|
|
await this.stateService.save(ConstantsService.disableFaviconKey, !!disableFavicon);
|
|
|
|
if (this.onSuccessfulLogin != null) {
|
|
|
|
this.onSuccessfulLogin();
|
|
|
|
}
|
|
|
|
if (this.onSuccessfulLoginNavigate != null) {
|
|
|
|
this.onSuccessfulLoginNavigate();
|
|
|
|
} else {
|
|
|
|
this.router.navigate([this.successRoute]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch { }
|
|
|
|
this.loggingIn = false;
|
|
|
|
}
|
2020-10-13 22:21:03 +02:00
|
|
|
|
|
|
|
private getOrgIdentiferFromState(state: string): string {
|
2020-11-23 22:12:28 +01:00
|
|
|
if (state === null || state === undefined) {
|
2020-10-13 22:21:03 +02:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
const stateSplit = state.split('_identifier=');
|
|
|
|
return stateSplit.length > 1 ? stateSplit[1] : null;
|
|
|
|
}
|
2020-11-23 22:12:28 +01:00
|
|
|
|
|
|
|
private checkState(state: string, checkState: string): boolean {
|
|
|
|
if (state === null || state === undefined) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (checkState === null || checkState === undefined) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
const stateSplit = state.split('_identifier=');
|
|
|
|
const checkStateSplit = checkState.split('_identifier=');
|
|
|
|
return stateSplit[0] === checkStateSplit[0];
|
|
|
|
}
|
2020-08-01 15:42:24 +02:00
|
|
|
}
|