chore: commit pending admin-panel, api auth and parity doc changes

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
s.esmaeili
2026-10-14 04:33:24 +03:30
co-authored by Claude Haiku 5.5
parent 55856cac89
commit cf014e1f6c
41 changed files with 3471 additions and 969 deletions
+36 -10
View File
@@ -9,14 +9,31 @@ import {
import { AuthGuard } from '@nestjs/passport';
import { Reflector } from '@nestjs/core';
import type { AuthenticatedAdmin } from './jwt.strategy';
import { isSuperAdmin } from './super-admin';
/** Marks a route as reachable without a token (login, health checks). */
export const IS_PUBLIC_KEY = 'isPublic';
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
/** Requires the caller to hold this rid (ancestors count — see expandWithParents). */
/**
* Requires the caller to hold this rid (ancestors count — see expandWithParents).
* With several rids, holding any one of them is enough.
*/
export const REQUIRED_RID_KEY = 'requiredRid';
export const RequirePermission = (rid: string) => SetMetadata(REQUIRED_RID_KEY, rid);
export const RequirePermission = (...rids: [string, ...string[]]) =>
SetMetadata(REQUIRED_RID_KEY, rids);
/**
* Restricts a route (or a whole controller) to the super admin — see
* super-admin.ts. `allowBranch` also admits an admin bound to a branch, because
* the branch panel serves its staff from the same retail endpoints.
*/
export const SUPER_ADMIN_ONLY_KEY = 'superAdminOnly';
export interface SuperAdminOnlyOptions {
allowBranch?: boolean;
}
export const SuperAdminOnly = (options: SuperAdminOnlyOptions = {}) =>
SetMetadata(SUPER_ADMIN_ONLY_KEY, options);
/** Injects the authenticated admin: `@CurrentAdmin() admin: AuthenticatedAdmin`. */
export const CurrentAdmin = createParamDecorator(
@@ -48,23 +65,32 @@ export class JwtAuthGuard extends AuthGuard('jwt') {
}
}
/** Enforces @RequirePermission(rid) against the expanded permission list. */
/** Enforces @SuperAdminOnly() and @RequirePermission(rid) against the expanded permission list. */
@Injectable()
export class PermissionsGuard implements CanActivate {
constructor(private readonly reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRid = this.reflector.getAllAndOverride<string>(REQUIRED_RID_KEY, [
context.getHandler(),
context.getClass(),
]);
if (!requiredRid) return true;
const targets = [context.getHandler(), context.getClass()];
const superAdminOnly = this.reflector.getAllAndOverride<SuperAdminOnlyOptions | undefined>(
SUPER_ADMIN_ONLY_KEY,
targets,
);
const requiredRids = this.reflector.getAllAndOverride<string[] | undefined>(REQUIRED_RID_KEY, targets);
if (!superAdminOnly && !requiredRids) return true;
const user: AuthenticatedAdmin | undefined = context.switchToHttp().getRequest().user;
if (!user) throw new ForbiddenException();
if (!user.permissions.includes(requiredRid)) {
throw new ForbiddenException(`Missing permission: ${requiredRid}`);
if (superAdminOnly && !isSuperAdmin(user.mobile)) {
if (!(superAdminOnly.allowBranch && user.branchId)) {
throw new ForbiddenException('این بخش فقط در دسترس مدیر ارشد سامانه است');
}
}
if (!requiredRids) return true;
if (!requiredRids.some((rid) => user.permissions.includes(rid))) {
throw new ForbiddenException(`Missing permission: ${requiredRids.join(' | ')}`);
}
return true;
}