@indice/ng-auth
Version:
Indice extensions for Angular v20 using oidc-client
512 lines (499 loc) • 20.3 kB
JavaScript
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