// @ts-check import { requireAdminRole, verifyAdminSessionToken } from '../../services/admin/admin-auth-service.js' import { writeAdminAuditLog } from '../../services/admin/admin-audit-service.js' import { buildSuccessPayload, createHttpError, sendRouteError } from '../../utils/http.js' import { logWarn } from '../../utils/logger.js' /** @typedef {import('express').NextFunction} NextFunction */ /** @typedef {import('express').Request} ExpressRequest */ /** @typedef {import('express').Response} ExpressResponse */ /** @typedef {import('../../types/admin-route-inputs.js').AdminRouteAdminSession} AdminRouteAdminSession */ /** @typedef {ExpressRequest & { adminSession?: AdminRouteAdminSession | null }} AdminExpressRequest */ /** * @typedef {{ * action?: string * targetType?: string * targetId?: string * data?: Record * }} AdminAuditPayload */ /** * @typedef {{ * successMessage?: string * errorMessage?: string * scope?: string * audit?: (req: AdminExpressRequest, data: unknown) => (AdminAuditPayload | null | undefined) * }} AdminJsonHandlerOptions */ /** * @typedef {{ * errorMessage?: string * scope?: string * }} AdminFileHandlerOptions */ /** * @param {(req: AdminExpressRequest, res: ExpressResponse) => unknown | Promise} action * @param {AdminJsonHandlerOptions} [options] */ export function createJsonHandler(action, { successMessage = 'ok', errorMessage, scope, audit } = {}) { return async (req, res) => { try { const data = await action(req, res) await recordAdminAudit(req.adminSession, req, data, audit) res.json(buildSuccessPayload(data, successMessage)) } catch (error) { sendRouteError(res, error, errorMessage, scope) } } } /** * @param {(req: AdminExpressRequest, res: ExpressResponse) => string | Promise} action * @param {AdminFileHandlerOptions} [options] */ export function createFileHandler(action, { errorMessage, scope } = {}) { return async (req, res) => { try { const filePath = await action(req, res) res.sendFile(filePath) } catch (error) { sendRouteError(res, error, errorMessage, scope) } } } /** @param {AdminExpressRequest} req */ /** @param {ExpressResponse} res */ /** @param {NextFunction} next */ export async function requireAdminSession(req, res, next) { try { req.adminSession = await verifyAdminSessionToken(extractBearerToken(req)) next() } catch (error) { sendRouteError(res, error, '后台鉴权失败', '[admin/auth]') } } /** @param {string[]} allowedRoles */ export function requireAdminRoles(allowedRoles) { return (req, res, next) => { try { requireAdminRole(req.adminSession, allowedRoles) next() } catch (error) { sendRouteError(res, error, '后台鉴权失败', '[admin/auth/role]') } } } async function recordAdminAudit(session, req, data, audit) { if (!audit) { return } const payload = typeof audit === 'function' ? audit(req, data) : audit if (!payload) { return } try { await writeAdminAuditLog(session, payload) } catch (error) { logWarn('[admin/audit]', '后台审计日志写入失败,已跳过', { action: payload.action, targetType: payload.targetType, targetId: payload.targetId, error, }) } } export function extractBearerToken(req) { const authorization = String(req.headers.authorization || '').trim() const matched = authorization.match(/^Bearer\s+(.+)$/i) if (!matched) { throw createHttpError('未登录或登录已失效', { statusCode: 401, errorCode: 'admin_auth_required', }) } return matched[1] }