b626db5d84
Unify product/store visibility on HQ whitelist, mark isTest snapshots, and fix SUPER_ADMIN access for the new module.
122 lines
3.7 KiB
TypeScript
122 lines
3.7 KiB
TypeScript
import {
|
|
CanActivate,
|
|
ExecutionContext,
|
|
ForbiddenException,
|
|
Injectable,
|
|
SetMetadata,
|
|
} from '@nestjs/common';
|
|
import { Reflector } from '@nestjs/core';
|
|
import {
|
|
HQ_PERMISSION_CATALOG,
|
|
HQ_ROLE_DEFAULT_PERMISSIONS,
|
|
expandHqPermissionKeys,
|
|
hasAnySystemSettingsPermission,
|
|
type HqPermissionKey,
|
|
} from '@dukang/shared-types';
|
|
import { PrismaService } from '../prisma/prisma.module';
|
|
import type { AuthUser } from './jwt-auth.guard';
|
|
|
|
export const HQ_PERMISSIONS_KEY = 'hq:permissions';
|
|
export const RequireHqPermissions = (...keys: string[]) =>
|
|
SetMetadata(HQ_PERMISSIONS_KEY, keys);
|
|
/** 任意一项系统设置分组权限即可访问系统设置接口 */
|
|
export const RequireAnySystemSettings = () =>
|
|
SetMetadata(HQ_PERMISSIONS_KEY, ['__any_system_settings__']);
|
|
|
|
@Injectable()
|
|
export class HqPermissionsResolver {
|
|
constructor(private readonly prisma: PrismaService) {}
|
|
|
|
private async loadActiveAccount(actorId: bigint) {
|
|
const account = await this.prisma.hqAccount.findUnique({
|
|
where: { id: actorId },
|
|
select: { adminRole: true, status: true },
|
|
});
|
|
if (!account || account.status !== 'ACTIVE') {
|
|
throw new ForbiddenException('HQ 账号不可用');
|
|
}
|
|
return account;
|
|
}
|
|
|
|
async resolveAccess(actorId: bigint): Promise<{
|
|
keys: HqPermissionKey[];
|
|
isSuperAdmin: boolean;
|
|
}> {
|
|
const account = await this.loadActiveAccount(actorId);
|
|
|
|
const userRows = await this.prisma.hqAccountPermission.findMany({
|
|
where: { hqAccountId: actorId },
|
|
select: { permissionKey: true },
|
|
});
|
|
const userKeys = userRows.map((r) => r.permissionKey);
|
|
|
|
if (account.adminRole === 'SUPER_ADMIN') {
|
|
// 超管拥有权限目录内全部项(含后续新增),另含危险操作与用户级附加项
|
|
return {
|
|
isSuperAdmin: true,
|
|
keys: expandHqPermissionKeys([
|
|
...HQ_PERMISSION_CATALOG.map((p) => p.key),
|
|
...userKeys,
|
|
]),
|
|
};
|
|
}
|
|
|
|
const roleRows = await this.prisma.hqRolePermission.findMany({
|
|
where: { adminRole: account.adminRole },
|
|
select: { permissionKey: true },
|
|
});
|
|
|
|
const roleKeys =
|
|
roleRows.length > 0
|
|
? roleRows.map((r) => r.permissionKey)
|
|
: [...(HQ_ROLE_DEFAULT_PERMISSIONS[account.adminRole] ?? [])];
|
|
|
|
return {
|
|
isSuperAdmin: false,
|
|
keys: expandHqPermissionKeys([...roleKeys, ...userKeys]),
|
|
};
|
|
}
|
|
|
|
async resolveEffectiveKeys(actorId: bigint): Promise<HqPermissionKey[]> {
|
|
const { keys } = await this.resolveAccess(actorId);
|
|
return keys;
|
|
}
|
|
}
|
|
|
|
@Injectable()
|
|
export class HqPermissionGuard implements CanActivate {
|
|
constructor(
|
|
private readonly resolver: HqPermissionsResolver,
|
|
private readonly reflector: Reflector,
|
|
) {}
|
|
|
|
async canActivate(context: ExecutionContext): Promise<boolean> {
|
|
const req = context.switchToHttp().getRequest();
|
|
const user = req.user as AuthUser | undefined;
|
|
if (!user || user.actorType !== 'HQ') {
|
|
throw new ForbiddenException('需要 HQ 权限');
|
|
}
|
|
const { keys, isSuperAdmin } = await this.resolver.resolveAccess(user.actorId);
|
|
req.hqPermissionKeys = keys;
|
|
|
|
const required =
|
|
this.reflector.getAllAndOverride<string[]>(HQ_PERMISSIONS_KEY, [
|
|
context.getHandler(),
|
|
context.getClass(),
|
|
]) ?? [];
|
|
|
|
if (!required.length) return true;
|
|
if (isSuperAdmin) return true;
|
|
if (required.includes('__any_system_settings__')) {
|
|
if (!hasAnySystemSettingsPermission(keys)) {
|
|
throw new ForbiddenException('无系统设置权限');
|
|
}
|
|
return true;
|
|
}
|
|
if (!required.some((k) => keys.includes(k as HqPermissionKey))) {
|
|
throw new ForbiddenException('权限不足');
|
|
}
|
|
return true;
|
|
}
|
|
}
|