import { CanActivate, ExecutionContext, Injectable, UnauthorizedException, } from '@nestjs/common'; import jwt from 'jsonwebtoken'; import type { FamilyActorContext } from '../../common/family-actor-context.js'; import { MESSAGES } from '../../common/messages.js'; import { PrismaService } from '../../prisma.service.js'; /** * C 端家属小程序登录守卫。 */ @Injectable() export class FamilyAccessTokenGuard implements CanActivate { constructor(private readonly prisma: PrismaService) {} async canActivate(context: ExecutionContext): Promise { const request = context.switchToHttp().getRequest<{ headers: Record; familyActor?: FamilyActorContext; }>(); const authorization = request.headers.authorization; const headerValue = Array.isArray(authorization) ? authorization[0] : authorization; if (!headerValue || !headerValue.startsWith('Bearer ')) { throw new UnauthorizedException(MESSAGES.AUTH.MISSING_BEARER); } request.familyActor = await this.verifyAndExtractActor( headerValue.slice('Bearer '.length).trim(), ); return true; } /** * 校验家属 token 并回库确认账号仍存在。 */ private async verifyAndExtractActor(token: string): Promise { const secret = process.env.AUTH_TOKEN_SECRET; if (!secret) { throw new UnauthorizedException(MESSAGES.AUTH.TOKEN_SECRET_MISSING); } let payload: string | jwt.JwtPayload; try { payload = jwt.verify(token, secret, { algorithms: ['HS256'], issuer: 'tyt-api-nest-family', }); } catch { throw new UnauthorizedException(MESSAGES.AUTH.TOKEN_INVALID); } if ( typeof payload !== 'object' || payload.type !== 'FAMILY_MINIAPP' || typeof payload.id !== 'number' || !Number.isInteger(payload.id) ) { throw new UnauthorizedException(MESSAGES.AUTH.TOKEN_PAYLOAD_INVALID); } const account = await this.prisma.familyMiniAppAccount.findUnique({ where: { id: payload.id }, select: { id: true, phone: true, openId: true, serviceUid: true, }, }); if (!account) { throw new UnauthorizedException(MESSAGES.AUTH.FAMILY_ACCOUNT_NOT_FOUND); } return account; } }