Refactor Study Desk application structure

This commit is contained in:
Elijah 2026-08-07 19:31:23 -07:00
parent faaccf8a7e
commit 089439ed90
145 changed files with 8087 additions and 3412 deletions

View file

@ -0,0 +1,56 @@
import { createHash, randomBytes, randomUUID } from "node:crypto";
import { existsSync } from "node:fs";
import { pathToFileURL } from "node:url";
import Database from "better-sqlite3";
import { databasePathFromUrl } from "./databasePath.mjs";
const PASSWORD_HASH_KEY = "admin_password_hash";
const RESET_TOKEN_KEY = "admin_password_reset";
const RESET_TOKEN_LIFETIME_MS = 15 * 60 * 1000;
function activeRecord(value) {
try {
const record = JSON.parse(value);
return new Date(record.expiresAt).getTime() > Date.now();
} catch {
return false;
}
}
export function createResetToken(databasePath) {
if (!existsSync(databasePath)) throw new Error(`Database does not exist: ${databasePath}`);
const database = new Database(databasePath, { fileMustExist: true });
try {
if (!database.prepare('SELECT 1 FROM "Setting" WHERE "key" = ?').get(PASSWORD_HASH_KEY)) {
throw new Error("Password recovery is unavailable before initial setup");
}
const existing = database
.prepare('SELECT "value" FROM "Setting" WHERE "key" = ?')
.get(RESET_TOKEN_KEY);
if (existing && activeRecord(existing.value)) {
throw new Error("An unexpired reset token already exists; it was not replaced");
}
const token = randomBytes(24).toString("base64url");
const expiresAt = new Date(Date.now() + RESET_TOKEN_LIFETIME_MS).toISOString();
const value = JSON.stringify({
digest: createHash("sha256").update(token, "utf8").digest("hex"),
nonce: randomUUID(),
expiresAt,
});
database
.prepare(
'INSERT INTO "Setting" ("key", "value") VALUES (?, ?) ON CONFLICT("key") DO UPDATE SET "value" = excluded."value"'
)
.run(RESET_TOKEN_KEY, value);
return { token, expiresAt };
} finally {
database.close();
}
}
if (import.meta.url === pathToFileURL(process.argv[1]).href) {
const result = createResetToken(databasePathFromUrl(process.env.DATABASE_URL));
console.info(`Password reset token: ${result.token}`);
console.info(`Expires: ${result.expiresAt}`);
}