import { Injectable } from '@nestjs/common'; import { ForbiddenException } from '@nestjs/common'; import { CaslAbilityFactory } from './casl-ability.factory'; import { AppAbility, AppSubject, AuthorizationRequest } from './interfaces'; import { CaslAction, permissionCodeSubject } from './casl.constants'; /** * Generic authorization service usable both inside and outside of HTTP * request context. * * ### HTTP use * Inject `AuthorizationService` into controllers/services and call * `abilityForRequest(req)` to get the current user's ability. * * ### Non-HTTP use (Agent Tool, background job, etc.) * Build an ability via `abilityFactory.createForUser(user)` and pass it * to `assert` / `can` directly. */ @Injectable() export class AuthorizationService { constructor(private readonly abilityFactory: CaslAbilityFactory) {} /** * Build an {@link AppAbility} for the current HTTP request. * * @param req — Express/NestJS request with `req.user` populated by JWT. */ abilityForRequest(req: AuthorizationRequest): AppAbility { if (!req.user) { throw new ForbiddenException('缺少可信授权身份'); } return this.abilityFactory.createForUser(req.user); } /** * Assert that the given ability allows the action on the subject. * Throws `ForbiddenException` on denial. */ assertAbility(ability: AppAbility, action: CaslAction, subject: AppSubject): void { if (!ability.can(action, subject)) { throw new ForbiddenException( `权限不足:${action} ${typeof subject === 'string' ? subject : 'resource'}`, ); } } /** * Check whether the given ability allows the action on the subject. * Returns boolean — never throws. */ canAbility(ability: AppAbility, action: CaslAction, subject: AppSubject): boolean { return ability.can(action, subject); } /** * Check whether the given ability allows the exact permission code. * Uses `CaslAction.Access` with `permissionCodeSubject(code)` — the same * mechanism as {@link PermissionGuard}. * * Returns boolean — never throws. */ canPermission(ability: AppAbility, permissionCode: string): boolean { return ability.can(CaslAction.Access, permissionCodeSubject(permissionCode)); } /** * Assert that the given ability allows the exact permission code. * Throws `ForbiddenException` on denial. */ assertPermission(ability: AppAbility, permissionCode: string): void { if (!this.canPermission(ability, permissionCode)) { throw new ForbiddenException( `权限不足:缺少权限码 ${permissionCode}`, ); } } /** * Assert that the user (from request) can perform an action. * Convenience shorthand — builds ability from request. */ assert(req: AuthorizationRequest, action: CaslAction, subject: AppSubject): void { const ability = this.abilityForRequest(req); this.assertAbility(ability, action, subject); } /** * Check that the user (from request) can perform an action. * Convenience shorthand — builds ability from request. */ can(req: AuthorizationRequest, action: CaslAction, subject: AppSubject): boolean { const ability = this.abilityForRequest(req); return this.canAbility(ability, action, subject); } }