Files
thebet365/apps/api/src/domains/identity/guards.ts
Mars 844727c82e feat: 前台匿名浏览、登录引导、客服入口与返水增强
前台:
- 未登录可浏览首页/赛事/赔率,下注等操作弹出登录引导(去登录/继续浏览)
- 顶部新增客服入口与 iframe 弹窗
- 登录页支持暂不登录返回浏览

API:
- 首页/赛事/冠军盘接口改为公开访问,支持 X-Locale 头
- JWT 守卫支持可选认证

返水:
- 注单新增 is_cashbacked 字段,发放时自动标记
- 预览展示玩家余额,明确平台直发不从代理扣款
- 后台注单列表与玩家历史展示回水状态

其他:
- 串关禁止同场重复选号(SAME_MATCH)
- 补充结算资金流分析文档

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 09:36:44 +08:00

102 lines
3.1 KiB
TypeScript

import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { AuthGuard } from '@nestjs/passport';
import { IS_PUBLIC_KEY, PERMISSIONS_KEY } from '../../shared/common/decorators';
import { appForbidden, appUnauthorized } from '../../shared/common/app-error';
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
constructor(private reflector: Reflector) {
super();
}
canActivate(context: ExecutionContext) {
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
context.getHandler(),
context.getClass(),
]);
if (isPublic) return true;
return super.canActivate(context);
}
handleRequest<TUser = unknown>(err: Error | null, user: TUser): TUser {
if (err || !user) throw err || appUnauthorized('INVALID_CREDENTIALS');
return user;
}
}
@Injectable()
export class PermissionsGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const required = this.reflector.getAllAndOverride<string[]>(PERMISSIONS_KEY, [
context.getHandler(),
context.getClass(),
]);
const { user } = context.switchToHttp().getRequest();
if (!user || user.userType !== 'ADMIN') {
throw appForbidden('ADMIN_ACCESS_REQUIRED');
}
if (user.role === 'SUPER_ADMIN') return true;
if (!required?.length) {
throw appForbidden('INSUFFICIENT_PERMISSIONS');
}
const userPerms: string[] = user.permissions ?? [];
const hasAccess = required.some((p) => userPerms.includes(p));
if (!hasAccess) throw appForbidden('INSUFFICIENT_PERMISSIONS');
return true;
}
}
export function UserTypeGuard(...types: string[]) {
@Injectable()
class MixedUserTypeGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const { user } = context.switchToHttp().getRequest();
if (!types.includes(user?.userType)) {
throw appForbidden('ACCESS_DENIED_PORTAL');
}
return true;
}
}
return MixedUserTypeGuard;
}
@Injectable()
export class PlayerGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
context.getHandler(),
context.getClass(),
]);
if (isPublic) return true;
const { user } = context.switchToHttp().getRequest();
if (user?.userType !== 'PLAYER') throw appForbidden('PLAYER_ACCESS_ONLY');
return true;
}
}
@Injectable()
export class AdminGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const { user } = context.switchToHttp().getRequest();
if (user?.userType !== 'ADMIN') throw appForbidden('ADMIN_ACCESS_ONLY');
return true;
}
}
@Injectable()
export class AgentGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const { user } = context.switchToHttp().getRequest();
if (user?.userType !== 'AGENT') throw appForbidden('AGENT_ACCESS_ONLY');
return true;
}
}