Refactor Study Desk application structure
This commit is contained in:
parent
faaccf8a7e
commit
089439ed90
145 changed files with 8087 additions and 3412 deletions
56
scripts/request-password-reset.mjs
Normal file
56
scripts/request-password-reset.mjs
Normal 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}`);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue