# All files / middleware auth.ts

0% Statements 0/62
0% Branches 0/1
0% Functions 0/1
0% Lines 0/62

Press n or j to go to the next uncovered block, b, p or k for the previous block.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81

     import { Context, Next } from 'koa';
    import jwt from 'jsonwebtoken';
    import { config } from '../config';
    import { UnauthorizedError, AppError } from './errorHandler';
    import { JwtPayload } from '../types';
    import { safeParseInt } from '../utils/safe-parse';

    export async function authMiddleware(ctx: Context, next: Next): Promise<void> {
      // 开发阶段默认跳过认证（除非显式设置 AUTH_ENABLED=true）
      const authEnabled = process.env.AUTH_ENABLED === 'true';
      if (!authEnabled) {
        ctx.state.user = {
          userId: '1',
          phone: 'test',
          memberLevel: 99,
        };
        await next();
        return;
      }

      const authHeader = ctx.get('Authorization');

      if (!authHeader) {
        throw new UnauthorizedError('缺少 Authorization 头');
      }

      const parts = authHeader.split(' ');
      if (parts.length !== 2 || parts[0] !== 'Bearer') {
        throw new UnauthorizedError('Authorization 格式错误');
      }

      const token = parts[1];

      try {
        const secret = config.jwt.secret;
        const payload = jwt.verify(token, secret) as JwtPayload;
        ctx.state.user = payload;
        await next();
      } catch (err: unknown) {
        const error = err as Error;
        if (error.name === 'TokenExpiredError') {
          throw new UnauthorizedError('Token 已过期');
        }
        if (error.name === 'JsonWebTokenError') {
          throw new UnauthorizedError('Token 无效');
        }
        throw new AppError('认证失败', 401, 401);
      }
    }

    // 可选认证（允许未登录访问，自动使用测试用户）
    export async function optionalAuth(ctx: Context, next: Next): Promise<void> {
      const authHeader = ctx.get('Authorization');

      if (authHeader) {
        const parts = authHeader.split(' ');
        if (parts.length === 2 && parts[0] === 'Bearer') {
          try {
            const payload = jwt.verify(parts[1], config.jwt.secret) as JwtPayload;
            ctx.state.user = payload;
          } catch {
            // 忽略错误，使用测试用户
            ctx.state.user = {
              userId: '1',
              phone: 'test',
              memberLevel: 99, // 超级VIP
            };
          }
        }
      } else {
        // 没有认证头，使用测试用户（超级VIP）
        ctx.state.user = {
          userId: '1',
          phone: 'test',
          memberLevel: 99, // 超级VIP
        };
      }

      await next();
    }