feat(api): 开发平台、门店相关代码

This commit is contained in:
R524809
2026-09-03 16:04:44 +08:00
parent 35fcf8776e
commit ada02e9135
40 changed files with 7014 additions and 5807 deletions
+5 -2
View File
@@ -13,10 +13,11 @@ const scrypt = promisify(scryptCallback) as (
keyLength: number,
options: { N: number; r: number; p: number; maxmem: number },
) => Promise<Buffer>;
// 使用较高成本的 scrypt 参数,控制离线破解密码哈希时的时间与内存成本。
const scryptOptions = { N: 16_384, r: 8, p: 1, maxmem: 32 * 1024 * 1024 };
export async function hashPassword(password: string): Promise<string> {
// The random salt and expensive KDF keep a database leak from exposing passwords directly.
// 每个密码使用独立随机盐,数据库泄露时不能直接复用预计算结果。
const salt = randomBytes(16).toString('base64url');
const derived = (await scrypt(password, salt, 64, scryptOptions)) as Buffer;
return `scrypt$${salt}$${derived.toString('base64url')}`;
@@ -40,6 +41,7 @@ export function createOpaqueToken(): string {
return randomBytes(48).toString('base64url');
}
export function tokenHash(token: string): string {
// refresh token 只以摘要形式存库;服务端无需保存可直接用于登录的原文。
return createHash('sha256').update(token).digest('base64url');
}
@@ -53,7 +55,7 @@ export function signAccessToken(
claims: AccessTokenClaims,
secret: string,
): string {
// Access tokens are short-lived; refresh sessions remain revocable in PostgreSQL.
// access token 是短期自包含凭证;真正可撤销的会话状态仍以 PostgreSQL 为准。
const header = Buffer.from(
JSON.stringify({ alg: 'HS256', typ: 'JWT' }),
).toString('base64url');
@@ -73,6 +75,7 @@ export function verifyAccessToken(
.update(`${header}.${payload}`)
.digest();
const actual = Buffer.from(signature, 'base64url');
// 恒定时间比较可避免根据签名比对耗时推测正确前缀。
if (actual.length !== expected.length || !timingSafeEqual(actual, expected))
return null;
try {