UNPKG

keycloak-angular

Version:
395 lines (387 loc) 14.1 kB
import { __awaiter } from 'tslib'; import { Injectable, NgModule } from '@angular/core'; import { HttpHeaders, HTTP_INTERCEPTORS } from '@angular/common/http'; import { Subject, Observable, from } from 'rxjs'; import * as Keycloak_ from 'keycloak-js'; import { mergeMap } from 'rxjs/operators'; import { CommonModule } from '@angular/common'; const KeycloakEventType = { OnAuthError: 0, OnAuthLogout: 1, OnAuthRefreshError: 2, OnAuthRefreshSuccess: 3, OnAuthSuccess: 4, OnReady: 5, OnTokenExpired: 6, }; KeycloakEventType[KeycloakEventType.OnAuthError] = 'OnAuthError'; KeycloakEventType[KeycloakEventType.OnAuthLogout] = 'OnAuthLogout'; KeycloakEventType[KeycloakEventType.OnAuthRefreshError] = 'OnAuthRefreshError'; KeycloakEventType[KeycloakEventType.OnAuthRefreshSuccess] = 'OnAuthRefreshSuccess'; KeycloakEventType[KeycloakEventType.OnAuthSuccess] = 'OnAuthSuccess'; KeycloakEventType[KeycloakEventType.OnReady] = 'OnReady'; KeycloakEventType[KeycloakEventType.OnTokenExpired] = 'OnTokenExpired'; function KeycloakEvent() { } if (false) { KeycloakEvent.prototype.type; KeycloakEvent.prototype.args; } class KeycloakAuthGuard { constructor(router, keycloakAngular) { this.router = router; this.keycloakAngular = keycloakAngular; } canActivate(route, state) { return new Promise(((resolve, reject) => __awaiter(this, void 0, void 0, function* () { try { this.authenticated = yield this.keycloakAngular.isLoggedIn(); this.roles = yield this.keycloakAngular.getUserRoles(true); const result = yield this.isAccessAllowed(route, state); resolve(result); } catch (error) { reject('An error happened during access validation. Details:' + error); } }))); } } if (false) { KeycloakAuthGuard.prototype.authenticated; KeycloakAuthGuard.prototype.roles; KeycloakAuthGuard.prototype.router; KeycloakAuthGuard.prototype.keycloakAngular; KeycloakAuthGuard.prototype.isAccessAllowed = function (route, state) { }; } const Keycloak = Keycloak_; class KeycloakService { constructor() { this._keycloakEvents$ = new Subject(); } bindsKeycloakEvents() { this._instance.onAuthError = (errorData => { this._keycloakEvents$.next({ args: errorData, type: KeycloakEventType.OnAuthError }); }); this._instance.onAuthLogout = (() => { this._keycloakEvents$.next({ type: KeycloakEventType.OnAuthLogout }); }); this._instance.onAuthRefreshSuccess = (() => { this._keycloakEvents$.next({ type: KeycloakEventType.OnAuthRefreshSuccess }); }); this._instance.onAuthRefreshError = (() => { this._keycloakEvents$.next({ type: KeycloakEventType.OnAuthRefreshError }); }); this._instance.onAuthSuccess = (() => { this._keycloakEvents$.next({ type: KeycloakEventType.OnAuthSuccess }); }); this._instance.onTokenExpired = (() => { this._keycloakEvents$.next({ type: KeycloakEventType.OnTokenExpired }); }); this._instance.onReady = (authenticated => { this._keycloakEvents$.next({ args: authenticated, type: KeycloakEventType.OnReady }); }); } loadExcludedUrls(bearerExcludedUrls) { const excludedUrls = []; for (const item of bearerExcludedUrls) { let excludedUrl; if (typeof item === 'string') { excludedUrl = { urlPattern: new RegExp(item, 'i'), httpMethods: [] }; } else { excludedUrl = { urlPattern: new RegExp(item.url, 'i'), httpMethods: item.httpMethods }; } excludedUrls.push(excludedUrl); } return excludedUrls; } initServiceValues({ enableBearerInterceptor = true, loadUserProfileAtStartUp = true, bearerExcludedUrls = [], authorizationHeaderName = 'Authorization', bearerPrefix = 'bearer', initOptions }) { this._enableBearerInterceptor = enableBearerInterceptor; this._loadUserProfileAtStartUp = loadUserProfileAtStartUp; this._authorizationHeaderName = authorizationHeaderName; this._bearerPrefix = bearerPrefix.trim().concat(' '); this._excludedUrls = this.loadExcludedUrls(bearerExcludedUrls); this._silentRefresh = initOptions ? initOptions.flow === 'implicit' : false; } init(options = {}) { return new Promise(((resolve, reject) => { this.initServiceValues(options); const { config, initOptions } = options; this._instance = Keycloak(config); this.bindsKeycloakEvents(); this._instance .init(initOptions) .success(((authenticated) => __awaiter(this, void 0, void 0, function* () { if (authenticated && this._loadUserProfileAtStartUp) { yield this.loadUserProfile(); } resolve(authenticated); }))) .error((kcError => { let msg = 'An error happened during Keycloak initialization.'; if (kcError) { let { error, error_description } = kcError; msg = msg.concat(`\nAdapter error details:\nError: ${error}\nDescription: ${error_description}`); } reject(msg); })); })); } login(options = {}) { return new Promise(((resolve, reject) => { this._instance .login(options) .success((() => __awaiter(this, void 0, void 0, function* () { if (this._loadUserProfileAtStartUp) { yield this.loadUserProfile(); } resolve(); }))) .error((() => reject(`An error happened during the login.`))); })); } logout(redirectUri) { return new Promise(((resolve, reject) => { const options = { redirectUri }; this._instance .logout(options) .success((() => { this._userProfile = undefined; resolve(); })) .error((() => reject('An error happened during logout.'))); })); } register(options = { action: 'register' }) { return new Promise(((resolve, reject) => { this._instance .register(options) .success((() => { resolve(); })) .error((() => reject('An error happened during the register execution.'))); })); } isUserInRole(role, resource) { let hasRole; hasRole = this._instance.hasResourceRole(role, resource); if (!hasRole) { hasRole = this._instance.hasRealmRole(role); } return hasRole; } getUserRoles(allRoles = true) { let roles = []; if (this._instance.resourceAccess) { for (const key in this._instance.resourceAccess) { if (this._instance.resourceAccess.hasOwnProperty(key)) { const resourceAccess = this._instance.resourceAccess[key]; const clientRoles = resourceAccess['roles'] || []; roles = roles.concat(clientRoles); } } } if (allRoles && this._instance.realmAccess) { let realmRoles = this._instance.realmAccess['roles'] || []; roles.push(...realmRoles); } return roles; } isLoggedIn() { return __awaiter(this, void 0, void 0, function* () { try { if (!this._instance.authenticated) { return false; } yield this.updateToken(20); return true; } catch (error) { return false; } }); } isTokenExpired(minValidity = 0) { return this._instance.isTokenExpired(minValidity); } updateToken(minValidity = 5) { return new Promise(((resolve, reject) => __awaiter(this, void 0, void 0, function* () { if (this._silentRefresh) { if (this.isTokenExpired()) { reject('Failed to refresh the token, or the session is expired'); } else { resolve(true); } return; } if (!this._instance) { reject('Keycloak Angular library is not initialized.'); return; } this._instance .updateToken(minValidity) .success((refreshed => { resolve(refreshed); })) .error((() => reject('Failed to refresh the token, or the session is expired'))); }))); } loadUserProfile(forceReload = false) { return new Promise(((resolve, reject) => __awaiter(this, void 0, void 0, function* () { if (this._userProfile && !forceReload) { resolve(this._userProfile); return; } if (!this._instance.authenticated) { reject('The user profile was not loaded as the user is not logged in.'); return; } this._instance .loadUserProfile() .success((result => { this._userProfile = ((result)); resolve(this._userProfile); })) .error((() => reject('The user profile could not be loaded.'))); }))); } getToken() { return new Promise(((resolve, reject) => __awaiter(this, void 0, void 0, function* () { try { yield this.updateToken(10); resolve(this._instance.token); } catch (error) { this.login(); } }))); } getUsername() { if (!this._userProfile) { throw new Error('User not logged in or user profile was not loaded.'); } return ((this._userProfile.username)); } clearToken() { this._instance.clearToken(); } addTokenToHeader(headers = new HttpHeaders()) { return Observable.create(((observer) => __awaiter(this, void 0, void 0, function* () { try { const token = yield this.getToken(); headers = headers.set(this._authorizationHeaderName, this._bearerPrefix + token); observer.next(headers); observer.complete(); } catch (error) { observer.error(error); } }))); } getKeycloakInstance() { return this._instance; } get excludedUrls() { return this._excludedUrls; } get enableBearerInterceptor() { return this._enableBearerInterceptor; } get keycloakEvents$() { return this._keycloakEvents$; } } KeycloakService.decorators = [ { type: Injectable } ]; if (false) { KeycloakService.prototype._instance; KeycloakService.prototype._userProfile; KeycloakService.prototype._enableBearerInterceptor; KeycloakService.prototype._silentRefresh; KeycloakService.prototype._loadUserProfileAtStartUp; KeycloakService.prototype._bearerPrefix; KeycloakService.prototype._authorizationHeaderName; KeycloakService.prototype._excludedUrls; KeycloakService.prototype._keycloakEvents$; } class KeycloakBearerInterceptor { constructor(keycloak) { this.keycloak = keycloak; } isUrlExcluded({ method, url }, { urlPattern, httpMethods }) { let httpTest = httpMethods.length === 0 || httpMethods.join().indexOf(method.toUpperCase()) > -1; let urlTest = urlPattern.test(url); return httpTest && urlTest; } intercept(req, next) { const { enableBearerInterceptor, excludedUrls } = this.keycloak; if (!enableBearerInterceptor) { return next.handle(req); } const shallPass = excludedUrls.findIndex((item => this.isUrlExcluded(req, item))) > -1; if (shallPass) { return next.handle(req); } return from(this.keycloak.isLoggedIn()).pipe(mergeMap(((loggedIn) => loggedIn ? this.handleRequestWithTokenHeader(req, next) : next.handle(req)))); } handleRequestWithTokenHeader(req, next) { return this.keycloak.addTokenToHeader(req.headers).pipe(mergeMap((headersWithBearer => { const kcReq = req.clone({ headers: headersWithBearer }); return next.handle(kcReq); }))); } } KeycloakBearerInterceptor.decorators = [ { type: Injectable } ]; KeycloakBearerInterceptor.ctorParameters = () => [ { type: KeycloakService } ]; if (false) { KeycloakBearerInterceptor.prototype.keycloak; } class CoreModule { } CoreModule.decorators = [ { type: NgModule, args: [{ imports: [CommonModule], providers: [ KeycloakService, { provide: HTTP_INTERCEPTORS, useClass: KeycloakBearerInterceptor, multi: true } ] },] } ]; class KeycloakAngularModule { } KeycloakAngularModule.decorators = [ { type: NgModule, args: [{ imports: [CoreModule] },] } ]; export { CoreModule, KeycloakAngularModule, KeycloakAuthGuard, KeycloakBearerInterceptor, KeycloakEventType, KeycloakService }; //# sourceMappingURL=keycloak-angular.js.map