- 实现用户登录、登出、密码修改等认证功能 - 添加会话管理和权限控制中间件 - 创建图片批注组件和相关API路由 - 实现批注的增删改查功能 - 添加Docker和Git忽略配置文件 - 创建系统架构文档和开发约定说明 - 集成认证模块到前端应用路由中
49 lines
2.2 KiB
TypeScript
49 lines
2.2 KiB
TypeScript
import { randomBytes } from 'crypto';
|
|
import type { NextFunction, Request, Response } from 'express';
|
|
import { database } from './database.js';
|
|
import { sha256 } from './auth.js';
|
|
|
|
export type CustomerRequest = Request & {
|
|
customer?: { project_id: number; reviewer_name: string };
|
|
};
|
|
|
|
function readCookie(req: Request, name: string): string | undefined {
|
|
return req.headers.cookie?.split(';').map((item) => item.trim()).find((item) => item.startsWith(`${name}=`))?.slice(name.length + 1);
|
|
}
|
|
|
|
export async function createCustomerSession(projectId: number, reviewerName: string): Promise<string> {
|
|
const token = randomBytes(32).toString('base64url');
|
|
await database.execute('INSERT INTO customer_sessions (project_id, reviewer_name, token_hash, expires_at) VALUES (?, ?, ?, ?)', [projectId, reviewerName, sha256(token), new Date(Date.now() + 7 * 86400000).toISOString()]);
|
|
return token;
|
|
}
|
|
|
|
export function customerSessionCookie(token: string): string {
|
|
const secure = process.env.NODE_ENV === 'production' ? '; Secure' : '';
|
|
return `review_session=${token}; HttpOnly; SameSite=Lax; Path=/api/review; Max-Age=604800${secure}`;
|
|
}
|
|
|
|
export async function optionalCustomer(req: CustomerRequest, _res: Response, next: NextFunction) {
|
|
const token = readCookie(req, 'review_session');
|
|
if (token) {
|
|
const row = await database.one<NonNullable<CustomerRequest['customer']>>(`
|
|
SELECT s.project_id, s.reviewer_name
|
|
FROM customer_sessions s
|
|
JOIN projects p ON p.id = s.project_id
|
|
WHERE s.token_hash = ? AND s.expires_at > ?
|
|
AND p.customer_access_enabled = TRUE AND p.status != 'archived'
|
|
AND (p.access_expires_at IS NULL OR p.access_expires_at > ?)
|
|
`, [sha256(token), new Date().toISOString(), new Date().toISOString()]);
|
|
if (row) req.customer = row;
|
|
}
|
|
next();
|
|
}
|
|
|
|
export async function requireCustomerProject(req: CustomerRequest, res: Response, next: NextFunction) {
|
|
const project = await database.one<{ id: number }>('SELECT id FROM projects WHERE slug = ?', [req.params.slug]);
|
|
if (!project || !req.customer || req.customer.project_id !== project.id) {
|
|
res.status(401).json({ error: '请先输入项目访问密码' });
|
|
return;
|
|
}
|
|
next();
|
|
}
|