| 维度 | 评分 | 关键发现 |
|---|---|---|
| 1. 功能正确性 | 0 |
P0 用户不存在时 user 为 null,访问 .password 抛出 TypeError,返回 500 而非 401。P0 明文密码直接比较,暗示数据库中没有 hash 存储。 P2 无输入验证 — 空 email、空 password、非法 email 格式均未拦截。 |
| 2. 测试覆盖 | 0 |
P0 无任何测试文件。未覆盖:合法登录、错误密码、不存在用户、空输入、JWT payload 正确性。 P1 无集成测试验证 JWT 可被 API handler 正常解密。 |
| 3. 安全 | 0 |
P0 明文密码比较 — 不符合 OWASP A2:2021。必须使用 bcrypt.compare()。P1 无速率限制 — 可暴力破解。 P1 错误信息泄露 — 用户存在 vs 不存在会产生不同的时序/堆栈行为,可枚举已注册用户。 P2 JWT 无 expiresIn — 永不过期,凭证泄漏后无撤销窗口。
|
| 4. 性能 | 3 |
单次 findFirst 查询,无 N+1 风险。但未指定 select 字段 — 数据库返回整行,密码字段通过网络传输到应用层处理。建议用 select: { id: true, password: true } 只取必要字段。
|
| 5. 可读性 | 3 |
代码短,逻辑直观。但 req 参数无 TypeScript 类型 — 缺少 NextApiRequest 类型标注。函数名 login 可以更精确(如 loginHandler 或 handleLogin)。
|
| 6. 可维护性 | 2 |
JWT secret 硬编码或依赖隐式全局 — 建议注入配置。 鉴权逻辑直接耦合在路由 handler 中 — 应抽取 authService 层以便复用和测试。
|
| 7. 工程规范 | 1 |
无 TypeScript 类型注解。 无 JSDoc 或注释说明入参/出参/错误码。 未提供 .env.example 中 JWT secret 的环境变量声明。
|
| 8. 风险评估 | 0 |
P0 Blast radius: 全平台 — 明文密码一旦泄漏,所有用户密码暴露。 P1 回滚成本极高 — 上线后需强制所有用户重置密码。 无灰度策略 — 无 feature flag 封装。 |
db.user.findFirst 无匹配返回 null,直接访问 .password 导致 TypeError,API 返回 500 而非 401。export async function login(req) { const user = await db.user.findFirst({ where: { email: req.body.email } }); - if (user.password === req.body.password) return jwt.sign({ id: user.id }); + if (!user) throw new AppError(401, 'Invalid credentials'); + const valid = await bcrypt.compare(req.body.password, user.passwordHash); + if (!valid) throw new AppError(401, 'Invalid credentials'); + return jwt.sign({ id: user.id }, JWT_SECRET, { expiresIn: '7d' }); throw new Error('wrong password'); }
user.password === req.body.password 表明密码以明文存储。必须使用 bcrypt 或 argon2 进行 hash 比较。数据库字段应命名为 passwordHash 以明确语义。// 注册时 (非本次 diff,但需联动改): const passwordHash = await bcrypt.hash(password, 12); // 登录时: const valid = await bcrypt.compare(req.body.password, user.passwordHash); if (!valid) throw new AppError(401, 'Invalid credentials');
req 隐式为 any,失去 TypeScript 的类型保护。应标注为 Next.js 的 NextApiRequest。import type { NextApiRequest, NextApiResponse } from 'next'; export async function login( req: NextApiRequest, res: NextApiResponse<{ token: string } | { error: string }> ) { ... }
jwt.sign({id: user.id}) 未设置 expiresIn。泄漏后无自动失效机制。建议 7d access + 30d refresh token 双令牌模式。const accessToken = jwt.sign( { id: user.id }, process.env.JWT_SECRET!, { expiresIn: '7d' } ); const refreshToken = jwt.sign( { id: user.id }, process.env.JWT_REFRESH_SECRET!, { expiresIn: '30d' } );
/api/auth 端点启用 rate-limit(如 5 次/分钟/IP)。// 使用 next-rate-limit 或 express-rate-limit 封装: import { rateLimit } from '@/lib/rate-limit'; export const loginHandler = rateLimit({ windowMs: 60_000, max: 5, keyGenerator: (req) => req.ip ?? req.socket.remoteAddress ?? 'unknown', })(login);
req.body.email 和 req.body.password 未验证格式和非空。import { z } from 'zod'; const LoginSchema = z.object({ email: z.string().email().max(255), password: z.string().min(8).max(128), }); const body = LoginSchema.parse(req.body); // throws ZodError → 400
// 最小测试集 (vitest / jest): describe('POST /api/auth', () => { it('returns 200 + JWT for valid credentials'); it('returns 401 for wrong password'); it('returns 401 for non-existent user'); it('returns 400 for empty email'); it('returns 400 for invalid email format'); it('returns 400 for short password (<8 chars)'); it('returns 429 after 5 failed attempts'); });
// ✅ 正确: 无论用户是否存在,都返回相同的错误 if (!user || !(await bcrypt.compare(password, user.passwordHash))) { throw new AppError(401, 'Invalid credentials'); }
⛔ Block — 不可合入
感谢提交。这份登录逻辑存在几个严重的安全和功能问题,需要在重新审查前修复。 P0 — 阻塞项 1. 明文密码比较 (api/auth.ts:4):user.password === req.body.password表明密码以明文存储和比较。这是不可接受的。请使用bcrypt.compare()进行安全比较,并确保注册时使用bcrypt.hash()存储。若现有数据为明文密码,需设计迁移方案。 2. 用户不存在时崩溃 (api/auth.ts:3):db.user.findFirst无匹配返回null,下一行访问user.password会抛出TypeError,API 返回 500 而非 401。请先判空。 3. 零测试覆盖:需补充至少 6 个测试 case:合法登录、错误密码、不存在用户、空输入、非法 email、速率限制触发。建议使用vitest+ mockdb。 P1 — 高危项 4. JWT 无过期时间 (api/auth.ts:4):jwt.sign缺少expiresIn,建议 7d access token + 30d refresh token。 5. 无速率限制:请对/api/auth添加 rate-limit(建议 5 次/分钟/IP)。 6. 错误信息泄露:用户不存在和密码错误应返回统一的401 Invalid credentials,防止用户枚举攻击。 P2 — 建议项 7. TypeScript 类型:req参数应标注为NextApiRequest,响应应标注NextApiResponse。 8. 输入验证:建议用zod校验email格式和password最小长度。 9. 字段裁剪:findFirst建议加select: { id: true, passwordHash: true },避免不必要字段传输。 请修复后再提交 review。有任何问题随时讨论。