feat(api): 开发平台、门店相关代码
This commit is contained in:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user