@dbg-riskit/angular-auth
Version:
1,040 lines (1,025 loc) • 42.2 kB
JavaScript
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