UNPKG

@dbg-riskit/angular-auth

Version:

1,040 lines (1,025 loc) 42.2 kB
import * as i1$1 from '@angular/common/http'; import { HttpParams, HttpHeaders, HTTP_INTERCEPTORS } from '@angular/common/http'; import * as i0 from '@angular/core'; import { InjectionToken, Injectable, Inject, NgModule, Optional, SkipSelf } from '@angular/core'; import * as i1$2 from '@angular/router'; import { RouterModule } from '@angular/router'; import { AUTH_PROVIDER } from '@dbg-riskit/angular-common'; import * as i2 from '@dbg-riskit/angular-http'; import { HttpRequestHeaders, HttpService, HttpModule } from '@dbg-riskit/angular-http'; import { LoggingModule } from '@dbg-riskit/angular-logging'; import * as i1 from '@dbg-riskit/common'; import { base64decode, base64encode, LocalStorage, AuthTokenType, secureRandom, CONTENT_TYPE, ReplaySubjectExt, ErrorType, toString } from '@dbg-riskit/common'; import { defer, of, throwError, EMPTY, merge } from 'rxjs'; import { __decorate } from 'tslib'; import { map, shareReplay, switchMap, catchError, defaultIfEmpty, tap, first } from 'rxjs/operators'; import * as i4 from '@angular/common'; class AuthFlow { constructor(_type) { this._type = _type; AuthFlow.CONSTANTS_MAP[_type] = this; } get type() { return this._type; } static byType(type) { return AuthFlow.CONSTANTS_MAP[type]; } } AuthFlow.CONSTANTS_MAP = {}; AuthFlow.AUTHORIZATION_CODE = new AuthFlow('openid-connect/authorization-code'); AuthFlow.IMPLICIT = new AuthFlow('openid-connect/implicit'); AuthFlow.HYBRID = new AuthFlow('openid-connect/hybrid'); AuthFlow.DIRECT = new AuthFlow('openid-connect/direct'); const AUTH_CONFIG = new InjectionToken('risk.authConfig'); /** * Helper class to decode and find JWT expiration. */ // @dynamic class JwtHelper { static decodeToken(token) { const parts = token.split('.'); if (parts.length !== 3) { throw new Error('JWT must have 3 parts'); } const decoded = base64decode(parts[1]); if (!decoded) { throw new Error('Cannot decode the token'); } return JSON.parse(decoded); } static getTokenExpirationDate(token, property = 'exp') { const decoded = JwtHelper.decodeToken(token); if (!decoded.hasOwnProperty(property)) { return null; } const date = new Date(0); // The 0 here is the key, which sets the date to the epoch date.setUTCSeconds(decoded[property]); return date; } static isTokenExpired(token, offsetSeconds) { const date = JwtHelper.getTokenExpirationDate(token); return JwtHelper.isBefore(date, offsetSeconds); } static isBefore(date, offsetSeconds) { offsetSeconds = offsetSeconds || 0; if (date == null) { return false; } // Token expired? return date.valueOf() < (new Date().valueOf() + (offsetSeconds * 1000)); } } function encodeTestToken(payload) { // Don't actually check or care about the header or signature in angular2-jwt return `.${base64encode(JSON.stringify(payload))}.`; } class AuthStorageService { constructor(logger) { this.logger = logger; } syncTime() { const now = Math.floor((new Date().valueOf()) / 1000); const computeDiff = (token, currDiff = 0) => { if (token) { const tokenData = JwtHelper.decodeToken(token); if (tokenData.iat) { const diff = tokenData.iat - now; if (Math.abs(diff) > Math.abs(currDiff)) { return diff; } } } return currDiff; }; this._timeSyncDiff = computeDiff(this.idToken); this._timeSyncDiff = computeDiff(this.accessToken, this._timeSyncDiff); this._timeSyncDiff = computeDiff(this.refreshToken, this._timeSyncDiff); this._timeSyncDiff = Math.floor(this._timeSyncDiff || 0); } get exp() { const computeExpiration = (token, date = null) => { if (token) { const tokenExp = JwtHelper.getTokenExpirationDate(token); if (date == null || tokenExp == null) { return tokenExp; } if (date > tokenExp) { return tokenExp; } } return date; }; let exp = computeExpiration(this.idToken); exp = computeExpiration(this.accessToken, exp); return computeExpiration(this.refreshToken, exp); } get refresh_in() { const exp = this.exp; const now = new Date(); if (!exp) { return 1; } const syncDiff = this._timeSyncDiff || 0; const expFromNow = Math.floor((exp.valueOf() - now.valueOf()) / 1000) - syncDiff; const refreshIn = expFromNow - 60; // Refresh at least a minute before this.logger.info(`Tokens will refresh in ${refreshIn}s.`); return refreshIn > 0 ? (refreshIn * 1000) : 1; } clear() { this.idToken = null; this.accessToken = null; this.refreshToken = null; this._timeSyncDiff = null; this.nonce = null; } } AuthStorageService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.2.6", ngImport: i0, type: AuthStorageService, deps: [{ token: i1.Logger }], target: i0.ɵɵFactoryTarget.Injectable }); AuthStorageService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "13.2.6", ngImport: i0, type: AuthStorageService }); __decorate([ LocalStorage('id_token', 'auth') ], AuthStorageService.prototype, "idToken", void 0); __decorate([ LocalStorage('access_token', 'auth') ], AuthStorageService.prototype, "accessToken", void 0); __decorate([ LocalStorage('refresh_token', 'auth') ], AuthStorageService.prototype, "refreshToken", void 0); __decorate([ LocalStorage('sync_diff', 'auth') ], AuthStorageService.prototype, "_timeSyncDiff", void 0); __decorate([ LocalStorage('nonce', 'auth') ], AuthStorageService.prototype, "nonce", void 0); __decorate([ LocalStorage('req_path', 'auth') ], AuthStorageService.prototype, "authRequestedPath", void 0); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.2.6", ngImport: i0, type: AuthStorageService, decorators: [{ type: Injectable }], ctorParameters: function () { return [{ type: i1.Logger }]; }, propDecorators: { idToken: [], accessToken: [], refreshToken: [], _timeSyncDiff: [], nonce: [], authRequestedPath: [] } }); class AuthConfigConsts { } AuthConfigConsts.DEFAULT_HEADER_NAME = 'Authorization'; AuthConfigConsts.HEADER_PREFIX_BEARER = 'Bearer '; class AuthHttpInterceptor { constructor(authStorage) { this.authStorage = authStorage; } intercept(request, next) { return defer(() => { if (request.headers instanceof HttpRequestHeaders) { const tokenType = request.headers.tokenType; switch (tokenType) { case AuthTokenType.API: return handleInterception(this.authStorage.idToken, request, next); case AuthTokenType.AUTH: return handleInterception(this.authStorage.accessToken, request, next); default: return next.handle(request); } } return next.handle(request); }); } } AuthHttpInterceptor.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.2.6", ngImport: i0, type: AuthHttpInterceptor, deps: [{ token: AuthStorageService }], target: i0.ɵɵFactoryTarget.Injectable }); AuthHttpInterceptor.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "13.2.6", ngImport: i0, type: AuthHttpInterceptor }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.2.6", ngImport: i0, type: AuthHttpInterceptor, decorators: [{ type: Injectable }], ctorParameters: function () { return [{ type: AuthStorageService }]; } }); function handleInterception(token, request, next) { if (token) { request = request.clone({ setHeaders: { [AuthConfigConsts.DEFAULT_HEADER_NAME]: `${AuthConfigConsts.HEADER_PREFIX_BEARER}${token}` } }); } return next.handle(request); } // @dynamic class RequestUtils { static get locationHref() { return window.location.href; } static set locationHref(href) { window.location.href = href; } static getURLPath(href) { const link = document.createElement('a'); link.href = href; return link.pathname; } static getOrigin() { const port = window.location.port ? `:${window.location.port}` : ''; return window.location.origin || (`${window.location.protocol}//${window.location.hostname}${port}`); } static getBaseURL(location, relativeURL = '/') { let baseURL = ''; if (location) { baseURL = RequestUtils.getURLPath(location.prepareExternalUrl('/')); } if (!baseURL.startsWith('/')) { baseURL = '/' + baseURL; } if (!baseURL.endsWith('/')) { baseURL += '/'; } if (relativeURL.startsWith('/')) { relativeURL = relativeURL.substring(1); } const origin = RequestUtils.getOrigin(); return origin + baseURL + relativeURL; } static getQueryParam(name) { const url = RequestUtils.locationHref; name = name.replace(/[\[\]]/g, '\\$&'); const regex = new RegExp(`[?#&]${name}(=([^&#]*)|&|#|$)`); const results = regex.exec(url); if (!results) { return null; } if (!results[2]) { return ''; } return decodeURIComponent(results[2].replace(/\+/g, ' ')); } static getOpenIDQueryParams() { const error = RequestUtils.getQueryParam('error'); const errorDesc = RequestUtils.getQueryParam('error_description'); const code = RequestUtils.getQueryParam('code'); const idToken = RequestUtils.getQueryParam('id_token'); const accessToken = RequestUtils.getQueryParam('access_token'); return { error, errorDesc, code, id_token: idToken, access_token: accessToken }; } static hasOpenIDQueryParams() { const params = RequestUtils.getOpenIDQueryParams(); return params.error != null || params.errorDesc != null || params.code != null || params.id_token != null || params.access_token != null; } } // @dynamic class NonceGenerator { static generateNonce() { return defer(() => { const guidHolder = 'xxx-xxyxxxxx4xxxyxxxxxx-xxxx4xxx'; const hex = '0123456789abcdef'; let r = 0; let guidResponse = ''; for (const placeHolder of guidHolder) { if (placeHolder !== '-' && placeHolder !== '4') { // each x and y needs to be random r = Math.floor(secureRandom() * 16); } if (placeHolder === 'x') { guidResponse += hex[r]; } else if (placeHolder === 'y') { /* eslint-disable no-bitwise */ // clock-seq-and-reserved first hex is filtered and remaining hex values are random r &= 0x3; // bit and with 0011 to set pos 2 to zero ?0?? r |= 0x8; // set pos 3 to 1 as 1??? /* eslint-enable */ guidResponse += hex[r]; } else { guidResponse += placeHolder; } } return of(guidResponse); }); } } class WellKnownService { constructor(http, authConfig) { this.http = http; this.authConfig = authConfig; const params = new HttpParams(); params.append('_', Date.now().toFixed(0)); this._wellKnown = this.http.get(this.authConfig.wellKnown, { params, headers: new HttpHeaders({ accept: CONTENT_TYPE.APPLICATION_JSON }) }).pipe(map((wellKnown) => ({ endpoints: { auth: wellKnown.authorization_endpoint, token: wellKnown.token_endpoint, logout: wellKnown.end_session_endpoint }, issuer: wellKnown.issuer })), shareReplay(1)); } get wellKnown() { return this._wellKnown; } } WellKnownService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.2.6", ngImport: i0, type: WellKnownService, deps: [{ token: i1$1.HttpClient }, { token: AUTH_CONFIG }], target: i0.ɵɵFactoryTarget.Injectable }); WellKnownService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "13.2.6", ngImport: i0, type: WellKnownService }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.2.6", ngImport: i0, type: WellKnownService, decorators: [{ type: Injectable }], ctorParameters: function () { return [{ type: i1$1.HttpClient }, { type: undefined, decorators: [{ type: Inject, args: [AUTH_CONFIG] }] }]; } }); 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); } 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: WellKnownService }, { token: i2.HttpService }, { token: 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: WellKnownService }, { type: i2.HttpService }, { type: AuthStorageService }, { type: i4.Location }]; } }); class AuthRoutingFlowService { constructor(router, http, authService, storage, authConfig, wellKnownService) { this.router = router; this.http = http; this.authService = authService; this.storage = storage; this.authConfig = authConfig; this.wellKnownService = wellKnownService; // Subscribe for login changes so we do correct navigation after logout this.authService.loggedIn .subscribe(() => { try { merge(this.http.unauthorized.pipe(map(() => false)), this.authService.loggedInStream) .subscribe((loggedIn) => { if (!loggedIn) { this.logout().pipe(first()).subscribe(); } }); } catch (ignore) { // Happens in tests only } }); } get authFlow() { return this.authConfig.flow; } get authorizationCodeFlow() { return this.authFlow === AuthFlow.AUTHORIZATION_CODE; } get implicitFlow() { return this.authFlow === AuthFlow.IMPLICIT; } get hybridFlow() { return this.authFlow === AuthFlow.HYBRID; } get directFlow() { return this.authFlow === AuthFlow.DIRECT; } logout(state) { return this.authService.loggedIn.pipe(switchMap((res) => { if (res) { return this.authService.logout(); } return EMPTY; }), defaultIfEmpty(0), switchMap(() => { this.storeRequestedPath(state); return this.doLogin(); })); } login(username, password) { return defer(() => { switch (this.authFlow) { case AuthFlow.DIRECT: if (username == null || password == null) { throw new Error('Username and password expexted!'); } return this.authService.directLogin(username, password).pipe(switchMap((res) => this.loginRedirect(res))); case AuthFlow.AUTHORIZATION_CODE: case AuthFlow.IMPLICIT: case AuthFlow.HYBRID: return this.doLogin(); default: throw new Error('Unsupported auth flow detected!'); } }); } loginViaService() { return this.authService.loggedIn.pipe(switchMap((res) => { switch (this.authFlow) { case AuthFlow.AUTHORIZATION_CODE: case AuthFlow.IMPLICIT: case AuthFlow.HYBRID: if (res) { return this.loginRedirect(res); } if (!RequestUtils.hasOpenIDQueryParams()) { return this.doLogin().pipe(map(() => res)); } return this.authService.checkLocationForLoginData().pipe(switchMap((loggedIn) => this.loginRedirect(loggedIn))); case AuthFlow.DIRECT: return of(true); default: throw new Error('Unknown auth flow detected!'); } })); } storeRequestedPath(state = this.router.routerState.snapshot) { if (state.url.startsWith(this.authConfig.loginRoute)) { this.storage.authRequestedPath = null; } else { this.storage.authRequestedPath = state.url; } } loginRedirect(res) { return this.wellKnownService.wellKnown.pipe(switchMap(() => { if (res) { if (this.storage.authRequestedPath) { this.router.navigateByUrl(this.storage.authRequestedPath); this.storage.authRequestedPath = null; } else { this.router.navigate([this.authConfig.afterLoginRedirectRoute]); } return of(res); } else { return this.doLogin().pipe(map(() => res)); } })); } doLogin() { switch (this.authFlow) { case AuthFlow.DIRECT: return this.wellKnownService.wellKnown.pipe(map(() => { this.router.navigate([this.authConfig.loginRoute]); return true; })); case AuthFlow.AUTHORIZATION_CODE: case AuthFlow.IMPLICIT: case AuthFlow.HYBRID: return this.authService.loginViaAuthService(); default: throw new Error('Unknown auth flow detected!'); } } } AuthRoutingFlowService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.2.6", ngImport: i0, type: AuthRoutingFlowService, deps: [{ token: i1$2.Router }, { token: i2.HttpService }, { token: AuthService }, { token: AuthStorageService }, { token: AUTH_CONFIG }, { token: WellKnownService }], target: i0.ɵɵFactoryTarget.Injectable }); AuthRoutingFlowService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "13.2.6", ngImport: i0, type: AuthRoutingFlowService }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.2.6", ngImport: i0, type: AuthRoutingFlowService, decorators: [{ type: Injectable }], ctorParameters: function () { return [{ type: i1$2.Router }, { type: i2.HttpService }, { type: AuthService }, { type: AuthStorageService }, { type: undefined, decorators: [{ type: Inject, args: [AUTH_CONFIG] }] }, { type: WellKnownService }]; } }); class AuthGuard { constructor(authService, authRoutingFlowService) { this.authService = authService; this.authRoutingFlowService = authRoutingFlowService; } canActivate(route, state) { return this.authService.loggedIn.pipe(switchMap((res) => { if (res) { return of(true); } return this.authRoutingFlowService.logout(state).pipe(map(() => false)); })); } canActivateChild(childRoute, state) { return this.canActivate(childRoute, state); } } AuthGuard.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.2.6", ngImport: i0, type: AuthGuard, deps: [{ token: AuthService }, { token: AuthRoutingFlowService }], target: i0.ɵɵFactoryTarget.Injectable }); AuthGuard.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "13.2.6", ngImport: i0, type: AuthGuard }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.2.6", ngImport: i0, type: AuthGuard, decorators: [{ type: Injectable }], ctorParameters: function () { return [{ type: AuthService }, { type: AuthRoutingFlowService }]; } }); // AoT workaround - we have to provide it using factory to prevent compiler from replacing // "window.prop" expressions. const TMP_HTTP_CONFIG = new InjectionToken('risk.auth.tmp_http_provider'); class AuthModule { constructor(parentModule) { if (parentModule) { throw new Error('AuthModule is already loaded. Import it in the AppModule only'); } } // AoT workaround - we have to provide it using factory to prevent compiler from replacing // "window.prop" expressions. static forAuthConfig(config) { return { ngModule: AuthModule, providers: [ { provide: AUTH_CONFIG, useFactory: config } ] }; } } AuthModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.2.6", ngImport: i0, type: AuthModule, deps: [{ token: AuthModule, optional: true, skipSelf: true }], target: i0.ɵɵFactoryTarget.NgModule }); AuthModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "13.2.6", ngImport: i0, type: AuthModule, imports: [RouterModule, HttpModule, LoggingModule] }); AuthModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "13.2.6", ngImport: i0, type: AuthModule, providers: [ AuthStorageService, AuthService, AuthGuard, AuthRoutingFlowService, WellKnownService, { provide: AUTH_PROVIDER, useExisting: AuthService }, { provide: HTTP_INTERCEPTORS, useClass: AuthHttpInterceptor, multi: true } ], imports: [[ RouterModule, HttpModule, LoggingModule ]] }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.2.6", ngImport: i0, type: AuthModule, decorators: [{ type: NgModule, args: [{ imports: [ RouterModule, HttpModule, LoggingModule ], providers: [ AuthStorageService, AuthService, AuthGuard, AuthRoutingFlowService, WellKnownService, { provide: AUTH_PROVIDER, useExisting: AuthService }, { provide: HTTP_INTERCEPTORS, useClass: AuthHttpInterceptor, multi: true } ] }] }], ctorParameters: function () { return [{ type: AuthModule, decorators: [{ type: Optional }, { type: SkipSelf }] }]; } }); function readAuthConfig() { return { wellKnown: window.authWellKnownEndpoint, clientID: window.authClientID, clientSecret: window.authClientSecret, flow: AuthFlow.byType(window.authFlow), scope: window.authScopes, useNonce: window.authUseNonce == null ? true : window.authUseNonce }; } /** * Generated bundle index. Do not edit. */ export { AUTH_CONFIG, AuthFlow, AuthGuard, AuthModule, AuthRoutingFlowService, AuthService, AuthStorageService, readAuthConfig }; //# sourceMappingURL=dbg-riskit-angular-auth.mjs.map