UNPKG

@indice/ng-auth

Version:

Indice extensions for Angular v20 using oidc-client

512 lines (499 loc) 20.3 kB
import * as i0 from '@angular/core'; import { InjectionToken, Inject, Injectable, Input, Directive, NgModule } from '@angular/core'; import { UserManager } from 'oidc-client-ts'; import { BehaviorSubject, from, throwError } from 'rxjs'; import { map, catchError } from 'rxjs/operators'; import * as i2 from '@angular/router'; import { Router } from '@angular/router'; const AUTH_SETTINGS = new InjectionToken('AUTH_SETTINGS'); const TENANT_PREFIX_URL = new InjectionToken('TENANT_PREFIX_URL'); /** https://github.com/IdentityModel/oidc-client-js/wiki */ class AuthService { constructor(authSettings) { this._user = null; this._userSubject = new BehaviorSubject(null); this.user$ = this._userSubject.asObservable(); this._userManager = new UserManager(authSettings); this.loadUser().subscribe(); this._userManager.clearStaleState(); this._userManager.events.addUserLoaded(() => { this._userManager.getUser().then((user) => { if (user) { this._user = user; this._userSubject.next(user); this._userSubject.complete(); } else { } }); }); this._userManager.events.addAccessTokenExpiring(() => { console.info('Access token expiring event occurred.'); // No need to call this._userManager.signinSilent() since automaticSilentRenew is set to true. // check your environment.ts for auth_settings }); this._userManager.events.addUserSignedOut(() => { this._userManager.clearStaleState(); this._userManager.removeUser(); }); } loadUser() { return from(this._userManager.getUser()).pipe(map((user) => { if (user) { this._user = user; this._userSubject.next(user); this._userSubject.complete(); } return user; })); } isLoggedIn() { return from(this._userManager.getUser()).pipe(map((user) => user ? true : false)); } getUserProfile() { return this._user?.profile || undefined; } getEmail() { return this.getUserProfile()?.email; } hasVerifiedEmail() { return this.getUserProfile()?.email_verified; } getSubjectId() { return this.getUserProfile()?.sub; } getFullName() { const userProfile = this.getUserProfile(); if (userProfile?.given_name && userProfile?.family_name) { return `${userProfile.given_name} ${userProfile.family_name}`; } return undefined; } getUserName() { return this.getUserProfile()?.name; } getDisplayName() { return this.getFullName() || this.getEmail() || this.getUserName() || ''; } getCurrentUser() { return this._user; } isAdmin() { const profile = this.getUserProfile(); if (!profile) { return false; } return profile["admin"] === true || this.hasRole('Administrator'); } getAuthorizationHeaderValue() { if (this._user) { return `${this._user.token_type} ${this._user.access_token}`; } return ''; } getAccessTokenValue() { if (this._user) { return `${this._user.access_token}`; } return ''; } signoutRedirect() { this._userManager.signoutRedirect(); } removeUser() { this._userManager.clearStaleState(); return from(this._userManager.removeUser()); } signoutRedirectCallback() { return from(this._userManager.signoutRedirectCallback()).pipe(map((response) => { this._user = null; this._userSubject.next(null); this._userSubject.complete(); return response; }, (error) => { throwError(error); })); } signinRedirect(signInRedirectOptions) { const authorizeArgs = {}; if (signInRedirectOptions?.location) { authorizeArgs['url_state'] = signInRedirectOptions.location; } if (signInRedirectOptions?.location) { authorizeArgs.state = { url: signInRedirectOptions.location }; } if (signInRedirectOptions?.promptRegister === true) { authorizeArgs['extraQueryParams'] = { operation: 'register' }; } if (signInRedirectOptions?.tenant) { authorizeArgs['acr_values'] = `tenant:${signInRedirectOptions.tenant}`; } this._userManager .signinRedirect(authorizeArgs) .catch((error) => { }); } signinRedirectCallback() { return from(this._userManager.signinRedirectCallback()).pipe(map((user) => { this._user = user; this._userSubject.next(this._user); this._userSubject.complete(); return user; }, (error) => { throwError(error); return null; })); } signinSilent() { return from(this._userManager.signinSilent()).pipe(map((user) => { this._userSubject.next(user); this._userSubject.complete(); return user; })); } signinSilentCallback() { return from(this._userManager.signinSilentCallback()).pipe(map((user) => { if (user) { this._user = user; } this._userSubject.next(this._user); this._userSubject.complete(); return user; }, (error) => { throwError(error); return null; })); } hasRole(roleName) { const profile = this.getUserProfile(); if (!profile) { return false; } const roleClaim = profile["role"]; if (roleClaim && Array.isArray(roleClaim)) { const roles = Array.from(roleClaim); return roles.indexOf(roleName) !== -1; } return roleClaim === roleName; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: AuthService, deps: [{ token: AUTH_SETTINGS }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: AuthService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: AuthService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: () => [{ type: undefined, decorators: [{ type: Inject, args: [AUTH_SETTINGS] }] }] }); class AuthGuardService { constructor(authService) { this.authService = authService; } canActivate(route, state) { const observable = this.authService.isLoggedIn(); observable.subscribe((isLoggedIn) => { if (!isLoggedIn) { this.authService.signinRedirect({ location: state?.url || undefined, promptRegister: route?.data?.register || route?.firstChild?.data?.register || false, tenant: route?.params['tenantAlias'] ?? 'localhost' }); } }); return observable; } canActivateChild(childRoute, state) { return this.canActivate(childRoute, state); } canLoad() { return this.canActivate(undefined, undefined); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: AuthGuardService, deps: [{ token: AuthService }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: AuthGuardService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: AuthGuardService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: () => [{ type: AuthService, decorators: [{ type: Inject, args: [AuthService] }] }] }); class AuthHttpInterceptor { constructor(authService, router) { this.authService = authService; this.router = router; } intercept(request, next) { let authRequest = null; if (request.url.indexOf('i18n') >= 0) { authRequest = request; } else { authRequest = request.clone({ headers: request.headers.set('Authorization', this.authService.getAuthorizationHeaderValue()), params: request.params }); } return next.handle(authRequest).pipe(map((httpEvent) => { return httpEvent; }), catchError((error) => { if (error?.status === 401) { this.authService.removeUser().pipe(map(() => this.authService.signoutRedirect())).subscribe(); } if (error?.status === 403) { this.router.navigate(['/forbidden']); } throw error; })); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: AuthHttpInterceptor, deps: [{ token: AuthService }, { token: Router }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: AuthHttpInterceptor }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: AuthHttpInterceptor, decorators: [{ type: Injectable }], ctorParameters: () => [{ type: AuthService, decorators: [{ type: Inject, args: [AuthService] }] }, { type: i2.Router, decorators: [{ type: Inject, args: [Router] }] }] }); class TenantService { constructor() { this._tenantSubject = new BehaviorSubject(''); } storeTenant(tenant) { this._tenantSubject.next(tenant); } getTenant() { return this._tenantSubject.asObservable(); } getTenantValue() { return this._tenantSubject.getValue(); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: TenantService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: TenantService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: TenantService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: () => [] }); class TenantHeaderInterceptor { constructor(tenantPrefixUrl, tenantStore) { this.tenantPrefixUrl = tenantPrefixUrl; this.tenantStore = tenantStore; } intercept(request, next) { let clonedRequest = request; // First check if request is going to our API or an external call. if (request.urlWithParams.toLowerCase().startsWith(this.tenantPrefixUrl.toLowerCase())) { this.tenantStore.getTenant().subscribe((tenant) => { if (tenant !== null || tenant !== '') { // Clone the request to add the new header. clonedRequest = request.clone({ headers: request.headers.append('X-Tenant-Id', `${tenant}`) }); } }) .unsubscribe(); } // Pass the cloned request instead of the original request to the next handle. return next.handle(clonedRequest); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: TenantHeaderInterceptor, deps: [{ token: TENANT_PREFIX_URL }, { token: TenantService }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: TenantHeaderInterceptor, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: TenantHeaderInterceptor, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: () => [{ type: undefined, decorators: [{ type: Inject, args: [TENANT_PREFIX_URL] }] }, { type: TenantService, decorators: [{ type: Inject, args: [TenantService] }] }] }); class ImgUserPictureDirective { constructor(authSettings, element) { this.authSettings = authSettings; this._size = 48; this._displayName = 'John Doe'; this._color = undefined; this._version = 0; this._initialized = false; this._img = element.nativeElement; } ngOnInit() { this.setProfileSrc(); this._initialized = true; } set setUserId(value) { if (this._userId === value) { return; } this._userId = value; if (this._initialized) { this.setProfileSrc(); } } set setSize(value) { this._size = value; } set setVersion(value) { if (this._version === value) { return; } this._version = value; if (this._initialized) { this.setProfileSrc(); } } set setDisplayName(value) { if (!value) { return; } let text = this._displayName; if (typeof value === 'string') { text = value; } else { const firstName = value.firstName || ''; const lastName = value.lastName || ''; const givenName = value.given_name || ''; const familyName = value.family_name || ''; // Construct the display name based on available properties if (firstName || lastName) { text = `${firstName} ${lastName}`.trim(); } else if (givenName || familyName) { text = `${givenName} ${familyName}`.trim(); } else if (value.email) { text = value.email; } else if (value.userName) { text = value.userName; } else if (value.name) { text = value.name; } } if (!text) { return; } this._displayName = text.split('@')[0].replaceAll(/[\+\(\)\{\}\.,\[\]]/g, ' '); // removes any special characters if (this._initialized) { this.setProfileSrc(); } } set setColor(value) { this._color = value?.replace(/^#+/, ''); } setProfileSrc() { let fallbackParts = ['/avatar', this._displayName, this._size, this._color].filter(x => !!(x)).join('/'); if (!this._userId) { let srcParts = ['/api/my/account/picture', this._size].join('/'); this._img.src = `${this.authSettings.authority}${srcParts}?d=${encodeURIComponent(fallbackParts)}&v=${this._version}`; // create my link return; } (async () => { // create public link const hash = await this.generateSHA256Hash(this._userId); let srcParts = ['/pictures', hash, this._size].join('/'); this._img.src = `${this.authSettings.authority}${srcParts}?d=${encodeURIComponent(fallbackParts)}&v=${this._version}`; })(); this._img.alt = `Profile picture ${this._displayName}`; } async generateSHA256Hash(text) { // Encode the text as UTF-8 const encoder = new TextEncoder(); const data = encoder.encode(text); // Compute the hash const hashBuffer = await crypto.subtle.digest('SHA-256', data); // Convert the hash to a hexadecimal string const hashArray = Array.from(new Uint8Array(hashBuffer)); const hashHex = hashArray.map(byte => byte.toString(16).padStart(2, '0')).join(''); return hashHex; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: ImgUserPictureDirective, deps: [{ token: AUTH_SETTINGS }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.1", type: ImgUserPictureDirective, isStandalone: false, selector: "img[userPicture]", inputs: { setUserId: ["userPicture", "setUserId"], setSize: ["size", "setSize"], setVersion: ["version", "setVersion"], setDisplayName: ["displayName", "setDisplayName"], setColor: ["color", "setColor"] }, ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: ImgUserPictureDirective, decorators: [{ type: Directive, args: [{ selector: 'img[userPicture]', standalone: false }] }], ctorParameters: () => [{ type: undefined, decorators: [{ type: Inject, args: [AUTH_SETTINGS] }] }, { type: i0.ElementRef }], propDecorators: { setUserId: [{ type: Input, args: ['userPicture'] }], setSize: [{ type: Input, args: ['size'] }], setVersion: [{ type: Input, args: ['version'] }], setDisplayName: [{ type: Input, args: ['displayName'] }], setColor: [{ type: Input, args: ['color'] }] } }); class IndiceAuthModule { // tslint:disable-next-line:typedef static forRoot() { return { ngModule: IndiceAuthModule, providers: [ AuthGuardService, AuthHttpInterceptor, AuthService, TenantHeaderInterceptor, TenantService ] }; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: IndiceAuthModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); } static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.1", ngImport: i0, type: IndiceAuthModule, declarations: [ImgUserPictureDirective], exports: [ImgUserPictureDirective] }); } static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: IndiceAuthModule }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.1", ngImport: i0, type: IndiceAuthModule, decorators: [{ type: NgModule, args: [{ declarations: [ImgUserPictureDirective], imports: [], exports: [ImgUserPictureDirective] }] }] }); class DefaultAuthSettings { constructor() { this.authority = ''; this.client_id = ''; this.redirect_uri = ''; this.post_logout_redirect_uri = ''; this.response_type = ''; this.scope = ''; this.filterProtocolClaims = true; this.loadUserInfo = false; this.silent_redirect_uri = ''; this.extraQueryParams = {}; } } class SignInRedirectOptions { } /* * Public API Surface of indice-auth */ /** * Generated bundle index. Do not edit. */ export { AUTH_SETTINGS, AuthGuardService, AuthHttpInterceptor, AuthService, DefaultAuthSettings, ImgUserPictureDirective, IndiceAuthModule, SignInRedirectOptions, TENANT_PREFIX_URL, TenantHeaderInterceptor, TenantService }; //# sourceMappingURL=indice-ng-auth.mjs.map