@dbg-riskit/angular-auth
Version:
457 lines • 68.5 kB
JavaScript
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