diff --git a/docs/how-to-start.zh-CN.md b/docs/how-to-start.zh-CN.md index 607fe066e..4b78a8279 100644 --- a/docs/how-to-start.zh-CN.md +++ b/docs/how-to-start.zh-CN.md @@ -144,7 +144,7 @@ const routes: Routes = [ { path: '', component: LayoutBasicComponent, - canActivate: [SimpleGuard], + canActivate: [authSimpleCanActivate], children: [ { path: '', redirectTo: 'dashboard', pathMatch: 'full' }, { path: 'dashboard', component: DashboardComponent, data: { title: '仪表盘' } }, @@ -153,19 +153,19 @@ const routes: Routes = [ ]; ``` -> 这里的 `SimpleGuard` 是因为采用基于 Simple Web Token 认证风格,其他认证方式请参考[用户认证](/auth)章节。 +> 这里的 `authSimpleCanActivate` 是因为采用基于 Simple Web Token 认证风格,其他认证方式请参考[用户认证](/auth)章节。 当用户未登录时会直接跳转至 `/passport/login` 页面,如果采用的是 JWT 认证方式,还会对 Token 是否有效进行检验。 #### 用户授权 -接者用户访问的页面还需要取决于授权程度,例如系统配置页普通用户肯定无法进入。在初始化项目数据小节里会根据当前用户的 Token 来获得授权的数据,并将数据交给 `@delon/acl`,同时它也提供一组路由守卫的具体实现 `ACLGuard` 类,例如希望整个系统配置模块都必须是 `admin` 角色才能访问,则: +接者用户访问的页面还需要取决于授权程度,例如系统配置页普通用户肯定无法进入。在初始化项目数据小节里会根据当前用户的 Token 来获得授权的数据,并将数据交给 `@delon/acl`,同时它也提供一组路由守卫的具体实现 `aclCanActivate` 方法,例如希望整个系统配置模块都必须是 `admin` 角色才能访问,则: ```ts const routes: Routes = [ { path: 'sys', - canActivate: [ACLGuard], + canActivate: [aclCanActivate], data: { guard: 'admin' }, children: [ { path: 'config', component: ConfigComponent }, diff --git a/docs/i18n.en-US.md b/docs/i18n.en-US.md index c845d3355..5823ae737 100644 --- a/docs/i18n.en-US.md +++ b/docs/i18n.en-US.md @@ -203,14 +203,14 @@ Use the [defaultLanguage](/cli/plugin/zh#defaultLanguage) plugin to quickly swit ## Internationalized routing -If you want to toggle internationalization by routed URLs, e.g. by accessing `/zh` and `/en` to change the language, just use the `AlainI18NGuard` guard in the root route: +If you want to toggle internationalization by routed URLs, e.g. by accessing `/zh` and `/en` to change the language, just use the `alainI18nCanActivate` guard in the root route: ```ts const routes: Route[] = [ { path: '', component: LayoutComponent, - canActivateChild: [AlainI18NGuard], + canActivateChild: [alainI18nCanActivate], children: [ { path: '', redirectTo: 'en', pathMatch: 'full' }, { path: ':i18n', component: HomeComponent } diff --git a/docs/i18n.zh-CN.md b/docs/i18n.zh-CN.md index 4e78fd925..a8e4a2da3 100644 --- a/docs/i18n.zh-CN.md +++ b/docs/i18n.zh-CN.md @@ -203,14 +203,14 @@ export class AppModule {} ## 国际化路由 -若想通过路由的URL来切换国际化,例如:通过访问 `/zh` 和 `/en` 来变更语言,则只需要在根路由中使用 `AlainI18NGuard` 守卫: +若想通过路由的URL来切换国际化,例如:通过访问 `/zh` 和 `/en` 来变更语言,则只需要在根路由中使用 `alainI18nCanActivate` 守卫: ```ts const routes: Route[] = [ { path: '', component: LayoutComponent, - canActivateChild: [AlainI18NGuard], + canActivateChild: [alainI18nCanActivate], children: [ { path: '', redirectTo: 'en', pathMatch: 'full' }, { path: ':i18n', component: HomeComponent } diff --git a/packages/acl/docs/guard.en-US.md b/packages/acl/docs/guard.en-US.md index 8f25851bd..e889fd398 100644 --- a/packages/acl/docs/guard.en-US.md +++ b/packages/acl/docs/guard.en-US.md @@ -8,7 +8,7 @@ type: Documents Routing guard prevent unauthorized users visit the page. -`@delon/acl` implements the generic guard class `ACLGuard`, which allows for complex operations through simple configuration in route registration, and supports the `Observable` type. +`@delon/acl` implements the generic guard functions `aclCanMatch`, `aclCanActivate`, `aclCanActivateChild`, which allows for complex operations through simple configuration in route registration, and supports the `Observable` type. Use the fixed attribute `guard` to specify the `ACLCanType` parameter value, for example: @@ -16,12 +16,12 @@ Use the fixed attribute `guard` to specify the `ACLCanType` parameter value, for const routes: Routes = [ { path: 'auth', - canActivate: [ ACLGuard ], + canActivate: [ aclCanActivate ], data: { guard: 'user1' as ACLGuardType } }, { path: 'auth', - canActivate: [ ACLGuard ], + canActivate: [ aclCanActivate ], data: { guard: { role: [ 'user1' ], @@ -33,7 +33,7 @@ const routes: Routes = [ }, { path: 'obs', - canActivate: [ ACLGuard ], + canActivate: [ aclCanActivate ], data: { guard: ((_srv, _injector) => { return of('user'); @@ -50,19 +50,19 @@ const routes: Routes = [ ```ts import { of } from 'rxjs'; -import { ACLGuard } from '@delon/acl'; +import { aclCanActivate, aclCanActivateChild, aclCanMatch } from '@delon/acl'; const routes: Routes = [ { path: 'guard', component: GuardComponent, children: [ - { path: 'auth', component: GuardAuthComponent, canActivate: [ ACLGuard ], data: { guard: 'user1' } }, - { path: 'admin', component: GuardAdminComponent, canActivate: [ ACLGuard ], data: { guard: 'admin' } } + { path: 'auth', component: GuardAuthComponent, canActivate: [ aclCanActivate ], data: { guard: 'user1' } }, + { path: 'admin', component: GuardAdminComponent, canActivate: [ aclCanActivate ], data: { guard: 'admin' } } ], - canActivateChild: [ ACLGuard ], + canActivateChild: [ aclCanActivateChild ], data: { guard: { role: [ 'user1' ], ability: [ 10, 'USER-EDIT' ], mode: 'allOf' } } }, - { path: 'pro', loadChildren: './pro/pro.module#ProModule', canMatch: [ ACLGuard ], data: { guard: 1 } }, - { path: 'pro', loadChildren: './pro/pro.module#ProModule', canMatch: [ ACLGuard ], data: { guard: of(false).pipe(map(v => 'admin')) } } + { path: 'pro', loadChildren: './pro/pro.module#ProModule', canMatch: [ aclCanMatch ], data: { guard: 1 } }, + { path: 'pro', loadChildren: './pro/pro.module#ProModule', canMatch: [ aclCanMatch ], data: { guard: of(false).pipe(map(v => 'admin')) } } ]; ``` diff --git a/packages/acl/docs/guard.zh-CN.md b/packages/acl/docs/guard.zh-CN.md index 47253a69a..6df6e61c3 100644 --- a/packages/acl/docs/guard.zh-CN.md +++ b/packages/acl/docs/guard.zh-CN.md @@ -8,7 +8,7 @@ type: Documents 路由守卫可以防止未授权用户访问页面。 -路由守卫需要单独对每一个路由进行设置,很多时候这看起来很繁琐,`@delon/acl` 实现了通用守卫类 `ACLGuard`,可以在路由注册时透过简单的配置完成一些复杂的操作,甚至支持 `Observable` 类型。 +路由守卫需要单独对每一个路由进行设置,很多时候这看起来很繁琐,`@delon/acl` 实现了通用守卫函数 `aclCanMatch`, `aclCanActivate`, `aclCanActivateChild`,可以在路由注册时透过简单的配置完成一些复杂的操作,甚至支持 `Observable` 类型。 使用固定属性 `guard` 来指定 `ACLCanType` 参数,例如: @@ -16,12 +16,12 @@ type: Documents const routes: Routes = [ { path: 'auth', - canActivate: [ ACLGuard ], + canActivate: [ aclCanActivate ], data: { guard: 'user1' as ACLGuardType } }, { path: 'auth', - canActivate: [ ACLGuard ], + canActivate: [ aclCanActivate ], data: { guard: { role: [ 'user1' ], @@ -33,7 +33,7 @@ const routes: Routes = [ }, { path: 'obs', - canActivate: [ ACLGuard ], + canActivate: [ aclCanActivate ], data: { guard: ((_srv, _injector) => { return of('user'); @@ -50,23 +50,19 @@ const routes: Routes = [ ```ts import { of } from 'rxjs'; -import { ACLGuard } from '@delon/acl'; +import { aclCanActivate, aclCanActivateChild, aclCanMatch } from '@delon/acl'; const routes: Routes = [ { path: 'guard', component: GuardComponent, children: [ - // 角色限定 - { path: 'auth', component: GuardAuthComponent, canActivate: [ ACLGuard ], data: { guard: 'user1' } }, - { path: 'admin', component: GuardAdminComponent, canActivate: [ ACLGuard ], data: { guard: 'admin' } } + { path: 'auth', component: GuardAuthComponent, canActivate: [ aclCanActivate ], data: { guard: 'user1' } }, + { path: 'admin', component: GuardAdminComponent, canActivate: [ aclCanActivate ], data: { guard: 'admin' } } ], - // 所有子路由有效 - canActivateChild: [ ACLGuard ], + canActivateChild: [ aclCanActivateChild ], data: { guard: { role: [ 'user1' ], ability: [ 10, 'USER-EDIT' ], mode: 'allOf' } } }, - // 权限点限定 - { path: 'pro', loadChildren: './pro/pro.module#ProModule', canMatch: [ ACLGuard ], data: { guard: 1 } }, - // 或使用Observable实现更复杂的行为 - { path: 'pro', loadChildren: './pro/pro.module#ProModule', canMatch: [ ACLGuard ], data: { guard: of(false).pipe(map(v => 'admin')) } } + { path: 'pro', loadChildren: './pro/pro.module#ProModule', canMatch: [ aclCanMatch ], data: { guard: 1 } }, + { path: 'pro', loadChildren: './pro/pro.module#ProModule', canMatch: [ aclCanMatch ], data: { guard: of(false).pipe(map(v => 'admin')) } } ]; ``` diff --git a/packages/acl/src/acl-guard.spec.ts b/packages/acl/src/acl-guard.spec.ts index 5122bd301..ede2957ee 100644 --- a/packages/acl/src/acl-guard.spec.ts +++ b/packages/acl/src/acl-guard.spec.ts @@ -1,211 +1,188 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ +import { Component } from '@angular/core'; import { TestBed } from '@angular/core/testing'; import { Router } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; import { of } from 'rxjs'; -import { ACLGuard } from './acl-guard'; +import { ACLGuardService, aclCanActivate, aclCanActivateChild, aclCanMatch } from './acl-guard'; import { DelonACLModule } from './acl.module'; import { ACLService } from './acl.service'; -import { ACLGuardFunctionType, ACLGuardType, ACLType } from './acl.type'; +import { ACLGuardData } from './acl.type'; describe('acl: guard', () => { - let srv: ACLGuard; + let srv: ACLGuardService; let acl: ACLService; + let router: Router; let routerSpy: jasmine.Spy; beforeEach(() => { TestBed.configureTestingModule({ - imports: [RouterTestingModule.withRoutes([]), DelonACLModule.forRoot()] + declarations: [TestComponent], + imports: [ + RouterTestingModule.withRoutes([ + { path: '403', component: TestComponent }, + { + path: 'canActivate', + component: TestComponent, + canActivate: [aclCanActivate], + data: { guard: { role: ['admin'] } } as ACLGuardData + }, + { + path: 'canActivateChild', + canActivateChild: [aclCanActivateChild], + data: { guard: { role: ['admin'] } } as ACLGuardData, + children: [{ path: '1', component: TestComponent }] + }, + { + path: 'canMatch', + component: TestComponent, + canMatch: [aclCanMatch], + data: { guard: { role: ['admin'] } } as ACLGuardData + } + ]), + DelonACLModule.forRoot() + ] }); - srv = TestBed.inject(ACLGuard); + srv = TestBed.inject(ACLGuardService); acl = TestBed.inject(ACLService); acl.set({ role: ['user'], ability: [1, 2, 3] - } as ACLType); - routerSpy = spyOn(TestBed.inject(Router), 'navigateByUrl'); - }); - - it(`should load route when no-specify permission`, (done: () => void) => { - srv.canActivate({} as any, null).subscribe(res => { - expect(res).toBeTruthy(); - done(); }); + router = TestBed.inject(Router); }); - it(`should load route when specify permission`, (done: () => void) => { - srv - .canActivate( - { - data: { - guard: 'user' - } - } as any, - null - ) - .subscribe(res => { - expect(res).toBeTruthy(); - done(); - }); - }); + describe('', () => { + beforeEach(() => (routerSpy = spyOn(router, 'navigateByUrl'))); - it(`should unable load route if no-permission`, (done: () => void) => { - srv - .canActivate( - { - data: { - guard: 'admin' - } - } as any, - null - ) - .subscribe(res => { - expect(res).toBeFalsy(); - done(); - }); - }); - - it(`should load route via function`, (done: () => void) => { - srv - .canActivate( - { - data: { - guard: ((_srv, _injector) => { - return of('user'); - }) as ACLGuardFunctionType - } - } as any, - null - ) - .subscribe(res => { + it(`should load route when no-specify permission`, (done: () => void) => { + srv.process({}).subscribe(res => { expect(res).toBeTruthy(); done(); }); - }); - - it(`should load route via Observable`, (done: () => void) => { - srv - .canActivate( - { - data: { - guard: of('user') as ACLGuardType - } - } as any, - null - ) - .subscribe(res => { - expect(res).toBeTruthy(); - done(); - }); - }); + }); - it(`should load route using ability`, (done: () => void) => { - srv - .canActivate( - { - data: { - guard: of(1) - } - } as any, - null - ) - .subscribe(res => { - expect(res).toBeTruthy(); - done(); - }); - }); + it(`should load route when specify permission`, (done: () => void) => { + srv + .process({ + guard: 'user' + }) + .subscribe(res => { + expect(res).toBeTruthy(); + done(); + }); + }); - it(`should unable load route using ability`, (done: () => void) => { - srv - .canActivate( - { - data: { - guard: of(10) - } - } as any, - null - ) - .subscribe(res => { - expect(res).toBeFalsy(); - done(); - }); - }); + it(`should unable load route if no-permission`, (done: () => void) => { + srv + .process({ + guard: 'admin' + }) + .subscribe(res => { + expect(res).toBeFalsy(); + done(); + }); + }); - describe(`#canMatch`, () => { - it(`should be can load when has [user] role`, (done: () => void) => { + it(`should load route via function`, (done: () => void) => { srv - .canMatch({ - data: { - guard: of('user') - } - } as any) + .process({ + guard: (_srv, _injector) => of('user') + }) .subscribe(res => { expect(res).toBeTruthy(); done(); }); }); - it(`should be can load when is null`, (done: () => void) => { + + it(`should load route via Observable`, (done: () => void) => { srv - .canMatch({ - data: { - guard: null - } - } as any) + .process({ + guard: of('user') + }) .subscribe(res => { expect(res).toBeTruthy(); done(); }); }); - }); - - it(`#canActivateChild`, (done: () => void) => { - srv - .canActivateChild( - { - data: { - guard: of('user') - } - } as any, - null! - ) - .subscribe(res => { - expect(res).toBeTruthy(); - done(); - }); - }); - describe('#guard_url', () => { - it(`should be rediect to default url: /403`, (done: () => void) => { + it(`should load route using ability`, (done: () => void) => { srv - .canActivate( - { - data: { - guard: 'admin' - } - } as any, - null - ) - .subscribe(() => { - expect(routerSpy.calls.first().args[0]).toBe(`/403`); + .process({ + guard: of(1) + }) + .subscribe(res => { + expect(res).toBeTruthy(); done(); }); }); - it(`should be specify rediect url`, (done: () => void) => { + + it(`should unable load route using ability`, (done: () => void) => { srv - .canActivate( - { - data: { - guard: 'admin', - guard_url: '/no' - } - } as any, - null - ) - .subscribe(() => { - expect(routerSpy.calls.first().args[0]).toBe(`/no`); + .process({ + guard: of(10) + }) + .subscribe(res => { + expect(res).toBeFalsy(); done(); }); }); + + describe('#guard_url', () => { + it(`should be rediect to default url: /403`, (done: () => void) => { + srv + .process({ + guard: 'admin' + }) + .subscribe(() => { + expect(routerSpy.calls.first().args[0]).toBe(`/403`); + done(); + }); + }); + it(`should be specify rediect url`, (done: () => void) => { + srv + .process({ + guard: 'admin', + guard_url: '/no' + }) + .subscribe(() => { + expect(routerSpy.calls.first().args[0]).toBe(`/no`); + done(); + }); + }); + }); + }); + + describe('#router', () => { + it('canMatch', async () => { + acl.set({ role: ['user'] }); + const targetUrl = '/canMatch'; + await router.navigateByUrl(targetUrl); + expect(router.url).toBe('/403'); + acl.set({ role: ['admin'] }); + await router.navigateByUrl(targetUrl); + expect(router.url).toBe(targetUrl); + }); + it('canActivate', async () => { + acl.set({ role: ['user'] }); + const targetUrl = '/canActivate'; + await router.navigateByUrl(targetUrl); + expect(router.url).toBe('/403'); + acl.set({ role: ['admin'] }); + await router.navigateByUrl(targetUrl); + expect(router.url).toBe(targetUrl); + }); + it('canActivateChild', async () => { + acl.set({ role: ['user'] }); + const targetUrl = '/canActivateChild/1'; + await router.navigateByUrl(targetUrl); + expect(router.url).toBe('/403'); + acl.set({ role: ['admin'] }); + await router.navigateByUrl(targetUrl); + expect(router.url).toBe(targetUrl); + }); }); }); + +@Component({ template: `` }) +class TestComponent {} diff --git a/packages/acl/src/acl-guard.ts b/packages/acl/src/acl-guard.ts index 990779f8f..1b1339cd3 100644 --- a/packages/acl/src/acl-guard.ts +++ b/packages/acl/src/acl-guard.ts @@ -1,65 +1,71 @@ -import { Injectable, Injector } from '@angular/core'; -import { - ActivatedRouteSnapshot, - CanActivate, - CanActivateChild, - CanMatch, - Data, - Route, - Router, - RouterStateSnapshot -} from '@angular/router'; +import { Injectable, Injector, inject } from '@angular/core'; +import { CanActivateChildFn, CanActivateFn, CanMatchFn, Router } from '@angular/router'; import { Observable, of, map, tap } from 'rxjs'; import { ACLService } from './acl.service'; -import { ACLCanType, ACLGuardType } from './acl.type'; +import type { ACLCanType, ACLGuardData } from './acl.type'; -/** - * Routing guard prevent unauthorized users visit the page, [ACL Document](https://ng-alain.com/acl). - * - * ```ts - * data: { - * path: 'home', - * canActivate: [ ACLGuard ], - * data: { guard: 'user1' } - * } - * ``` - */ -@Injectable({ providedIn: 'root' }) -export class ACLGuard implements CanActivate, CanActivateChild, CanMatch { +@Injectable() +export class ACLGuardService { constructor( private srv: ACLService, private router: Router, private injector: Injector ) {} - private process(data: Data): Observable { + process(data?: ACLGuardData): Observable { data = { guard: null, guard_url: this.srv.guard_url, ...data }; - let guard: ACLGuardType = data.guard; + let guard = data.guard; if (typeof guard === 'function') guard = guard(this.srv, this.injector); return (guard && guard instanceof Observable ? guard : of(guard != null ? (guard as ACLCanType) : null)).pipe( map(v => this.srv.can(v)), tap(v => { if (v) return; - this.router.navigateByUrl(data.guard_url); + this.router.navigateByUrl(data!!.guard_url!!); }) ); } - - // lazy loading - canMatch(route: Route): Observable { - return this.process(route.data!); - } - // all children route - canActivateChild(childRoute: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { - return this.canActivate(childRoute, state); - } - // route - canActivate(route: ActivatedRouteSnapshot, _state: RouterStateSnapshot | null): Observable { - return this.process(route.data); - } } + +/** + * Routing guard prevent unauthorized users visit the page, [ACL Document](https://ng-alain.com/acl). + * + * ```ts + * data: { + * path: 'home', + * canActivate: [ aclCanActivate ], + * data: { guard: 'user1' } + * } + * ``` + */ +export const aclCanActivate: CanActivateFn = route => inject(ACLGuardService).process(route.data); + +/** + * Routing guard prevent unauthorized users visit the page, [ACL Document](https://ng-alain.com/acl). + * + * ```ts + * data: { + * path: 'home', + * canActivateChild: [ aclCanActivateChild ], + * data: { guard: 'user1' } + * } + * ``` + */ +export const aclCanActivateChild: CanActivateChildFn = route => inject(ACLGuardService).process(route.data); + +/** + * Routing guard prevent unauthorized users visit the page, [ACL Document](https://ng-alain.com/acl). + * + * ```ts + * data: { + * path: 'home', + * canMatch: [ aclCanMatch ], + * data: { guard: 'user1' } + * } + * ``` + */ +export const aclCanMatch: CanMatchFn = route => inject(ACLGuardService).process(route.data); diff --git a/packages/acl/src/acl.module.ts b/packages/acl/src/acl.module.ts index 91f56da5b..64e366cc9 100644 --- a/packages/acl/src/acl.module.ts +++ b/packages/acl/src/acl.module.ts @@ -1,6 +1,7 @@ import { CommonModule } from '@angular/common'; import { ModuleWithProviders, NgModule } from '@angular/core'; +import { ACLGuardService } from './acl-guard'; import { ACLIfDirective } from './acl-if.directive'; import { ACLDirective } from './acl.directive'; import { ACLService } from './acl.service'; @@ -16,7 +17,7 @@ export class DelonACLModule { static forRoot(): ModuleWithProviders { return { ngModule: DelonACLModule, - providers: [ACLService] + providers: [ACLService, ACLGuardService] }; } } diff --git a/packages/acl/src/acl.type.ts b/packages/acl/src/acl.type.ts index 409f33b99..4b0ad5c5e 100644 --- a/packages/acl/src/acl.type.ts +++ b/packages/acl/src/acl.type.ts @@ -39,3 +39,8 @@ export type ACLCanType = number | number[] | string | string[] | ACLType; export type ACLGuardFunctionType = (srv: ACLService, injector: Injector) => Observable; export type ACLGuardType = ACLCanType | Observable | ACLGuardFunctionType; + +export interface ACLGuardData { + guard?: ACLGuardType | null; + guard_url?: string | null; +} diff --git a/packages/auth/docs/guard.en-US.md b/packages/auth/docs/guard.en-US.md index f0aacd353..c6842608a 100644 --- a/packages/auth/docs/guard.en-US.md +++ b/packages/auth/docs/guard.en-US.md @@ -15,11 +15,11 @@ When a route does not initiate a request, it means that the Token validity canno { path: 'home', component: MockComponent, - canActivate: [JWTGuard], + canActivate: [authJWTCanActivate], }, { path: 'my', - canActivateChild: [JWTGuard], + canActivateChild: [authJWTCanActivateChild], children: [ { path: 'profile', component: MockComponent } ], @@ -35,5 +35,5 @@ When a route does not initiate a request, it means that the Token validity canno Similarly, the different authentication styles are: -- `SimpleGuard` based on Simple Web Token authentication style -- `JWTGuard` based on Json Web Token authentication style +- `authSimpleCanActivate`, `authSimpleCanActivateChild`, `authSimpleCanMatch` based on Simple Web Token authentication style +- `authJWTCanActivate`, `authJWTCanActivateChild`, `authJWTCanMatch` based on Json Web Token authentication style diff --git a/packages/auth/docs/guard.zh-CN.md b/packages/auth/docs/guard.zh-CN.md index 5fada8c26..aff9b28ff 100644 --- a/packages/auth/docs/guard.zh-CN.md +++ b/packages/auth/docs/guard.zh-CN.md @@ -13,11 +13,11 @@ type: Documents { path: 'home', component: MockComponent, - canActivate: [JWTGuard], + canActivate: [authJWTCanActivate], }, { path: 'my', - canActivateChild: [JWTGuard], + canActivateChild: [authJWTCanActivateChild], children: [ { path: 'profile', component: MockComponent } ], @@ -33,5 +33,5 @@ type: Documents 同样,针对不同认证风格分别为: -- `SimpleGuard` 基于 Simple Web Token 认证风格 -- `JWTGuard` 基于 Json Web Token 认证风格 +- `authSimpleCanActivate`, `authSimpleCanActivateChild`, `authSimpleCanMatch` 基于 Simple Web Token 认证风格 +- `authJWTCanActivate`, `authJWTCanActivateChild`, `authJWTCanMatch` 基于 Json Web Token 认证风格 diff --git a/packages/auth/src/token/jwt/jwt.guard.spec.ts b/packages/auth/src/token/jwt/jwt.guard.spec.ts index 314a5e660..2b2678151 100644 --- a/packages/auth/src/token/jwt/jwt.guard.spec.ts +++ b/packages/auth/src/token/jwt/jwt.guard.spec.ts @@ -3,7 +3,7 @@ import { fakeAsync, TestBed } from '@angular/core/testing'; import { Router } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; -import { JWTGuard } from './jwt.guard'; +import { authJWTCanActivate, authJWTCanActivateChild, authJWTCanMatch } from './jwt.guard'; import { DelonAuthModule } from '../../auth.module'; import { DA_SERVICE_TOKEN, ITokenService } from '../interface'; @@ -19,16 +19,16 @@ describe('auth: JWTGuard', () => { { path: 'home', component: MockComponent, - canActivate: [JWTGuard] + canActivate: [authJWTCanActivate] }, { path: 'my', - canActivateChild: [JWTGuard], + canActivateChild: [authJWTCanActivateChild], children: [{ path: 'profile', component: MockComponent }] }, { path: 'lazy', - canMatch: [JWTGuard], + canMatch: [authJWTCanMatch], loadChildren: () => AModule }, { diff --git a/packages/auth/src/token/jwt/jwt.guard.ts b/packages/auth/src/token/jwt/jwt.guard.ts index 1b3de4665..cc48c4bea 100644 --- a/packages/auth/src/token/jwt/jwt.guard.ts +++ b/packages/auth/src/token/jwt/jwt.guard.ts @@ -1,70 +1,62 @@ -import { Inject, Injectable, Injector } from '@angular/core'; -import { - ActivatedRouteSnapshot, - CanActivate, - CanActivateChild, - CanMatch, - Route, - RouterStateSnapshot -} from '@angular/router'; - -import { AlainAuthConfig } from '@delon/util/config'; +import { Inject, Injectable, Injector, inject } from '@angular/core'; +import { CanActivateChildFn, CanActivateFn, CanMatchFn } from '@angular/router'; import { JWTTokenModel } from './jwt.model'; import { CheckJwt, ToLogin } from '../helper'; import { DA_SERVICE_TOKEN, ITokenService } from '../interface'; -/** - * JWT 路由守卫, [ACL Document](https://ng-alain.com/auth/guard). - * - * ```ts - * data: { - * path: 'home', - * canActivate: [ JWTGuard ] - * }, - * { - * path: 'my', - * canActivateChild: [JWTGuard], - * children: [ - * { path: 'profile', component: MockComponent } - * ], - * }, - * ``` - */ @Injectable({ providedIn: 'root' }) -export class JWTGuard implements CanActivate, CanActivateChild, CanMatch { - private url: string | undefined; - - private get cog(): AlainAuthConfig { - return this.srv.options; - } - +export class AuthJWTGuardService { constructor( @Inject(DA_SERVICE_TOKEN) private srv: ITokenService, private injector: Injector ) {} - private process(): boolean { - const res = CheckJwt(this.srv.get(JWTTokenModel), this.cog.token_exp_offset!); + process(url?: string): boolean { + const cog = this.srv.options; + const res = CheckJwt(this.srv.get(JWTTokenModel), cog.token_exp_offset!); if (!res) { - ToLogin(this.cog, this.injector, this.url); + ToLogin(cog, this.injector, url); } return res; } - - // lazy loading - canMatch(route: Route): boolean { - this.url = route.path; - return this.process(); - } - // all children route - canActivateChild(_childRoute: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { - this.url = state.url; - return this.process(); - } - // route - canActivate(_route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { - this.url = state.url; - return this.process(); - } } + +/** + * JWT 路由守卫, [ACL Document](https://ng-alain.com/auth/guard). + * + * ```ts + * data: { + * path: 'home', + * canActivate: [ authJWTCanActivate ], + * data: { guard: 'user1' } + * } + * ``` + */ +export const authJWTCanActivate: CanActivateFn = (_, state) => inject(AuthJWTGuardService).process(state.url); + +/** + * JWT 路由守卫, [ACL Document](https://ng-alain.com/auth/guard). + * + * ```ts + * data: { + * path: 'home', + * canActivateChild: [ authJWTCanActivateChild ], + * data: { guard: 'user1' } + * } + * ``` + */ +export const authJWTCanActivateChild: CanActivateChildFn = (_, state) => inject(AuthJWTGuardService).process(state.url); + +/** + * JWT 路由守卫, [ACL Document](https://ng-alain.com/auth/guard). + * + * ```ts + * data: { + * path: 'home', + * canMatch: [ authJWTCanMatch ], + * data: { guard: 'user1' } + * } + * ``` + */ +export const authJWTCanMatch: CanMatchFn = route => inject(AuthJWTGuardService).process(route.path); diff --git a/packages/auth/src/token/simple/simple.guard.spec.ts b/packages/auth/src/token/simple/simple.guard.spec.ts index ffa5e54f2..ffd63da1f 100644 --- a/packages/auth/src/token/simple/simple.guard.spec.ts +++ b/packages/auth/src/token/simple/simple.guard.spec.ts @@ -3,7 +3,7 @@ import { fakeAsync, TestBed } from '@angular/core/testing'; import { Router } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; -import { SimpleGuard } from './simple.guard'; +import { authSimpleCanActivate, authSimpleCanActivateChild, authSimpleCanMatch } from './simple.guard'; import { DelonAuthModule } from '../../auth.module'; import { DA_SERVICE_TOKEN, ITokenService } from '../interface'; @@ -19,16 +19,16 @@ describe('auth: SimpleGuard', () => { { path: 'home', component: MockComponent, - canActivate: [SimpleGuard] + canActivate: [authSimpleCanActivate] }, { path: 'my', - canActivateChild: [SimpleGuard], + canActivateChild: [authSimpleCanActivateChild], children: [{ path: 'profile', component: MockComponent }] }, { path: 'lazy', - canMatch: [SimpleGuard], + canMatch: [authSimpleCanMatch], loadChildren: () => AModule }, { diff --git a/packages/auth/src/token/simple/simple.guard.ts b/packages/auth/src/token/simple/simple.guard.ts index e2c54863c..8059531fc 100644 --- a/packages/auth/src/token/simple/simple.guard.ts +++ b/packages/auth/src/token/simple/simple.guard.ts @@ -1,70 +1,62 @@ -import { Inject, Injectable, Injector } from '@angular/core'; -import { - ActivatedRouteSnapshot, - CanActivate, - CanActivateChild, - CanMatch, - Route, - RouterStateSnapshot -} from '@angular/router'; - -import { AlainAuthConfig } from '@delon/util/config'; +import { Inject, Injectable, Injector, inject } from '@angular/core'; +import { CanActivateChildFn, CanActivateFn, CanMatchFn } from '@angular/router'; import { SimpleTokenModel } from './simple.model'; import { CheckSimple, ToLogin } from '../helper'; import { DA_SERVICE_TOKEN, ITokenService } from '../interface'; -/** - * Simple 路由守卫, [ACL Document](https://ng-alain.com/auth/guard). - * - * ```ts - * data: { - * path: 'home', - * canActivate: [ SimpleGuard ] - * }, - * { - * path: 'my', - * canActivateChild: [SimpleGuard], - * children: [ - * { path: 'profile', component: MockComponent } - * ], - * }, - * ``` - */ @Injectable({ providedIn: 'root' }) -export class SimpleGuard implements CanActivate, CanActivateChild, CanMatch { - private url?: string; - - private get cog(): AlainAuthConfig { - return this.srv.options; - } - +export class AuthSimpleGuardService { constructor( @Inject(DA_SERVICE_TOKEN) private srv: ITokenService, private injector: Injector ) {} - private process(): boolean { + process(url?: string): boolean { const res = CheckSimple(this.srv.get() as SimpleTokenModel); if (!res) { - ToLogin(this.cog, this.injector, this.url); + ToLogin(this.srv.options, this.injector, url); } return res; } - - // lazy loading - canMatch(route: Route): boolean { - this.url = route.path; - return this.process(); - } - // all children route - canActivateChild(_childRoute: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { - this.url = state.url; - return this.process(); - } - // route - canActivate(_route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { - this.url = state.url; - return this.process(); - } } + +/** + * Simple 路由守卫, [ACL Document](https://ng-alain.com/auth/guard). + * + * ```ts + * data: { + * path: 'home', + * canActivate: [ authSimpleCanActivate ], + * data: { guard: 'user1' } + * } + * ``` + */ +export const authSimpleCanActivate: CanActivateFn = (_, state) => inject(AuthSimpleGuardService).process(state.url); + +/** + * Simple 路由守卫, [ACL Document](https://ng-alain.com/auth/guard). + * + * ```ts + * data: { + * path: 'home', + * canActivateChild: [ authSimpleCanActivateChild ], + * data: { guard: 'user1' } + * } + * ``` + */ +export const authSimpleCanActivateChild: CanActivateChildFn = (_, state) => + inject(AuthSimpleGuardService).process(state.url); + +/** + * Simple 路由守卫, [ACL Document](https://ng-alain.com/auth/guard). + * + * ```ts + * data: { + * path: 'home', + * canMatch: [ authSimpleCanMatch ], + * data: { guard: 'user1' } + * } + * ``` + */ +export const authSimpleCanMatch: CanMatchFn = route => inject(AuthSimpleGuardService).process(route.path); diff --git a/packages/theme/src/services/i18n/i18n-url.guard.ts b/packages/theme/src/services/i18n/i18n-url.guard.ts index d605f2de4..bf1113ee0 100644 --- a/packages/theme/src/services/i18n/i18n-url.guard.ts +++ b/packages/theme/src/services/i18n/i18n-url.guard.ts @@ -1,5 +1,5 @@ -import { Inject, Injectable, Optional } from '@angular/core'; -import { ActivatedRouteSnapshot, CanActivate, CanActivateChild, RouterStateSnapshot, UrlTree } from '@angular/router'; +import { Inject, Injectable, Optional, inject } from '@angular/core'; +import { ActivatedRouteSnapshot, CanActivateChildFn, CanActivateFn } from '@angular/router'; import { Observable, of } from 'rxjs'; import { AlainConfigService } from '@delon/util/config'; @@ -7,7 +7,7 @@ import { AlainConfigService } from '@delon/util/config'; import { AlainI18NService, ALAIN_I18N_TOKEN } from './i18n'; @Injectable({ providedIn: 'root' }) -export class AlainI18NGuard implements CanActivate, CanActivateChild { +export class AlainI18NGuardService { constructor( @Optional() @Inject(ALAIN_I18N_TOKEN) @@ -15,25 +15,35 @@ export class AlainI18NGuard implements CanActivate, CanActivateChild { private cogSrv: AlainConfigService ) {} - private resolve(route: ActivatedRouteSnapshot): Observable { + process(route: ActivatedRouteSnapshot): Observable { const lang = route.params && route.params[this.cogSrv.get('themeI18n')?.paramNameOfUrlGuard ?? 'i18n']; if (lang != null) { this.i18nSrv.use(lang); } return of(true); } +} - canActivateChild( - childRoute: ActivatedRouteSnapshot, - _: RouterStateSnapshot - ): boolean | UrlTree | Observable | Promise { - return this.resolve(childRoute); - } +/** + * Simple 路由守卫, [ACL Document](https://ng-alain.com/auth/guard). + * + * ```ts + * data: { + * path: 'home', + * canActivate: [ alainI18nCanActivate ] + * } + * ``` + */ +export const alainI18nCanActivate: CanActivateFn = childRoute => inject(AlainI18NGuardService).process(childRoute); - canActivate( - route: ActivatedRouteSnapshot, - _: RouterStateSnapshot - ): boolean | UrlTree | Observable | Promise { - return this.resolve(route); - } -} +/** + * Simple 路由守卫, [ACL Document](https://ng-alain.com/auth/guard). + * + * ```ts + * data: { + * path: 'home', + * canActivateChild: [ alainI18nCanActivateChild ] + * } + * ``` + */ +export const alainI18nCanActivateChild: CanActivateChildFn = route => inject(AlainI18NGuardService).process(route); diff --git a/packages/theme/src/services/i18n/i18n.spec.ts b/packages/theme/src/services/i18n/i18n.spec.ts index 9f86ae4c0..e11ffa5db 100644 --- a/packages/theme/src/services/i18n/i18n.spec.ts +++ b/packages/theme/src/services/i18n/i18n.spec.ts @@ -7,7 +7,7 @@ import { RouterTestingModule } from '@angular/router/testing'; import { AlainConfig, ALAIN_CONFIG } from '@delon/util/config'; import { AlainI18NService, ALAIN_I18N_TOKEN } from './i18n'; -import { AlainI18NGuard } from './i18n-url.guard'; +import { alainI18nCanActivate, alainI18nCanActivateChild } from './i18n-url.guard'; import { AlainThemeModule } from '../../theme.module'; describe('theme: i18n', () => { @@ -93,8 +93,8 @@ describe('theme: i18n', () => { { path: ':i18n', component: TestComponent, - canActivate: [AlainI18NGuard], - canActivateChild: [AlainI18NGuard] + canActivate: [alainI18nCanActivate], + canActivateChild: [alainI18nCanActivateChild] } ]) ], @@ -114,7 +114,7 @@ describe('theme: i18n', () => { imports: [ AlainThemeModule.forRoot(), RouterTestingModule.withRoutes([ - { path: ':invalid', component: TestComponent, canActivate: [AlainI18NGuard] } + { path: ':invalid', component: TestComponent, canActivate: [alainI18nCanActivate] } ]) ], declarations: [TestComponent] @@ -132,7 +132,9 @@ describe('theme: i18n', () => { TestBed.configureTestingModule({ imports: [ AlainThemeModule.forRoot(), - RouterTestingModule.withRoutes([{ path: ':lang', component: TestComponent, canActivate: [AlainI18NGuard] }]) + RouterTestingModule.withRoutes([ + { path: ':lang', component: TestComponent, canActivate: [alainI18nCanActivate] } + ]) ], declarations: [TestComponent], providers: [{ provide: ALAIN_CONFIG, useValue: { themeI18n: { paramNameOfUrlGuard: 'lang' } } as AlainConfig }] diff --git a/schematics/application/files/src/app/routes/routes-routing.module.ts b/schematics/application/files/src/app/routes/routes-routing.module.ts index 4b769ef1f..c3abd4ab6 100644 --- a/schematics/application/files/src/app/routes/routes-routing.module.ts +++ b/schematics/application/files/src/app/routes/routes-routing.module.ts @@ -1,7 +1,7 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { startPageGuard } from '@core'; -import { SimpleGuard } from '@delon/auth'; +import { authSimpleCanActivate } from '@delon/auth'; import { environment } from '@env/environment'; // layout import { LayoutBasicComponent } from '../layout/basic/basic.component'; @@ -20,7 +20,7 @@ const routes: Routes = [ { path: '', component: LayoutBasicComponent, - canActivate: [startPageGuard, SimpleGuard], + canActivate: [startPageGuard, authSimpleCanActivate], children: [ { path: '', redirectTo: 'dashboard', pathMatch: 'full' }, { path: 'dashboard', component: DashboardComponent, data: { title: '仪表盘'<% if (!i18n) { %>, titleI18n: 'dashboard'<% } %> } }, diff --git a/src/app/routes/routes.module.ts b/src/app/routes/routes.module.ts index 4e805da6b..f2ff41289 100644 --- a/src/app/routes/routes.module.ts +++ b/src/app/routes/routes.module.ts @@ -1,7 +1,7 @@ import { NgModule } from '@angular/core'; import { Route, RouterModule } from '@angular/router'; -import { AlainI18NGuard } from '@delon/theme'; +import { alainI18nCanActivate } from '@delon/theme'; import { NotFoundComponent } from './404/404.component'; import { HomeComponent } from './home/home.component'; @@ -14,7 +14,7 @@ const routes: Route[] = [ { path: '', component: LayoutComponent, - canActivateChild: [AlainI18NGuard], + canActivateChild: [alainI18nCanActivate], children: [ { path: '', redirectTo: 'en', pathMatch: 'full' }, { path: ':lang', component: HomeComponent, data: { titleI18n: 'slogan' } },