feat(auth): 添加认证模块和图片批注功能(项目初始化)

- 实现用户登录、登出、密码修改等认证功能
- 添加会话管理和权限控制中间件
- 创建图片批注组件和相关API路由
- 实现批注的增删改查功能
- 添加Docker和Git忽略配置文件
- 创建系统架构文档和开发约定说明
- 集成认证模块到前端应用路由中
This commit is contained in:
yuzhe
2026-07-21 15:28:55 +08:00
commit b0c498fbb6
81 changed files with 10826 additions and 0 deletions

43
api/routes/auth.ts Normal file
View File

@@ -0,0 +1,43 @@
import { Router, type Request, type Response } from 'express';
import { database } from '../database.js';
import { clearSession, createSession, hashPassword, sessionCookie, type AuthRequest, verifyPassword } from '../auth.js';
const router = Router();
router.post('/login', async (req: Request, res: Response) => {
const username = String(req.body?.username ?? '').trim();
const password = String(req.body?.password ?? '');
const row = await database.one<{ id: number; password_hash: string }>('SELECT id, password_hash FROM users WHERE username = ? AND status = ?', [username, 'active']);
if (!row || !verifyPassword(password, row.password_hash)) { res.status(401).json({ error: '账号或密码错误' }); return; }
await database.execute('UPDATE users SET last_login_at = ? WHERE id = ?', [new Date().toISOString(), row.id]);
const token = await createSession(row.id);
res.setHeader('Set-Cookie', sessionCookie(token));
res.json({ success: true });
});
router.post('/logout', async (req: Request, res: Response) => {
const token = req.headers.cookie?.match(/(?:^|; )proofing_session=([^;]+)/)?.[1];
await clearSession(token);
res.setHeader('Set-Cookie', 'proofing_session=; HttpOnly; SameSite=Lax; Path=/; Max-Age=0');
res.status(204).end();
});
router.get('/me', (req: AuthRequest, res: Response) => {
if (!req.authUser) { res.status(401).json({ error: '未登录' }); return; }
res.json(req.authUser);
});
router.post('/change-password', async (req: AuthRequest, res: Response) => {
if (!req.authUser) { res.status(401).json({ error: '未登录' }); return; }
const currentPassword = String(req.body?.currentPassword ?? '');
const newPassword = String(req.body?.newPassword ?? '');
if (newPassword.length < 8 || !/[A-Za-z]/.test(newPassword) || !/\d/.test(newPassword)) {
res.status(400).json({ error: '新密码至少 8 位,并同时包含字母和数字' }); return;
}
const row = await database.one<{ password_hash: string }>('SELECT password_hash FROM users WHERE id = ?', [req.authUser.id]);
if (!row || !verifyPassword(currentPassword, row.password_hash)) { res.status(400).json({ error: '当前密码错误' }); return; }
await database.execute('UPDATE users SET password_hash = ?, must_change_password = ? WHERE id = ?', [hashPassword(newPassword), false, req.authUser.id]);
res.json({ success: true });
});
export default router;