duoqi-api/src/middleware/admin-auth.ts
Wang Zhuoxuan 3991a02a8c feat: 添加管理员用户名密码登录功能
新增 /v1/admin/auth/login 接口,支持用户名密码登录获取 JWT Token。
- 添加 admin_users 表存储管理员账号和哈希密码
- 使用 bcryptjs 进行密码哈希(cost=10)
- JWT Token 认证优先,保留 ADMIN_TOKEN 作为向后兼容
- 记录登录审计日志到 admin_audit_log
- 种子数据创建默认管理员(username: admin, password: admin123)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 15:25:31 +08:00

60 lines
1.7 KiB
TypeScript

import { FastifyInstance } from 'fastify';
import fp from 'fastify-plugin';
import { UnauthorizedError, ForbiddenError } from '../utils/errors.js';
import { config } from '../utils/config.js';
import type { JwtPayload } from '../types/auth.js';
// Extend @fastify/jwt's type system for admin JWT
declare module '@fastify/jwt' {
interface FastifyJWT {
payload: JwtPayload;
}
}
async function adminAuthMiddleware(app: FastifyInstance): Promise<void> {
app.addHook('onRequest', async (request) => {
if (!request.url.startsWith('/v1/admin')) {
return;
}
// Skip public admin endpoints
const publicPaths = ['/v1/admin/auth', '/v1/admin/auth/login'];
if (publicPaths.some((p) => request.url === p)) {
return;
}
const authHeader = request.headers.authorization;
if (!authHeader?.startsWith('Bearer ')) {
throw new UnauthorizedError('Missing admin token');
}
const token = authHeader.slice(7);
// Try JWT verification first (new way)
try {
const decoded = app.jwt.verify<JwtPayload>(token);
if (decoded.authType === 'admin') {
// Successfully verified admin JWT - request.jwtVerify() will attach the decoded payload
return;
}
} catch {
// JWT verification failed, try fallback (backward compatibility)
}
// Fallback: ADMIN_TOKEN (legacy way)
if (token === config.ADMIN_TOKEN) {
// Manually attach a fake decoded payload for legacy token
request.jwtVerify = async () => ({
userId: 'legacy-admin',
authType: 'admin',
role: 'super_admin',
});
return;
}
throw new ForbiddenError('Invalid admin token');
});
}
export default fp(adminAuthMiddleware);