76 lines
2.6 KiB
TypeScript
76 lines
2.6 KiB
TypeScript
import { createHash, timingSafeEqual } from 'crypto'
|
|
import { NextRequest, NextResponse } from 'next/server'
|
|
|
|
import {
|
|
NotificationCenterAdminError,
|
|
resolveNotificationCenterRecipientAlias,
|
|
} from '@/lib/notification-center-admin'
|
|
|
|
export const runtime = 'nodejs'
|
|
export const dynamic = 'force-dynamic'
|
|
|
|
function withNoStore(response: NextResponse) {
|
|
response.headers.set('Cache-Control', 'no-store, no-cache, must-revalidate')
|
|
response.headers.set('Pragma', 'no-cache')
|
|
response.headers.set('Expires', '0')
|
|
return response
|
|
}
|
|
|
|
function sha256(value: string) {
|
|
return createHash('sha256').update(value, 'utf8').digest()
|
|
}
|
|
|
|
function isAuthorizedBearer(providedSecret: string | null, configuredSecret: string | null) {
|
|
if (!providedSecret || !configuredSecret) return false
|
|
return timingSafeEqual(sha256(providedSecret), sha256(configuredSecret))
|
|
}
|
|
|
|
function readBearerSecret(request: NextRequest) {
|
|
const header = String(request.headers.get('authorization') || '')
|
|
if (!header.startsWith('Bearer ')) return null
|
|
const token = header.slice('Bearer '.length).trim()
|
|
return token || null
|
|
}
|
|
|
|
function errorResponse(status: number, code: string, message: string) {
|
|
return withNoStore(
|
|
NextResponse.json(
|
|
{
|
|
error: { code, message },
|
|
},
|
|
{ status },
|
|
),
|
|
)
|
|
}
|
|
|
|
export async function POST(request: NextRequest) {
|
|
const configuredSecret = String(process.env.YYD_NOTIFICATION_CENTER_ADMIN_API_SECRET || '').trim() || null
|
|
if (!configuredSecret) {
|
|
return errorResponse(503, 'NOTIFICATION_CENTER_ADMIN_SECRET_NOT_CONFIGURED', '服务端未配置通知中心鉴权密钥')
|
|
}
|
|
|
|
const providedSecret = readBearerSecret(request)
|
|
if (!isAuthorizedBearer(providedSecret, configuredSecret)) {
|
|
return errorResponse(401, 'UNAUTHORIZED', '未授权')
|
|
}
|
|
|
|
const body = await request.json().catch(() => ({}))
|
|
const recipientAlias = String((body as { recipientAlias?: unknown })?.recipientAlias || '').trim()
|
|
if (!recipientAlias) {
|
|
return errorResponse(422, 'RECIPIENT_ALIAS_REQUIRED', '花名不能为空')
|
|
}
|
|
|
|
try {
|
|
const resolved = await resolveNotificationCenterRecipientAlias(recipientAlias)
|
|
if (!resolved) {
|
|
return errorResponse(404, 'RECIPIENT_ALIAS_NOT_FOUND', '花名未找到对应钉钉 userId')
|
|
}
|
|
return withNoStore(NextResponse.json(resolved, { status: 200 }))
|
|
} catch (error) {
|
|
if (error instanceof NotificationCenterAdminError) {
|
|
return errorResponse(error.statusCode, error.code, error.message)
|
|
}
|
|
return errorResponse(500, 'NOTIFICATION_CENTER_RECIPIENT_RESOLVE_FAILED', '花名解析失败,请稍后重试')
|
|
}
|
|
}
|