@@ -9,8 +9,8 @@ import { JwtAuthGuard } from './jwt-auth.guard';
|
||||
|
||||
@Injectable()
|
||||
export class HqAuthGuard extends JwtAuthGuard implements CanActivate {
|
||||
canActivate(context: ExecutionContext): boolean {
|
||||
const ok = super.canActivate(context);
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const ok = await super.canActivate(context);
|
||||
if (!ok) return false;
|
||||
|
||||
const req = context.switchToHttp().getRequest();
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import {
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
ForbiddenException,
|
||||
Injectable,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { CLIENT_APP_ACTOR_MAP, ClientApp } from '@dukang/shared-types';
|
||||
import { PrismaService } from '../prisma/prisma.module';
|
||||
|
||||
export interface AuthUser {
|
||||
actorType: string;
|
||||
@@ -17,11 +19,20 @@ export interface AuthUser {
|
||||
storeId?: bigint;
|
||||
}
|
||||
|
||||
/**
|
||||
* v3.5.1 #9:门店/合伙人账号停用或解绑后,运行时接口强制拦截。
|
||||
* 仅对 STORE / PARTNER actor 做 DB 状态校验;USER / HQ 跳过(不影响 C 端/小程序/总部)。
|
||||
* 不通过时抛 ForbiddenException({ reason: 'ACCOUNT_DISABLED' }),由 HttpExceptionFilter 透传 reason,
|
||||
* 前端据此 clearAuth() 并跳登录页。
|
||||
*/
|
||||
@Injectable()
|
||||
export class JwtAuthGuard implements CanActivate {
|
||||
constructor(protected readonly jwtService: JwtService) {}
|
||||
constructor(
|
||||
protected readonly jwtService: JwtService,
|
||||
protected readonly prisma: PrismaService,
|
||||
) {}
|
||||
|
||||
canActivate(context: ExecutionContext): boolean {
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const req = context.switchToHttp().getRequest();
|
||||
const auth = req.headers.authorization as string | undefined;
|
||||
if (!auth?.startsWith('Bearer ')) {
|
||||
@@ -47,10 +58,81 @@ export class JwtAuthGuard implements CanActivate {
|
||||
? { storeId: BigInt(payload.storeId) }
|
||||
: {}),
|
||||
} satisfies AuthUser;
|
||||
return true;
|
||||
} catch (err) {
|
||||
if (err instanceof UnauthorizedException) throw err;
|
||||
throw new UnauthorizedException('Invalid token');
|
||||
}
|
||||
|
||||
// 账号启停 / 绑定态校验(仅门店与合伙人)
|
||||
await this.assertAccountActive(req.user);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async assertAccountActive(user: AuthUser): Promise<void> {
|
||||
if (user.actorType === 'STORE') {
|
||||
const acc = await this.prisma.storeAccount.findUnique({
|
||||
where: { id: user.actorId },
|
||||
select: { status: true, isTest: true },
|
||||
});
|
||||
if (!acc) {
|
||||
throw new ForbiddenException({
|
||||
reason: 'ACCOUNT_DISABLED',
|
||||
message: '门店账号不存在或已停用,请重新登录',
|
||||
});
|
||||
}
|
||||
// 测试门店账号跳过强校验,避免测试环境自锁
|
||||
if (acc.isTest) return;
|
||||
if (acc.status !== 'ACTIVE') {
|
||||
throw new ForbiddenException({
|
||||
reason: 'ACCOUNT_DISABLED',
|
||||
message: '门店账号已停用,请重新登录',
|
||||
});
|
||||
}
|
||||
if (user.storeId != null) {
|
||||
const store = await this.prisma.store.findUnique({
|
||||
where: { id: user.storeId },
|
||||
select: { status: true, isTest: true },
|
||||
});
|
||||
if (!store) {
|
||||
throw new ForbiddenException({
|
||||
reason: 'ACCOUNT_DISABLED',
|
||||
message: '门店不存在或已关闭,请重新登录',
|
||||
});
|
||||
}
|
||||
if (!store.isTest && store.status !== 'OPEN') {
|
||||
throw new ForbiddenException({
|
||||
reason: 'ACCOUNT_DISABLED',
|
||||
message: '门店已停用或关闭,请重新登录',
|
||||
});
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (user.actorType === 'PARTNER') {
|
||||
const acc = await this.prisma.partnerAccount.findUnique({
|
||||
where: { id: user.actorId },
|
||||
select: { status: true, bindingStatus: true, isTest: true },
|
||||
});
|
||||
if (!acc) {
|
||||
throw new ForbiddenException({
|
||||
reason: 'ACCOUNT_DISABLED',
|
||||
message: '合伙人账号不存在或已停用,请重新登录',
|
||||
});
|
||||
}
|
||||
// 测试合伙人账号跳过强校验,避免测试环境自锁
|
||||
if (acc.isTest) return;
|
||||
if (acc.status !== 'ACTIVE' || acc.bindingStatus !== 'ACTIVE') {
|
||||
throw new ForbiddenException({
|
||||
reason: 'ACCOUNT_DISABLED',
|
||||
message: '合伙人账号已停用或解绑,请重新登录',
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// USER / HQ 不在此校验(按需求仅门店 + 合伙人)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ export class PartnerPrimaryGuard implements CanActivate {
|
||||
) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
this.jwtAuthGuard.canActivate(context);
|
||||
await this.jwtAuthGuard.canActivate(context);
|
||||
const req = context.switchToHttp().getRequest();
|
||||
const user = req.user as AuthUser;
|
||||
if (user.actorType !== 'PARTNER') {
|
||||
|
||||
Reference in New Issue
Block a user