Files
dukang/server/dukang-api/src/common/guards/optional-jwt-auth.guard.ts
T
2026-08-04 21:38:49 +08:00

51 lines
1.8 KiB
TypeScript

import {
CanActivate,
ExecutionContext,
Injectable,
} from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { CLIENT_APP_ACTOR_MAP, ClientApp } from '@dukang/shared-types';
import type { AuthUser } from './jwt-auth.guard';
/** C 端 H5 / 小程序同属 USER,目录白名单只认 actorId→手机号,允许端间 token 兼容 */
const USER_CLIENT_APPS = new Set<ClientApp>([ClientApp.USER_MINI, ClientApp.USER_H5]);
function clientAppCompatible(headerApp: ClientApp | undefined, payloadApp: unknown): boolean {
if (!headerApp || payloadApp == null) return false;
if (headerApp === payloadApp) return true;
return USER_CLIENT_APPS.has(headerApp) && USER_CLIENT_APPS.has(payloadApp as ClientApp);
}
@Injectable()
export class OptionalJwtAuthGuard implements CanActivate {
constructor(private readonly jwtService: JwtService) {}
canActivate(context: ExecutionContext): boolean {
const req = context.switchToHttp().getRequest();
const auth = req.headers.authorization as string | undefined;
if (!auth?.startsWith('Bearer ')) {
return true;
}
try {
const payload = this.jwtService.verify(auth.slice(7));
const clientApp = req.headers['x-client-app'] as ClientApp;
if (!clientAppCompatible(clientApp, payload.clientApp)) return true;
const expectedActor = CLIENT_APP_ACTOR_MAP[clientApp];
if (payload.actorType !== expectedActor) return true;
req.user = {
actorType: payload.actorType,
actorId: BigInt(payload.actorId),
clientApp,
sub: payload.sub,
phoneVerified: !!payload.phoneVerified,
...(payload.storeId != null && payload.storeId !== ''
? { storeId: BigInt(payload.storeId) }
: {}),
} satisfies AuthUser;
} catch {
/* ignore invalid token */
}
return true;
}
}