From 65b03e63f7627de198be777e08e8e0acbfe42c4f Mon Sep 17 00:00:00 2001 From: masumulu28 Date: Mon, 25 Dec 2023 11:24:36 +0300 Subject: [PATCH] feat: create auth-error-filter and include filters to oauth error listener * Create abstarct-auth-filter * Create auth-events model * Create oauth-error-filter.service --- npm/ng-packs/packages/core/package.json | 5 +- .../src/lib/abstracts/auth-error-filter.ts | 34 +++++++++++ .../core/src/lib/abstracts/auth.service.ts | 3 +- .../packages/core/src/lib/abstracts/index.ts | 1 + .../packages/core/src/lib/core.module.ts | 4 +- .../core/src/lib/models/auth-events.ts | 60 +++++++++++++++++++ .../packages/core/src/lib/models/auth.ts | 11 +++- .../packages/core/src/lib/models/index.ts | 1 + npm/ng-packs/packages/oauth/package.json | 6 +- .../packages/oauth/src/lib/oauth.module.ts | 6 +- .../packages/oauth/src/lib/services/index.ts | 1 + .../services/oauth-error-filter.service.ts | 46 ++++++++++++++ .../oauth/src/lib/services/oauth.service.ts | 6 +- .../src/lib/strategies/auth-flow-strategy.ts | 21 +++++-- 14 files changed, 185 insertions(+), 20 deletions(-) create mode 100644 npm/ng-packs/packages/core/src/lib/abstracts/auth-error-filter.ts create mode 100644 npm/ng-packs/packages/core/src/lib/models/auth-events.ts create mode 100644 npm/ng-packs/packages/oauth/src/lib/services/oauth-error-filter.service.ts diff --git a/npm/ng-packs/packages/core/package.json b/npm/ng-packs/packages/core/package.json index ceece99e72..b3a368f2b1 100644 --- a/npm/ng-packs/packages/core/package.json +++ b/npm/ng-packs/packages/core/package.json @@ -8,9 +8,8 @@ }, "dependencies": { "@abp/utils": "~7.4.4", - "angular-oauth2-oidc": "^15.0.1", - "just-clone": "^6.1.1", - "just-compare": "^2.3.0", + "just-clone": "^6.0.0", + "just-compare": "^2.0.0", "ts-toolbelt": "6.15.4", "tslib": "^2.0.0" }, diff --git a/npm/ng-packs/packages/core/src/lib/abstracts/auth-error-filter.ts b/npm/ng-packs/packages/core/src/lib/abstracts/auth-error-filter.ts new file mode 100644 index 0000000000..eb0a90ade2 --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/abstracts/auth-error-filter.ts @@ -0,0 +1,34 @@ +import { Signal, signal } from '@angular/core'; +import { AuthErrorEvent, AuthErrorFilter } from '../models'; + +export abstract class AbstractAuthErrorFilter { + readonly #filters = signal>([]); + filters = this.#filters.asReadonly(); + + abstract get(id: string): T; + abstract add(filter: T): void; + abstract patch(item: Partial): void; + abstract remove(id: string): void; + abstract run(event: E): boolean; +} + +export class AuthErrorFilterService< + T = AuthErrorEvent, + E = AuthErrorFilter, +> extends AbstractAuthErrorFilter { + get(id: string): T { + throw new Error('Import AbpOAuthModule from @abp/ng.oauth or custom implementation'); + } + add(filter: T): void { + throw new Error('Import AbpOAuthModule from @abp/ng.oauth or custom implementation'); + } + patch(item: Partial): void { + throw new Error('Import AbpOAuthModule from @abp/ng.oauth or custom implementation'); + } + remove(id: string): void { + throw new Error('Import AbpOAuthModule from @abp/ng.oauth or custom implementation'); + } + run(event: E): boolean { + throw new Error('Import AbpOAuthModule from @abp/ng.oauth or custom implementation'); + } +} diff --git a/npm/ng-packs/packages/core/src/lib/abstracts/auth.service.ts b/npm/ng-packs/packages/core/src/lib/abstracts/auth.service.ts index 3c6d62fae6..8527232d5f 100644 --- a/npm/ng-packs/packages/core/src/lib/abstracts/auth.service.ts +++ b/npm/ng-packs/packages/core/src/lib/abstracts/auth.service.ts @@ -33,9 +33,8 @@ export class AuthService implements IAuthService { navigateToLogin(queryParams?: Params): void {} - get isInternalAuth() { + get isInternalAuth(): boolean { throw new Error('not implemented'); - return false; } get isAuthenticated(): boolean { diff --git a/npm/ng-packs/packages/core/src/lib/abstracts/index.ts b/npm/ng-packs/packages/core/src/lib/abstracts/index.ts index f9b4794de0..97f3c524f4 100644 --- a/npm/ng-packs/packages/core/src/lib/abstracts/index.ts +++ b/npm/ng-packs/packages/core/src/lib/abstracts/index.ts @@ -3,3 +3,4 @@ export * from './ng-model.component'; export * from './auth.guard'; export * from './auth.service'; export * from './auth-response.model'; +export * from './auth-error-filter'; diff --git a/npm/ng-packs/packages/core/src/lib/core.module.ts b/npm/ng-packs/packages/core/src/lib/core.module.ts index c4f7c78fa7..0ec8f505f8 100644 --- a/npm/ng-packs/packages/core/src/lib/core.module.ts +++ b/npm/ng-packs/packages/core/src/lib/core.module.ts @@ -39,6 +39,7 @@ import { QUEUE_MANAGER } from './tokens/queue.token'; import { DefaultQueueManager } from './utils/queue'; import { IncludeLocalizationResourcesProvider } from './providers/include-localization-resources.provider'; import { SORT_COMPARE_FUNC, compareFuncFactory } from './tokens/compare-func.token'; +import { AuthErrorFilterService } from './abstracts'; /** * BaseCoreModule is the module that holds @@ -179,7 +180,7 @@ export class CoreModule { }, { provide: SORT_COMPARE_FUNC, - useFactory: compareFuncFactory + useFactory: compareFuncFactory, }, { provide: QUEUE_MANAGER, @@ -189,6 +190,7 @@ export class CoreModule { provide: OTHERS_GROUP, useValue: options.othersGroup || 'AbpUi::OthersGroup', }, + AuthErrorFilterService, IncludeLocalizationResourcesProvider, ], }; diff --git a/npm/ng-packs/packages/core/src/lib/models/auth-events.ts b/npm/ng-packs/packages/core/src/lib/models/auth-events.ts new file mode 100644 index 0000000000..e83fc7dd27 --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/models/auth-events.ts @@ -0,0 +1,60 @@ +export type EventType = + | 'discovery_document_loaded' + | 'jwks_load_error' + | 'invalid_nonce_in_state' + | 'discovery_document_load_error' + | 'discovery_document_validation_error' + | 'user_profile_loaded' + | 'user_profile_load_error' + | 'token_received' + | 'token_error' + | 'code_error' + | 'token_refreshed' + | 'token_refresh_error' + | 'silent_refresh_error' + | 'silently_refreshed' + | 'silent_refresh_timeout' + | 'token_validation_error' + | 'token_expires' + | 'session_changed' + | 'session_error' + | 'session_terminated' + | 'session_unchanged' + | 'logout' + | 'popup_closed' + | 'popup_blocked' + | 'token_revoke_error'; + +export abstract class AuthEvent { + readonly type: EventType; + constructor(type: EventType) { + this.type = type; + } +} + +export class AuthSuccessEvent extends AuthEvent { + readonly info: any; + constructor(type: EventType, info?: any) { + super(type); + this.info = info; + } +} + +export class AuthInfoEvent extends AuthEvent { + readonly info: any; + + constructor(type: EventType, info?: any) { + super(type); + this.info = info; + } +} + +export class AuthErrorEvent extends AuthEvent { + readonly reason: object; + readonly params: object; + constructor(type: EventType, reason: object, params?: object) { + super(type); + this.reason = reason; + this.params = params; + } +} diff --git a/npm/ng-packs/packages/core/src/lib/models/auth.ts b/npm/ng-packs/packages/core/src/lib/models/auth.ts index dcaf6e7f11..23818878f4 100644 --- a/npm/ng-packs/packages/core/src/lib/models/auth.ts +++ b/npm/ng-packs/packages/core/src/lib/models/auth.ts @@ -1,5 +1,6 @@ -import { UnaryFunction } from 'rxjs'; import { Injector } from '@angular/core'; +import { UnaryFunction } from 'rxjs'; +import { AuthErrorEvent } from './auth-events'; export interface LoginParams { username: string; @@ -13,7 +14,13 @@ export type PipeToLoginFn = ( injector: Injector, ) => UnaryFunction; /** - * @deprecated The interface should not be used anymore. + * @deprecated The interface should not be used anymore. */ export type SetTokenResponseToStorageFn = (tokenRes: T) => void; export type CheckAuthenticationStateFn = (injector: Injector) => void; + +export interface AuthErrorFilter { + id: string; + executable: boolean; + execute: (event: T) => boolean; +} diff --git a/npm/ng-packs/packages/core/src/lib/models/index.ts b/npm/ng-packs/packages/core/src/lib/models/index.ts index 0549dd0128..c343ee2440 100644 --- a/npm/ng-packs/packages/core/src/lib/models/index.ts +++ b/npm/ng-packs/packages/core/src/lib/models/index.ts @@ -7,3 +7,4 @@ export * from './rest'; export * from './session'; export * from './utility'; export * from './auth'; +export * from './auth-events'; diff --git a/npm/ng-packs/packages/oauth/package.json b/npm/ng-packs/packages/oauth/package.json index 7359ccc829..83fedc2d53 100644 --- a/npm/ng-packs/packages/oauth/package.json +++ b/npm/ng-packs/packages/oauth/package.json @@ -9,9 +9,9 @@ "dependencies": { "@abp/ng.core": "~7.4.4", "@abp/utils": "~7.4.4", - "angular-oauth2-oidc": "^15.0.1", - "just-clone": "^6.1.1", - "just-compare": "^2.3.0", + "angular-oauth2-oidc": "^15.0.0", + "just-clone": "^6.0.0", + "just-compare": "^2.0.0", "tslib": "^2.0.0" }, "publishConfig": { diff --git a/npm/ng-packs/packages/oauth/src/lib/oauth.module.ts b/npm/ng-packs/packages/oauth/src/lib/oauth.module.ts index ac800e7c8f..9c67cb1a88 100644 --- a/npm/ng-packs/packages/oauth/src/lib/oauth.module.ts +++ b/npm/ng-packs/packages/oauth/src/lib/oauth.module.ts @@ -1,18 +1,19 @@ import { APP_INITIALIZER, ModuleWithProviders, NgModule, Provider } from '@angular/core'; import { CommonModule } from '@angular/common'; +import { HTTP_INTERCEPTORS } from '@angular/common/http'; import { OAuthModule, OAuthStorage } from 'angular-oauth2-oidc'; import { AbpLocalStorageService, ApiInterceptor, + AuthErrorFilterService, AuthGuard, AuthService, CHECK_AUTHENTICATION_STATE_FN_KEY, noop, PIPE_TO_LOGIN_FN_KEY, } from '@abp/ng.core'; -import { AbpOAuthService } from './services'; +import { AbpOAuthService, OAuthErrorFilterService } from './services'; import { OAuthConfigurationHandler } from './handlers/oauth-configuration.handler'; -import { HTTP_INTERCEPTORS } from '@angular/common/http'; import { OAuthApiInterceptor } from './interceptors/api.interceptor'; import { AbpOAuthGuard } from './guards/oauth.guard'; import { NavigateToManageProfileProvider } from './providers'; @@ -60,6 +61,7 @@ export class AbpOAuthModule { }, OAuthModule.forRoot().providers as Provider[], { provide: OAuthStorage, useClass: AbpLocalStorageService }, + { provide: AuthErrorFilterService, useExisting: OAuthErrorFilterService }, ], }; } diff --git a/npm/ng-packs/packages/oauth/src/lib/services/index.ts b/npm/ng-packs/packages/oauth/src/lib/services/index.ts index ddc54916a3..375177f3b5 100644 --- a/npm/ng-packs/packages/oauth/src/lib/services/index.ts +++ b/npm/ng-packs/packages/oauth/src/lib/services/index.ts @@ -1 +1,2 @@ export * from './oauth.service'; +export * from './oauth-error-filter.service'; diff --git a/npm/ng-packs/packages/oauth/src/lib/services/oauth-error-filter.service.ts b/npm/ng-packs/packages/oauth/src/lib/services/oauth-error-filter.service.ts new file mode 100644 index 0000000000..bcd048064f --- /dev/null +++ b/npm/ng-packs/packages/oauth/src/lib/services/oauth-error-filter.service.ts @@ -0,0 +1,46 @@ +import { AbstractAuthErrorFilter, AuthErrorFilter } from '@abp/ng.core'; +import { Injectable, signal } from '@angular/core'; +import { OAuthErrorEvent } from 'angular-oauth2-oidc'; + +@Injectable({ providedIn: 'root' }) +export class OAuthErrorFilterService extends AbstractAuthErrorFilter< + AuthErrorFilter, + OAuthErrorEvent +> { + readonly #filters = signal>>([]); + + filters = this.#filters.asReadonly(); + + get(id: string): AuthErrorFilter { + return this.#filters().find(({ id: _id }) => _id === id); + } + + add(filter: AuthErrorFilter): void { + this.#filters.update(items => [...items, filter]); + } + + patch(item: Partial>): void { + const _item = this.filters().find(({ id }) => id === item.id); + if (!_item) { + return; + } + + Object.assign(_item, item); + } + + remove(id: string): void { + const item = this.filters().find(({ id: _id }) => _id === id); + if (!item) { + return; + } + + this.#filters.update(items => items.filter(({ id: _id }) => _id !== id)); + } + + run(event: OAuthErrorEvent): boolean { + return this.filters() + .filter(({ executable }) => !!executable) + .map(({ execute }) => execute(event)) + .some(item => item); + } +} diff --git a/npm/ng-packs/packages/oauth/src/lib/services/oauth.service.ts b/npm/ng-packs/packages/oauth/src/lib/services/oauth.service.ts index 8b10d39bf2..2e615c95a5 100644 --- a/npm/ng-packs/packages/oauth/src/lib/services/oauth.service.ts +++ b/npm/ng-packs/packages/oauth/src/lib/services/oauth.service.ts @@ -44,10 +44,10 @@ export class AbpOAuthService implements IAuthService { } logout(queryParams?: Params): Observable { - - if(!this.strategy){ - return EMPTY + if (!this.strategy) { + return EMPTY; } + return this.strategy.logout(queryParams); } diff --git a/npm/ng-packs/packages/oauth/src/lib/strategies/auth-flow-strategy.ts b/npm/ng-packs/packages/oauth/src/lib/strategies/auth-flow-strategy.ts index b47eeb3ac7..e5189966bd 100644 --- a/npm/ng-packs/packages/oauth/src/lib/strategies/auth-flow-strategy.ts +++ b/npm/ng-packs/packages/oauth/src/lib/strategies/auth-flow-strategy.ts @@ -1,13 +1,16 @@ import { Injector } from '@angular/core'; +import { HttpErrorResponse } from '@angular/common/http'; import { Params, Router } from '@angular/router'; + +import { Observable, of } from 'rxjs'; +import { filter, map, switchMap, take, tap } from 'rxjs/operators'; import { AuthConfig, OAuthErrorEvent, OAuthService as OAuthService2, OAuthStorage, } from 'angular-oauth2-oidc'; -import { Observable, of } from 'rxjs'; -import { filter, map, switchMap, take, tap } from 'rxjs/operators'; + import { AbpLocalStorageService, ConfigStateService, @@ -17,9 +20,10 @@ import { SessionStateService, TENANT_KEY, } from '@abp/ng.core'; + import { clearOAuthStorage } from '../utils/clear-o-auth-storage'; import { oAuthStorage } from '../utils/oauth-storage'; -import { HttpErrorResponse } from '@angular/common/http'; +import { OAuthErrorFilterService } from '../services'; export abstract class AuthFlowStrategy { abstract readonly isInternalAuth: boolean; @@ -34,6 +38,8 @@ export abstract class AuthFlowStrategy { protected tenantKey: string; protected router: Router; + protected readonly oAuthErrorFilterService: OAuthErrorFilterService; + abstract checkIfInternalAuth(queryParams?: Params): boolean; abstract navigateToLogin(queryParams?: Params): void; abstract logout(queryParams?: Params): Observable; @@ -54,6 +60,7 @@ export abstract class AuthFlowStrategy { this.oAuthConfig = this.environment.getEnvironment().oAuthConfig || {}; this.tenantKey = injector.get(TENANT_KEY); this.router = injector.get(Router); + this.oAuthErrorFilterService = injector.get(OAuthErrorFilterService); this.listenToOauthErrors(); } @@ -97,6 +104,7 @@ export abstract class AuthFlowStrategy { if (redirect_uri && redirect_uri !== '/') { return redirect_uri; } + return '/'; }), switchMap(redirectUri => @@ -118,7 +126,12 @@ export abstract class AuthFlowStrategy { this.oAuthService.events .pipe( filter(event => event instanceof OAuthErrorEvent), - tap(() => clearOAuthStorage()), + tap((err: OAuthErrorEvent) => { + const shouldSkip = this.oAuthErrorFilterService.run(err); + if (!shouldSkip) { + clearOAuthStorage(); + } + }), switchMap(() => this.configState.refreshAppState()), ) .subscribe();