UNPKG

@dbg-riskit/angular-auth

Version:

457 lines 68.5 kB
import { Inject, Injectable } from '@angular/core'; import { AuthTokenType, ErrorType, ReplaySubjectExt, toString } from '@dbg-riskit/common'; import { HttpService } from '@dbg-riskit/angular-http'; import { EMPTY, of, throwError } from 'rxjs'; import { catchError, defaultIfEmpty, first, map, shareReplay, switchMap, tap } from 'rxjs/operators'; import { AUTH_CONFIG, AuthFlow } from './auth.config'; import { JwtHelper } from './jwt.helper'; import { NonceGenerator } from './nonce.generator'; import { RequestUtils } from './request.utils'; import * as i0 from "@angular/core"; import * as i1 from "./well.known.service"; import * as i2 from "@dbg-riskit/angular-http"; import * as i3 from "./auth.storage.service"; import * as i4 from "@angular/common"; export const AUTH_CHECK_INTERVAL = 60000; const OPEN_ID_SCOPE = 'openid'; const DEFAULT_OPEN_ID_SCOPES = ['profile', 'email', 'address', 'phone']; const UNSUPPORTED_AUTH_FLOW_TYPE = 'Unsupported auth flow type!'; function unsupportedFlowTypeError() { return new Error(UNSUPPORTED_AUTH_FLOW_TYPE); } export class AuthService { constructor(authConfig, wellKnownService, http, storage, location) { this.authConfig = authConfig; this.wellKnownService = wellKnownService; this.http = http; this.storage = storage; this._loggedInStream = new ReplaySubjectExt(1); // Use false only if === false this.authConfig.useNonce = this.authConfig.useNonce !== false; this.redirectURL = RequestUtils.getBaseURL(location, this.authConfig.loginRoute); this.openIDScope = [OPEN_ID_SCOPE].concat(this.authConfig.scope || DEFAULT_OPEN_ID_SCOPES).join(' '); // Try to load tokens and process them this._initService = this.wellKnownService.wellKnown.pipe(switchMap((wellKnown) => this.processToken({ id_token: this.storage.idToken, access_token: this.storage.accessToken, refresh_token: this.storage.refreshToken }, false) // Do not sync time as we are now in the future ;) .pipe(catchError(() => this.tryToRefresh()), defaultIfEmpty(0), map(() => wellKnown))), shareReplay(1)); } get loggedIn() { return this._initService.pipe(map(() => !!this.tokenData)); } get userProfile() { return this._initService.pipe(map(() => this.tokenData)); } get loggedInStream() { return this._loggedInStream.asObservable(); } emitLoginStatusChange(status) { if (this._loggedInStream.lastValue !== status) { this._loggedInStream.next(status); } } // <editor-fold defaultstate="collapsed" desc="Indirect login"> loginViaAuthService() { return this.wellKnownService.wellKnown.pipe(switchMap((wellKnown) => NonceGenerator.generateNonce().pipe(map((nonce) => ({ wellKnown, nonce })))), map(({ wellKnown, nonce }) => { let responseType; switch (this.authConfig.flow) { case AuthFlow.AUTHORIZATION_CODE: responseType = 'code'; break; case AuthFlow.IMPLICIT: responseType = 'id_token token'; break; case AuthFlow.HYBRID: responseType = 'code id_token'; break; default: throw unsupportedFlowTypeError(); } let location = `${wellKnown.endpoints.auth}?response_type=${encodeURIComponent(responseType)}&scope=${encodeURIComponent(this.openIDScope)}&client_id=${encodeURIComponent(this.authConfig.clientID)}&redirect_uri=${encodeURIComponent(this.redirectURL)}`; if (this.authConfig.useNonce) { this.storage.nonce = nonce; location += `&nonce=${encodeURIComponent(nonce)}`; } RequestUtils.locationHref = location; return true; })); } checkLocationForLoginData() { return this.wellKnownService.wellKnown.pipe(switchMap(() => { const params = RequestUtils.getOpenIDQueryParams(); if (params.error) { return throwError({ message: `(${params.error}) ${params.errorDesc}`, status: 500, errorType: ErrorType.AUTH }); } switch (this.authConfig.flow) { case AuthFlow.HYBRID: return this.checkParametersHybridFlow(params); case AuthFlow.AUTHORIZATION_CODE: return this.checkParametersAuthCodeFlow(params); case AuthFlow.IMPLICIT: return this.checkParametersImplicitFlow(params); default: return throwError(unsupportedFlowTypeError()); } })); } checkParametersHybridFlow(params) { if (!params.id_token || !params.code) { return throwError({ message: 'Authentication server did not sent required data.', status: 500, errorType: ErrorType.AUTH }); } // Login using token first return this.processToken({ id_token: params.id_token }).pipe(tap(() => { // Request permanent token + refresh token this.requestTokenBasedOnCode(params.code, this.redirectURL).pipe(catchError(() => this.logout())).subscribe((tokenValid) => { if (!tokenValid) { this.logout().pipe(first()).subscribe(); } }); })); } checkParametersAuthCodeFlow(params) { if (!params.code) { return throwError({ message: 'Authentication server did not send required data.', status: 500, errorType: ErrorType.AUTH }); } // Request token + refresh token return this.requestTokenBasedOnCode(params.code, this.redirectURL); } checkParametersImplicitFlow(params) { if (!params.id_token || !params.access_token) { return throwError({ message: 'Authentication server did not send required data.', status: 500, errorType: ErrorType.AUTH }); } // Login using id_token and access_token. Refresh is not permited here return this.processToken({ id_token: params.id_token, access_token: params.access_token }); } requestTokenBasedOnCode(code, redirectURI) { return this.wellKnownService.wellKnown.pipe(switchMap((wellKnown) => this.http.post({ resourceURL: wellKnown.endpoints.token, data: HttpService.toHttpParams({ code, redirect_uri: redirectURI, grant_type: 'authorization_code', nonce: this.authConfig.useNonce ? this.storage.nonce : null, client_id: this.authConfig.clientID, client_secret: this.authConfig.clientSecret }), tokenType: AuthTokenType.NONE, endpoint: '' // Do not use any prefix })), switchMap((response) => this.processToken(response))); } // </editor-fold> // <editor-fold defaultstate="collapsed" desc="Direct login"> directLogin(username, password) { return this.wellKnownService.wellKnown.pipe(switchMap((wellKnown) => this.http.post({ resourceURL: wellKnown.endpoints.token, data: HttpService.toHttpParams({ username, password, grant_type: 'password', scope: this.openIDScope, client_id: this.authConfig.clientID, client_secret: this.authConfig.clientSecret }), tokenType: AuthTokenType.NONE, endpoint: '' // Do not use any prefix })), switchMap((response) => this.processToken(response))); } // </editor-fold> processToken(response, syncTime = true) { return this.wellKnownService.wellKnown.pipe(switchMap(() => { if (this.validateResponseNonce(response)) { return throwError({ status: 401, message: 'Non-matching nonce.', errorType: ErrorType.AUTH }); } if (AuthService.validateResponseTokenType(response)) { return throwError({ status: 401, message: 'Invalid token type.', errorType: ErrorType.AUTH }); } return this.storeTokensIfValid(response, syncTime); })); } validateResponseNonce(response) { return this.authConfig.useNonce && response.nonce && response.nonce !== this.storage.nonce; } static validateResponseTokenType(response) { return response.token_type && response.token_type.toLocaleLowerCase() !== 'bearer'; } storeTokensIfValid(response, syncTime) { if (response.id_token == null) { return throwError({ status: 401, message: 'Authentication failed. Server did not generate a token.', errorType: ErrorType.AUTH }); } this.storage.idToken = response.id_token; const chain = this.validateToken(response.id_token).pipe(tap((tokenData) => { this.tokenData = tokenData; })); if (response.access_token != null) { chain.pipe(switchMap(() => this.validateToken(response.access_token))); } return chain.pipe(map(() => { // store username and token in local storage to keep user logged in between page refreshes this.storage.idToken = response.id_token; this.storage.accessToken = response.access_token; if (response.refresh_token) { this.storage.refreshToken = response.refresh_token; } if (syncTime) { // Sync our local time with the auth server time as we need to refresh tokens // at correct time point. We use token iat here as this is the // "current server time" - "request duration (ignored as it is small, max. few sec.)". this.storage.syncTime(); } this.afterLogin(); return true; })); } afterLogin() { switch (this.authConfig.flow) { case AuthFlow.DIRECT: case AuthFlow.AUTHORIZATION_CODE: this.setupAuthCheck(); this.setupTokenRefresh(); this.emitLoginStatusChange(true); break; case AuthFlow.HYBRID: if (this.storage.accessToken) { this.setupAuthCheck(); this.setupTokenRefresh(); } this.emitLoginStatusChange(true); break; case AuthFlow.IMPLICIT: this.setupAuthCheck(); this.emitLoginStatusChange(true); break; default: throw unsupportedFlowTypeError(); } } logout() { const logout = () => { // remove user from local storage and clear http auth header delete this.tokenData; this.storage.clear(); this.disableAuthCheck(); this.disableTokenRefresh(); this.emitLoginStatusChange(false); return EMPTY; }; const idToken = this.storage.idToken; if (idToken) { // Send logout request first... return this.wellKnownService.wellKnown.pipe(switchMap((wellKnown) => this.http.get({ resourceURL: wellKnown.endpoints.logout, params: { id_token_hint: idToken }, tokenType: AuthTokenType.NONE, endpoint: '' // Do not use any prefix })), catchError(logout), switchMap(logout)); } else { return this.wellKnownService.wellKnown.pipe(catchError(logout), switchMap(logout)); } } // <editor-fold defaultstate="collapsed" desc="Periodic Auth check"> setupAuthCheck() { this.disableAuthCheck(); this.authCheckInterval = setInterval(() => this.checkAuth(), AUTH_CHECK_INTERVAL); } disableAuthCheck() { if (this.authCheckInterval != null) { clearInterval(this.authCheckInterval); this.authCheckInterval = null; } } checkAuth() { this.wellKnownService.wellKnown.pipe(switchMap(() => { if (!this.tokenData || !this.storage.idToken || !this.storage.accessToken || this.storage.refresh_in <= 0) { return this.logout(); } return of(true); })).subscribe(); } // </editor-fold> // <editor-fold defaultstate="collapsed" desc="Token refresh"> setupTokenRefresh() { this.disableTokenRefresh(); this.refreshTimeout = setTimeout(() => this.refreshToken(), this.storage.refresh_in); } disableTokenRefresh() { if (this.refreshTimeout != null) { clearTimeout(this.refreshTimeout); this.refreshTimeout = null; } } tryToRefresh() { return this.wellKnownService.wellKnown.pipe(switchMap((wellKnown) => { if (!this.storage.refreshToken) { return throwError({ message: 'Could not refresh. Not logged in!', status: 500, errorType: ErrorType.AUTH }); } return this.http.post({ resourceURL: wellKnown.endpoints.token, data: HttpService.toHttpParams({ grant_type: 'refresh_token', client_id: this.authConfig.clientID, client_secret: this.authConfig.clientSecret, refresh_token: this.storage.refreshToken }), tokenType: AuthTokenType.NONE, endpoint: '' // Do not use any prefix }); }), switchMap((response) => this.processToken(response)), catchError(() => of(false))); } refreshToken() { this.tryToRefresh().pipe(switchMap((refreshed) => { if (!refreshed) { return this.logout(); } return EMPTY; })).subscribe(); } // </editor-fold> // <editor-fold defaultstate="collapsed" desc="Token validation"> validateToken(token) { return this.wellKnownService.wellKnown.pipe(switchMap((wellKnown) => { function throwCatchedError(err) { return throwError({ status: 500, message: err ? toString(err) : 'Error parsing token from auth response!', errorType: ErrorType.AUTH }); } try { const tokenData = JwtHelper.decodeToken(token); // Check expiration return AuthService.checkTokenExpiration(token).pipe( // Check nonce switchMap(() => this.checkTokenNonce(tokenData)), // Check issuer switchMap(() => AuthService.checkTokenIssuer(tokenData, wellKnown)), // Check audience switchMap(() => this.checkTokenAudience(tokenData)), map(() => tokenData), catchError(throwCatchedError)); } catch (err) { return throwCatchedError(err); } })); } static checkTokenExpiration(token) { if (JwtHelper.isTokenExpired(token)) { return throwError({ status: 500, message: 'Invalid token expiration!', errorType: ErrorType.AUTH }); } return of(undefined); } checkTokenNonce(tokenData) { if (this.authConfig.useNonce) { switch (this.authConfig.flow) { case AuthFlow.AUTHORIZATION_CODE: case AuthFlow.HYBRID: case AuthFlow.IMPLICIT: if (tokenData.nonce !== this.storage.nonce) { return throwError({ status: 500, message: 'Non-matching nonce!', errorType: ErrorType.AUTH }); } break; case AuthFlow.DIRECT: // No check here - nonce not supported break; default: return throwError({ status: 500, message: UNSUPPORTED_AUTH_FLOW_TYPE, errorType: ErrorType.AUTH }); } } return of(undefined); } static checkTokenIssuer(tokenData, wellKnown) { if (tokenData.iss !== wellKnown.issuer) { return throwError({ status: 500, message: 'Invalid token issuer!', errorType: ErrorType.AUTH }); } return of(undefined); } checkTokenAudience(tokenData) { if (Array.isArray(tokenData.aud)) { if (!tokenData.aud.some((aud) => aud === this.authConfig.clientID) || tokenData.azp !== this.authConfig.clientID) { return throwError({ status: 500, message: 'Invalid token audience!', errorType: ErrorType.AUTH }); } } else if (tokenData.aud !== this.authConfig.clientID) { return throwError({ status: 500, message: 'Invalid token audience!', errorType: ErrorType.AUTH }); } return of(undefined); } } AuthService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.2.6", ngImport: i0, type: AuthService, deps: [{ token: AUTH_CONFIG }, { token: i1.WellKnownService }, { token: i2.HttpService }, { token: i3.AuthStorageService }, { token: i4.Location }], target: i0.ɵɵFactoryTarget.Injectable }); AuthService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "13.2.6", ngImport: i0, type: AuthService }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.2.6", ngImport: i0, type: AuthService, decorators: [{ type: Injectable }], ctorParameters: function () { return [{ type: undefined, decorators: [{ type: Inject, args: [AUTH_CONFIG] }] }, { type: i1.WellKnownService }, { type: i2.HttpService }, { type: i3.AuthStorageService }, { type: i4.Location }]; } }); //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXV0aC5zZXJ2aWNlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vcGtnL2RiZy1yaXNraXQvYW5ndWxhci1hdXRoL3NyYy9saWIvYXV0aC5zZXJ2aWNlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUNBLE9BQU8sRUFBQyxNQUFNLEVBQUUsVUFBVSxFQUFDLE1BQU0sZUFBZSxDQUFDO0FBQ2pELE9BQU8sRUFBZSxhQUFhLEVBQUUsU0FBUyxFQUFFLGdCQUFnQixFQUFFLFFBQVEsRUFBVyxNQUFNLG9CQUFvQixDQUFDO0FBQ2hILE9BQU8sRUFBQyxXQUFXLEVBQUMsTUFBTSwwQkFBMEIsQ0FBQztBQUNyRCxPQUFPLEVBQUMsS0FBSyxFQUFjLEVBQUUsRUFBRSxVQUFVLEVBQUMsTUFBTSxNQUFNLENBQUM7QUFDdkQsT0FBTyxFQUFDLFVBQVUsRUFBRSxjQUFjLEVBQUUsS0FBSyxFQUFFLEdBQUcsRUFBRSxXQUFXLEVBQUUsU0FBUyxFQUFFLEdBQUcsRUFBQyxNQUFNLGdCQUFnQixDQUFDO0FBQ25HLE9BQU8sRUFBQyxXQUFXLEVBQWMsUUFBUSxFQUFDLE1BQU0sZUFBZSxDQUFDO0FBRWhFLE9BQU8sRUFBQyxTQUFTLEVBQUMsTUFBTSxjQUFjLENBQUM7QUFDdkMsT0FBTyxFQUFDLGNBQWMsRUFBQyxNQUFNLG1CQUFtQixDQUFDO0FBQ2pELE9BQU8sRUFBb0IsWUFBWSxFQUFDLE1BQU0saUJBQWlCLENBQUM7Ozs7OztBQUloRSxNQUFNLENBQUMsTUFBTSxtQkFBbUIsR0FBRyxLQUFLLENBQUM7QUFFekMsTUFBTSxhQUFhLEdBQUcsUUFBUSxDQUFDO0FBQy9CLE1BQU0sc0JBQXNCLEdBQUcsQ0FBQyxTQUFTLEVBQUUsT0FBTyxFQUFFLFNBQVMsRUFBRSxPQUFPLENBQUMsQ0FBQztBQUV4RSxNQUFNLDBCQUEwQixHQUFHLDZCQUE2QixDQUFDO0FBRWpFLFNBQVMsd0JBQXdCO0lBQzdCLE9BQU8sSUFBSSxLQUFLLENBQUMs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Q0FBQyxDQUFDO2FBQ047U0FFSjthQUFNLElBQUksU0FBUyxDQUFDLEdBQUcsS0FBSyxJQUFJLENBQUMsVUFBVSxDQUFDLFFBQVEsRUFBRTtZQUNuRCxPQUFPLFVBQVUsQ0FBQztnQkFDZCxNQUFNLEVBQUssR0FBRztnQkFDZCxPQUFPLEVBQUkseUJBQXlCO2dCQUNwQyxTQUFTLEVBQUUsU0FBUyxDQUFDLElBQUk7YUFDNUIsQ0FBQyxDQUFDO1NBQ047UUFFRCxPQUFPLEVBQUUsQ0FBQyxTQUFTLENBQUMsQ0FBQztJQUN6QixDQUFDOzt3R0FwaUJRLFdBQVcsa0JBYU8sV0FBVzs0R0FiN0IsV0FBVzsyRkFBWCxXQUFXO2tCQUR2QixVQUFVOzswQkFjYSxNQUFNOzJCQUFDLFdBQVciLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQge0xvY2F0aW9ufSBmcm9tICdAYW5ndWxhci9jb21tb24nO1xuaW1wb3J0IHtJbmplY3QsIEluamVjdGFibGV9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xuaW1wb3J0IHtBdXRoUHJvdmlkZXIsIEF1dGhUb2tlblR5cGUsIEVycm9yVHlwZSwgUmVwbGF5U3ViamVjdEV4dCwgdG9TdHJpbmcsIFVzZXJJbmZvfSBmcm9tICdAZGJnLXJpc2tpdC9jb21tb24nO1xuaW1wb3J0IHtIdHRwU2VydmljZX0gZnJvbSAnQGRiZy1yaXNraXQvYW5ndWxhci1odHRwJztcbmltcG9ydCB7RU1QVFksIE9ic2VydmFibGUsIG9mLCB0aHJvd0Vycm9yfSBmcm9tICdyeGpzJztcbmltcG9ydCB7Y2F0Y2hFcnJvciwgZGVmYXVsdElmRW1wdHksIGZpcnN0LCBtYXAsIHNoYXJlUmVwbGF5LCBzd2l0Y2hNYXAsIHRhcH0gZnJvbSAncnhqcy9vcGVyYXRvcnMnO1xuaW1wb3J0IHtBVVRIX0NPTkZJRywgQXV0aENvbmZpZywgQXV0aEZsb3d9IGZyb20gJy4vYXV0aC5jb25maWcnO1xuaW1wb3J0IHtBdXRoU3RvcmFnZVNlcnZpY2V9IGZyb20gJy4vYXV0aC5zdG9yYWdlLnNlcnZpY2UnO1xuaW1wb3J0IHtKd3RIZWxwZXJ9IGZyb20gJy4vand0LmhlbHBlcic7XG5pbXBvcnQge05vbmNlR2VuZXJhdG9yfSBmcm9tICcuL25vbmNlLmdlbmVyYXRvcic7XG5pbXBvcnQge09wZW5JRFF1ZXJ5UGFyYW1zLCBSZXF1ZXN0VXRpbHN9IGZyb20gJy4vcmVxdWVzdC51dGlscyc7XG5pbXBvcnQge0F1dGhSZXNwb25zZSwgVG9rZW5EYXRhfSBmcm9tICcuL3Rva2VuLnJlc3BvbnNlcyc7XG5pbXBvcnQge1dlbGxLbm93biwgV2VsbEtub3duU2VydmljZX0gZnJvbSAnLi93ZWxsLmtub3duLnNlcnZpY2UnO1xuXG5leHBvcnQgY29uc3QgQVVUSF9DSEVDS19JTlRFUlZBTCA9IDYwMDAwO1xuXG5jb25zdCBPUEVOX0lEX1NDT1BFID0gJ29wZW5pZCc7XG5jb25zdCBERUZBVUxUX09QRU5fSURfU0NPUEVTID0gWydwcm9maWxlJywgJ2VtYWlsJywgJ2FkZHJlc3MnLCAncGhvbmUnXTtcblxuY29uc3QgVU5TVVBQT1JURURfQVVUSF9GTE9XX1RZUEUgPSAnVW5zdXBwb3J0ZWQgYXV0aCBmbG93IHR5cGUhJztcblxuZnVuY3Rpb24gdW5zdXBwb3J0ZWRGbG93VHlwZUVycm9yKCk6IEVycm9yIHtcbiAgICByZXR1cm4gbmV3IEVycm9yKFVOU1VQUE9SVEVEX0FVVEhfRkxPV19UWVBFKTtcbn1cblxuQEluamVjdGFibGUoKVxuZXhwb3J0IGNsYXNzIEF1dGhTZXJ2aWNlIGltcGxlbWVudHMgQXV0aFByb3ZpZGVyIHtcblxuICAgIHByaXZhdGUgcmVhZG9ubHkgX2xvZ2dlZEluU3RyZWFtOiBSZXBsYXlTdWJqZWN0RXh0PGJvb2xlYW4+ID0gbmV3IFJlcGxheVN1YmplY3RFeHQ8Ym9vbGVhbj4oMSk7XG5cbiAgICBwcml2YXRlIHRva2VuRGF0YT86IFVzZXJJbmZvO1xuXG4gICAgcHJpdmF0ZSByZWZyZXNoVGltZW91dD86IE5vZGVKUy5UaW1lciB8IG51bGw7XG4gICAgcHJpdmF0ZSBhdXRoQ2hlY2tJbnRlcnZhbD86IE5vZGVKUy5UaW1lciB8IG51bGw7XG5cbiAgICBwcml2YXRlIHJlYWRvbmx5IHJlZGlyZWN0VVJMOiBzdHJpbmc7XG4gICAgcHJpdmF0ZSByZWFkb25seSBvcGVuSURTY29wZTogc3RyaW5nO1xuICAgIHByaXZhdGUgcmVhZG9ubHkgX2luaXRTZXJ2aWNlOiBPYnNlcnZhYmxlPFdlbGxLbm93bj47XG5cbiAgICBwdWJsaWMgY29uc3RydWN0b3IoQEluamVjdChBVVRIX0NPTkZJRykgcHJpdmF0ZSByZWFkb25seSBhdXRoQ29uZmlnOiBBdXRoQ29uZmlnLFxuICAgICAgICAgICAgICAgICAgICAgICBwcml2YXRlIHJlYWRvbmx5IHdlbGxLbm93blNlcnZpY2U6IFdlbGxLbm93blNlcnZpY2UsXG4gICAgICAgICAgICAgICAgICAgICAgIHByaXZhdGUgcmVhZG9ubHkgaHR0cDogSHR0cFNlcnZpY2UsXG4gICAgICAgICAgICAgICAgICAgICAgIHByaXZhdGUgcmVhZG9ubHkgc3RvcmFnZTogQXV0aFN0b3JhZ2VTZXJ2aWNlLFxuICAgICAgICAgICAgICAgICAgICAgICBsb2NhdGlvbjogTG9jYXRpb24pIHtcblxuICAgICAgICAvLyBVc2UgZmFsc2Ugb25seSBpZiA9PT0gZmFsc2VcbiAgICAgICAgdGhpcy5hdXRoQ29uZmlnLnVzZU5vbmNlID0gdGhpcy5hdXRoQ29uZmlnLnVzZU5vbmNlICE9PSBmYWxzZTtcblxuICAgICAgICB0aGlzLnJlZGlyZWN0VVJMID0gUmVxdWVzdFV0aWxzLmdldEJhc2VVUkwobG9jYXRpb24sIHRoaXMuYXV0aENvbmZpZy5sb2dpblJvdXRlKTtcbiAgICAgICAgdGhpcy5vcGVuSURTY29wZSA9IFtPUEVOX0lEX1NDT1BFXS5jb25jYXQodGhpcy5hdXRoQ29uZmlnLnNjb3BlIHx8IERFRkFVTFRfT1BFTl9JRF9TQ09QRVMpLmpvaW4oJyAnKTtcblxuICAgICAgICAvLyBUcnkgdG8gbG9hZCB0b2tlbnMgYW5kIHByb2Nlc3MgdGhlbVxuICAgICAgICB0aGlzLl9pbml0U2VydmljZSA9IHRoaXMud2VsbEtub3duU2VydmljZS53ZWxsS25vd24ucGlwZShcbiAgICAgICAgICAgIHN3aXRjaE1hcCgod2VsbEtub3duOiBXZWxsS25vd24pID0+IHRoaXMucHJvY2Vzc1Rva2VuKHtcbiAgICAgICAgICAgICAgICAgICAgaWRfdG9rZW4gICAgIDogdGhpcy5zdG9yYWdlLmlkVG9rZW4sXG4gICAgICAgICAgICAgICAgICAgIGFjY2Vzc190b2tlbiA6IHRoaXMuc3RvcmFnZS5hY2Nlc3NUb2tlbixcbiAgICAgICAgICAgICAgICAgICAgcmVmcmVzaF90b2tlbjogdGhpcy5zdG9yYWdlLnJlZnJlc2hUb2tlblxuICAgICAgICAgICAgICAgIH0sIGZhbHNlKSAvLyBEbyBub3Qgc3luYyB0aW1lIGFzIHdlIGFyZSBub3cgaW4gdGhlIGZ1dHVyZSA7KVxuICAgICAgICAgICAgICAgICAgICAucGlwZShcbiAgICAgICAgICAgICAgICAgICAgICAgIGNhdGNoRXJyb3IoKCkgPT4gdGhpcy50cnlUb1JlZnJlc2goKSksXG4gICAgICAgICAgICAgICAgICAgICAgICBkZWZhdWx0SWZFbXB0eSgwKSxcbiAgICAgICAgICAgICAgICAgICAgICAgIG1hcCgoKSA9PiB3ZWxsS25vd24pXG4gICAgICAgICAgICAgICAgICAgIClcbiAgICAgICAgICAgICksXG4gICAgICAgICAgICBzaGFyZVJlcGxheSgxKVxuICAgICAgICApO1xuICAgIH1cblxuICAgIHB1YmxpYyBnZXQgbG9nZ2VkSW4oKTogT2JzZXJ2YWJsZTxib29sZWFuPiB7XG4gICAgICAgIHJldHVybiB0aGlzLl9pbml0U2VydmljZS5waXBlKFxuICAgICAgICAgICAgbWFwKCgpID0+ICEhdGhpcy50b2tlbkRhdGEpXG4gICAgICAgICk7XG4gICAgfVxuXG4gICAgcHVibGljIGdldCB1c2VyUHJvZmlsZSgpOiBPYnNlcnZhYmxlPFVzZXJJbmZvIHwgdW5kZWZpbmVkPiB7XG4gICAgICAgIHJldHVybiB0aGlzLl9pbml0U2VydmljZS5waXBlKFxuICAgICAgICAgICAgbWFwKCgpID0+IHRoaXMudG9rZW5EYXRhKVxuICAgICAgICApO1xuICAgIH1cblxuICAgIHB1YmxpYyBnZXQgbG9nZ2VkSW5TdHJlYW0oKTogT2JzZXJ2YWJsZTxib29sZWFuPiB7XG4gICAgICAgIHJldHVybiB0aGlzLl9sb2dnZWRJblN0cmVhbS5hc09ic2VydmFibGUoKTtcbiAgICB9XG5cbiAgICBwdWJsaWMgZW1pdExvZ2luU3RhdHVzQ2hhbmdlKHN0YXR1czogYm9vbGVhbikge1xuICAgICAgICBpZiAodGhpcy5fbG9nZ2VkSW5TdHJlYW0ubGFzdFZhbHVlICE9PSBzdGF0dXMpIHtcbiAgICAgICAgICAgIHRoaXMuX2xvZ2dlZEluU3RyZWFtLm5leHQoc3RhdHVzKTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIC8vIDxlZGl0b3ItZm9sZCBkZWZhdWx0c3RhdGU9XCJjb2xsYXBzZWRcIiBkZXNjPVwiSW5kaXJlY3QgbG9naW5cIj5cblxuICAgIHB1YmxpYyBsb2dpblZpYUF1dGhTZXJ2aWNlKCk6IE9ic2VydmFibGU8Ym9vbGVhbj4ge1xuICAgICAgICByZXR1cm4gdGhpcy53ZWxsS25vd25TZXJ2aWNlLndlbGxLbm93bi5waXBlKFxuICAgICAgICAgICAgc3dpdGNoTWFwKCh3ZWxsS25vd246IFdlbGxLbm93bikgPT5cbiAgICAgICAgICAgICAgICBOb25jZUdlbmVyYXRvci5nZW5lcmF0ZU5vbmNlKCkucGlwZShcbiAgICAgICAgICAgICAgICAgICAgbWFwKChub25jZSkgPT4gKHtcbiAgICAgICAgICAgICAgICAgICAgICAgIHdlbGxLbm93bixcbiAgICAgICAgICAgICAgICAgICAgICAgIG5vbmNlXG4gICAgICAgICAgICAgICAgICAgIH0pKVxuICAgICAgICAgICAgICAgIClcbiAgICAgICAgICAgICksXG4gICAgICAgICAgICBtYXAoKHt3ZWxsS25vd24sIG5vbmNlfSkgPT4ge1xuICAgICAgICAgICAgICAgIGxldCByZXNwb25zZVR5cGU6IHN0cmluZztcbiAgICAgICAgICAgICAgICBzd2l0Y2ggKHRoaXMuYXV0aENvbmZpZy5mbG93KSB7XG4gICAgICAgICAgICAgICAgICAgIGNhc2UgQXV0aEZsb3cuQVVUSE9SSVpBVElPTl9DT0RFOlxuICAgICAgICAgICAgICAgICAgICAgICAgcmVzcG9uc2VUeXBlID0gJ2NvZGUnO1xuICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICAgICAgICAgIGNhc2UgQXV0aEZsb3cuSU1QTElDSVQ6XG4gICAgICAgICAgICAgICAgICAgICAgICByZXNwb25zZVR5cGUgPSAnaWRfdG9rZW4gdG9rZW4nO1xuICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICAgICAgICAgIGNhc2UgQXV0aEZsb3cuSFlCUklEOlxuICAgICAgICAgICAgICAgICAgICAgICAgcmVzcG9uc2VUeXBlID0gJ2NvZGUgaWRfdG9rZW4nO1xuICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICAgICAgICAgIGRlZmF1bHQ6XG4gICAgICAgICAgICAgICAgICAgICAgICB0aHJvdyB1bnN1cHBvcnRlZEZsb3dUeXBlRXJyb3IoKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgbGV0IGxvY2F0aW9uID0gYCR7d2VsbEtub3duLmVuZHBvaW50cy5hdXRoXG4gICAgICAgICAgICAgICAgfT9yZXNwb25zZV90eXBlPSR7ZW5jb2RlVVJJQ29tcG9uZW50KHJlc3BvbnNlVHlwZSlcbiAgICAgICAgICAgICAgICB9JnNjb3BlPSR7ZW5jb2RlVVJJQ29tcG9uZW50KHRoaXMub3BlbklEU2NvcGUpXG4gICAgICAgICAgICAgICAgfSZjbGllbnRfaWQ9JHtlbmNvZGVVUklDb21wb25lbnQodGhpcy5hdXRoQ29uZmlnLmNsaWVudElEKVxuICAgICAgICAgICAgICAgIH0mcmVkaXJlY3RfdXJpPSR7ZW5jb2RlVVJJQ29tcG9uZW50KHRoaXMucmVkaXJlY3RVUkwpfWA7XG4gICAgICAgICAgICAgICAgaWYgKHRoaXMuYXV0aENvbmZpZy51c2VOb25jZSkge1xuICAgICAgICAgICAgICAgICAgICB0aGlzLnN0b3JhZ2Uubm9uY2UgPSBub25jZTtcbiAgICAgICAgICAgICAgICAgICAgbG9jYXRpb24gKz0gYCZub25jZT0ke2VuY29kZVVSSUNvbXBvbmVudChub25jZSl9YDtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgUmVxdWVzdFV0aWxzLmxvY2F0aW9uSHJlZiA9IGxvY2F0aW9uO1xuICAgICAgICAgICAgICAgIHJldHVybiB0cnVlO1xuICAgICAgICAgICAgfSlcbiAgICAgICAgKTtcbiAgICB9XG5cbiAgICBwdWJsaWMgY2hlY2tMb2NhdGlvbkZvckxvZ2luRGF0YSgpOiBPYnNlcnZhYmxlPGJvb2xlYW4+IHtcbiAgICAgICAgcmV0dXJuIHRoaXMud2VsbEtub3duU2VydmljZS53ZWxsS25vd24ucGlwZShcbiAgICAgICAgICAgIHN3aXRjaE1hcCgoKSA9PiB7XG4gICAgICAgICAgICAgICAgY29uc3QgcGFyYW1zID0gUmVxdWVzdFV0aWxzLmdldE9wZW5JRFF1ZXJ5UGFyYW1zKCk7XG5cbiAgICAgICAgICAgICAgICBpZiAocGFyYW1zLmVycm9yKSB7XG4gICAgICAgICAgICAgICAgICAgIHJldHVybiB0aHJvd0Vycm9yKHtcbiAgICAgICAgICAgICAgICAgICAgICAgIG1lc3NhZ2UgIDogYCgke3BhcmFtcy5lcnJvcn0pICR7cGFyYW1zLmVycm9yRGVzY31gLFxuICAgICAgICAgICAgICAgICAgICAgICAgc3RhdHVzICAgOiA1MDAsXG4gICAgICAgICAgICAgICAgICAgICAgICBlcnJvclR5cGU6IEVycm9yVHlwZS5BVVRIXG4gICAgICAgICAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgIHN3aXRjaCAodGhpcy5hdXRoQ29uZmlnLmZsb3cpIHtcbiAgICAgICAgICAgICAgICAgICAgY2FzZSBBdXRoRmxvdy5IWUJSSUQ6XG4gICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gdGhpcy5jaGVja1BhcmFtZXRlcnNIeWJyaWRGbG93KHBhcmFtcyk7XG4gICAgICAgICAgICAgICAgICAgIGNhc2UgQXV0aEZsb3cuQVVUSE9SSVpBVElPTl9DT0RFOlxuICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuIHRoaXMuY2hlY2tQYXJhbWV0ZXJzQXV0aENvZGVGbG93KHBhcmFtcyk7XG4gICAgICAgICAgICAgICAgICAgIGNhc2UgQXV0aEZsb3cuSU1QTElDSVQ6XG4gICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gdGhpcy5jaGVja1BhcmFtZXRlcnNJbXBsaWNpdEZsb3cocGFyYW1zKTtcbiAgICAgICAgICAgICAgICAgICAgZGVmYXVsdDpcbiAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybiB0aHJvd0Vycm9yKHVuc3VwcG9ydGVkRmxvd1R5cGVFcnJvcigpKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9KVxuICAgICAgICApO1xuICAgIH1cblxuICAgIHByaXZhdGUgY2hlY2tQYXJhbWV0ZXJzSHlicmlkRmxvdyhwYXJhbXM6IE9wZW5JRFF1ZXJ5UGFyYW1zKSB7XG4gICAgICAgIGlmICghcGFyYW1zLmlkX3Rva2VuIHx8ICFwYXJhbXMuY29kZSkge1xuICAgICAgICAgICAgcmV0dXJuIHRocm93RXJyb3Ioe1xuICAgICAgICAgICAgICAgIG1lc3NhZ2UgIDogJ0F1dGhlbnRpY2F0aW9uIHNlcnZlciBkaWQgbm90IHNlbnQgcmVxdWlyZWQgZGF0YS4nLFxuICAgICAgICAgICAgICAgIHN0YXR1cyAgIDogNTAwLFxuICAgICAgICAgICAgICAgIGVycm9yVHlwZTogRXJyb3JUeXBlLkFVVEhcbiAgICAgICAgICAgIH0pO1xuICAgICAgICB9XG4gICAgICAgIC8vIExvZ2luIHVzaW5nIHRva2VuIGZpcnN0XG4gICAgICAgIHJldHVybiB0aGlzLnByb2Nlc3NUb2tlbih7XG4gICAgICAgICAgICBpZF90b2tlbjogcGFyYW1zLmlkX3Rva2VuXG4gICAgICAgIH0pLnBpcGUoXG4gICAgICAgICAgICB0YXAoKCkgPT4ge1xuICAgICAgICAgICAgICAgIC8vIFJlcXVlc3QgcGVybWFuZW50IHRva2VuICsgcmVmcmVzaCB0b2tlblxuICAgICAgICAgICAgICAgIHRoaXMucmVxdWVzdFRva2VuQmFzZWRPbkNvZGUocGFyYW1zLmNvZGUhLCB0aGlzLnJlZGlyZWN0VVJMKS5waXBlKFxuICAgICAgICAgICAgICAgICAgICBjYXRjaEVycm9yKCgpID0+IHRoaXMubG9nb3V0KCkpXG4gICAgICAgICAgICAgICAgKS5zdWJzY3JpYmUoKHRva2VuVmFsaWQ6IGJvb2xlYW4pID0+IHtcbiAgICAgICAgICAgICAgICAgICAgaWYgKCF0b2tlblZhbGlkKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICB0aGlzLmxvZ291dCgpLnBpcGUoZmlyc3QoKSkuc3Vic2NyaWJlKCk7XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9KTtcbiAgICAgICAgICAgIH0pXG4gICAgICAgICk7XG4gICAgfVxuXG4gICAgcHJpdmF0ZSBjaGVja1BhcmFtZXRlcnNBdXRoQ29kZUZsb3cocGFyYW1zOiBPcGVuSURRdWVyeVBhcmFtcykge1xuICAgICAgICBpZiAoIXBhcmFtcy5jb2RlKSB7XG4gICAgICAgICAgICByZXR1cm4gdGhyb3dFcnJvcih7XG4gICAgICAgICAgICAgICAgbWVzc2FnZSAgOiAnQXV0aGVudGljYXRpb24gc2VydmVyIGRpZCBub3Qgc2VuZCByZXF1aXJlZCBkYXRhLicsXG4gICAgICAgICAgICAgICAgc3RhdHVzICAgOiA1MDAsXG4gICAgICAgICAgICAgICAgZXJyb3JUeXBlOiBFcnJvclR5cGUuQVVUSFxuICAgICAgICAgICAgfSk7XG4gICAgICAgIH1cbiAgICAgICAgLy8gUmVxdWVzdCB0b2tlbiArIHJlZnJlc2ggdG9rZW5cbiAgICAgICAgcmV0dXJuIHRoaXMucmVxdWVzdFRva2VuQmFzZWRPbkNvZGUocGFyYW1zLmNvZGUsIHRoaXMucmVkaXJlY3RVUkwpO1xuICAgIH1cblxuICAgIHByaXZhdGUgY2hlY2tQYXJhbWV0ZXJzSW1wbGljaXRGbG93KHBhcmFtczogT3BlbklEUXVlcnlQYXJhbXMpIHtcbiAgICAgICAgaWYgKCFwYXJhbXMuaWRfdG9rZW4gfHwgIXBhcmFtcy5hY2Nlc3NfdG9rZW4pIHtcbiAgICAgICAgICAgIHJldHVybiB0aHJvd0Vycm9yKHtcbiAgICAgICAgICAgICAgICBtZXNzYWdlICA6ICdBdXRoZW50aWNhdGlvbiBzZXJ2ZXIgZGlkIG5vdCBzZW5kIHJlcXVpcmVkIGRhdGEuJyxcbiAgICAgICAgICAgICAgICBzdGF0dXMgICA6IDUwMCxcbiAgICAgICAgICAgICAgICBlcnJvclR5cGU6IEVycm9yVHlwZS5BVVRIXG4gICAgICAgICAgICB9KTtcbiAgICAgICAgfVxuICAgICAgICAvLyBMb2dpbiB1c2luZyBpZF90b2tlbiBhbmQgYWNjZXNzX3Rva2VuLiBSZWZyZXNoIGlzIG5vdCBwZXJtaXRlZCBoZXJlXG4gICAgICAgIHJldHVybiB0aGlzLnByb2Nlc3NUb2tlbih7XG4gICAgICAgICAgICBpZF90b2tlbiAgICA6IHBhcmFtcy5pZF90b2tlbixcbiAgICAgICAgICAgIGFjY2Vzc190b2tlbjogcGFyYW1zLmFjY2Vzc190b2tlblxuICAgICAgICB