Files
gongxue-base/apps/server/src/authorization/authorization.service.ts

96 lines
3.2 KiB
TypeScript

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);
}
}