44 lines
1.5 KiB
TypeScript
44 lines
1.5 KiB
TypeScript
import type { NextFunction, Request, Response } from 'express'
|
|
import { safeEqual, sha256 } from './signature'
|
|
import { AdminApiError, getAdminToken, type AdminAuditActor } from './admin-project-registry'
|
|
|
|
const clean = (value: unknown) => String(value || '').trim()
|
|
|
|
const parseBearerToken = (value: unknown) => {
|
|
const raw = clean(value)
|
|
if (!raw) return ''
|
|
const matched = raw.match(/^Bearer\s+(.+)$/i)
|
|
return clean(matched?.[1] || '')
|
|
}
|
|
|
|
export type AdminRequest = Request & {
|
|
adminActor?: AdminAuditActor
|
|
}
|
|
|
|
export const extractAdminActor = (req: Request): AdminAuditActor => ({
|
|
actorType: 'admin',
|
|
actorId: clean(req.header('x-yyd-admin-actor')) || 'admin',
|
|
requestId: clean(req.header('x-request-id')) || undefined,
|
|
ipAddress: clean(req.ip || req.socket.remoteAddress) || undefined,
|
|
})
|
|
|
|
export const authenticateAdmin = (req: AdminRequest, _res: Response, next: NextFunction) => {
|
|
try {
|
|
const expectedToken = getAdminToken()
|
|
if (!expectedToken) {
|
|
throw new AdminApiError(503, 'ADMIN_AUTH_NOT_CONFIGURED', '管理员鉴权未配置')
|
|
}
|
|
const actualToken = parseBearerToken(req.header('authorization'))
|
|
if (!actualToken) {
|
|
throw new AdminApiError(401, 'ADMIN_AUTH_REQUIRED', '缺少管理员 Bearer Token')
|
|
}
|
|
if (!safeEqual(sha256(actualToken), sha256(expectedToken))) {
|
|
throw new AdminApiError(401, 'ADMIN_TOKEN_INVALID', '管理员 Token 无效')
|
|
}
|
|
req.adminActor = extractAdminActor(req)
|
|
next()
|
|
} catch (error) {
|
|
next(error)
|
|
}
|
|
}
|