29 lines
1.5 KiB
JavaScript
29 lines
1.5 KiB
JavaScript
import { createHmac, timingSafeEqual } from 'node:crypto';
|
|
|
|
function signature(secret, type, parts) {
|
|
return createHmac('sha256', secret).update([type, ...parts].join('\u001f')).digest('hex').slice(0, 24).toUpperCase();
|
|
}
|
|
|
|
export function resolveDocumentVerificationSecret(env = process.env) {
|
|
const configured = String(env.DOCUMENT_VERIFICATION_SECRET || '');
|
|
if (env.NODE_ENV === 'production' && configured.length < 32) {
|
|
throw new Error('生产环境必须设置至少 32 个字符的 DOCUMENT_VERIFICATION_SECRET');
|
|
}
|
|
return configured || String(env.SESSION_SECRET || '') || 'development-document-verification-secret';
|
|
}
|
|
|
|
export function scoreReportCode(secret, registration, exam, results = []) {
|
|
const scores = [...results].sort((a, b) => String(a.subjectId).localeCompare(String(b.subjectId))).map(item => `${item.subjectId}:${Number(item.score)}:${item.publishedAt || item.updatedAt || ''}`);
|
|
return `SR-${signature(secret, 'score-report', [registration.id, registration.userId, exam.id, ...scores])}`;
|
|
}
|
|
|
|
export function admissionNoticeCode(secret, placement, exam) {
|
|
return `AN-${signature(secret, 'admission-notice', [placement.id, placement.userId, placement.schoolId, exam.id, placement.payload?.categoryCode || '', placement.updatedAt || ''])}`;
|
|
}
|
|
|
|
export function safeCodeEqual(left, right) {
|
|
const a = Buffer.from(String(left || '').toUpperCase());
|
|
const b = Buffer.from(String(right || '').toUpperCase());
|
|
return a.length === b.length && timingSafeEqual(a, b);
|
|
}
|