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