Ґарди (охоронці)
Якщо ви хочете обмежити доступ до певних маршрутів, ви можете скористатись ґардами. Готовий приклад застосунку з ґардами ви можете проглянути у теці examples, або у RealWorld example.
Будь-який ґард є DI провайдером, що передається в інжектори на рівні запиту (якщо контролер є неодинаком), або на інших рівнях (якщо контрол ер є одинаком). Кожен ґард повинен бути класом, що впроваджує інтерфейс CanActivate
:
interface CanActivate {
canActivate(ctx: RequestContext, params?: any[]): boolean | number | Promise<boolean | number>;
}
Наприклад, це можна зробити так:
import { guard, CanActivate, RequestContext } from '@ditsmod/core';
import { AuthService } from './auth.service.js';
@guard({ isSingleton: false })
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService) {}
async canActivate(ctx: RequestContext, params?: any[]) {
return Boolean(await this.authService.updateAndGetSession());
}
}
Файли ґардів рекомендується називати із закінченням *.guard.ts
, а імена їхніх класів - із закінченням *Guard
.
Якщо canActivate()
повертає:
true
чиPromise<true>
, значить Ditsmod буде обробляти відповідний маршрут із цим ґардом;false
чиPromise<false>
, значить відповідь на запит міститиме 401 статус і обробки маршруту з боку контролера не буде;number
чиPromise<number>
Ditsmod інтерпретує як номер статусу (403, 401 і т.п.), який треба повернути у відповіді на HTTP-запит.
Передача ґардів до інжекторів
Ґарди можна передавати у метаданих модуля чи контролера:
import { featureModule } from '@ditsmod/core';
import { AuthGuard } from 'auth.guard';
@featureModule({
providersPerReq: [AuthGuard],
})
export class SomeModule {}
В даному разі ґард буде працювати на рівні запиту, для контролерів неодинаків.
Використання ґардів
Ґарди передаються до контролерів в масиві у третьому параметрі декоратора route
:
import { controller, Res, route } from '@ditsmod/core';
import { AuthGuard } from './auth.guard.js';
@controller()
export class SomeController {
@route('GET', 'some-url', [AuthGuard])
tellHello(res: Res) {
res.send('Hello, admin!');
}
}
Ґарди з параметрами
Ґард у методі canActivate()
має два параметри. Аргументи для першого параметра з типом даних RequestContext
підставляються автоматично, а для другого параметра аргументи можна передавати у декораторі route
у масиві, де на першому місці йде певний ґард.
Давайте розглянемо такий приклад:
import { controller, Res, route } from '@ditsmod/core';
import { PermissionsGuard } from './permissions.guard.js';
import { Permission } from './permission.js';
@controller()
export class SomeController {
@route('GET', 'some-url', [[PermissionsGuard, Permission.canActivateAdministration]])
tellHello(res: Res) {
res.send('Hello, admin!');
}
}
Як бачите, на місці третього параметра у route
передається масив в масиві, де на першому місці вказано PermissionsGuard
, а далі йдуть аргументи для нього. В такому разі PermissionsGuard
отримає ці аргументи у своєму методі canActivate()
:
import { injectable, CanActivate, Status, RequestContext } from '@ditsmod/core';
import { AuthService } from './auth.service.js';
import { Permission } from './permission.js';
@injectable()
export class PermissionsGuard implements CanActivate {
constructor(private authService: AuthService) {}
async canActivate(ctx: RequestContext, params?: Permission[]) {
if (await this.authService.hasPermissions(params)) {
return true;
} else {
return Status.FORBIDDEN;
}
}
}
Хелпери для ґардів з параметрами
Оскільки ґарди з параметрами повинні передаватись у вигляді масива в масиві, це ускладнює читабельність та погіршує безпечність типів. Для таких випадків краще створити хелпер за допомогою фабрики createHelperForGuardWithParams()
:
import { createHelperForGuardWithParams } from '@ditsmod/core';
import { Permission } from './types.js';
import { PermissionsGuard } from './permissions-guard.js';
export const requirePermissions = createHelperForGuardWithParams<Permission>(PermissionsGuard);
В даному прикладі у якості аргументу передається PermissionsGuard
, який приймає параметри з типом Permission
у своєму методі canActivate()
.
Тепер requirePermissions()
можна використовувати для створення роутів:
import { controller, Res, route } from '@ditsmod/core';
import { requirePermissions } from '../auth/guards-utils.js';
import { Permission } from '../auth/types.js';
@controller()
export class SomeController {
@route('GET', 'administration', [requirePermissions(Permission.canActivateAdministration)])
helloAdmin(res: Res) {
res.send('some secret');
}
}
Встановлення ґардів на імпортований модуль
Можна також централізовано підключати ґарди на рівні модуля:
import { featureModule } from '@ditsmod/core';
import { OtherModule } from '../other/other.module.js';
import { AuthModule } from '../auth/auth.module.js';
import { AuthGuard } from '../auth/auth.guard.js';
@featureModule({
imports: [
AuthModule,
{ path: 'some-path', module: OtherModule, guards: [AuthGuard] }
]
})
export class SomeModule {}
В такому разі AuthGuard
буде автоматично додаватись до кожного маршруту в OtherModule
.