feat: add TallyNote local reimbursement ledger
TallyNote release / linux-x64 (push) Failing after 2m41s

This commit is contained in:
Qiufeng
2026-08-29 01:02:29 +08:00
commit 9719429f4a
62 changed files with 29200 additions and 0 deletions
+52
View File
@@ -0,0 +1,52 @@
import argon2 from "argon2";
import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
const ARGON_OPTIONS = {
type: argon2.argon2id,
memoryCost: 65_536,
timeCost: 3,
parallelism: 1,
hashLength: 32,
} as const;
export function normalizeUsername(username: string): string {
return username.normalize("NFKC").trim().toLocaleLowerCase("und");
}
export function validateNewPassword(password: string): string | null {
const length = [...password].length;
if (length < 12 || length > 128 || Buffer.byteLength(password, "utf8") > 512) {
return "密码长度需要为 12–128 个字符";
}
return null;
}
export function hashPassword(password: string): Promise<string> {
return argon2.hash(password, ARGON_OPTIONS);
}
export async function verifyPassword(hash: string, password: string): Promise<boolean> {
try {
return await argon2.verify(hash, password);
} catch {
return false;
}
}
export function randomToken(bytes = 32): string {
return randomBytes(bytes).toString("base64url");
}
export function sha256(value: string | Buffer): string {
return createHash("sha256").update(value).digest("hex");
}
export function constantTimeEqual(left: string, right: string): boolean {
const leftBuffer = Buffer.from(left);
const rightBuffer = Buffer.from(right);
return leftBuffer.length === rightBuffer.length && timingSafeEqual(leftBuffer, rightBuffer);
}
export function temporaryPassword(): string {
return `${randomToken(15)}A7!`;
}