Protect Tabularium with persistent authenticator-code login
Verify and deploy Tabularium / verify-and-deploy (push) Successful in 5m28s

This commit is contained in:
2026-10-27 21:48:16 +08:00
parent 0a4057ac2a
commit 9fcfbdb694
31 changed files with 1549 additions and 60 deletions
+72
View File
@@ -0,0 +1,72 @@
import { chmodSync } from "node:fs";
import { dirname, join } from "node:path";
import { DatabaseSync } from "node:sqlite";
import { authId, matchingStep, type AuthConfig } from "./auth";
function database() {
const configFile = process.env.AUTH_CONFIG_FILE;
if (!configFile) throw new Error("Authentication is not configured");
const file = join(dirname(configFile), "login-state.sqlite");
const db = new DatabaseSync(file);
chmodSync(file, 0o600);
db.exec(`PRAGMA busy_timeout=5000;
CREATE TABLE IF NOT EXISTS login_state (
id TEXT PRIMARY KEY, window_start INTEGER NOT NULL,
attempts INTEGER NOT NULL DEFAULT 0, last_step INTEGER NOT NULL DEFAULT -1
);`);
return db;
}
// SQLite's write transaction serializes attempts across workers and restarts.
export function consumeCode(
config: AuthConfig,
code: string,
now = Date.now(),
): "ok" | "invalid" | "limited" {
const db = database();
try {
db.exec("BEGIN IMMEDIATE");
const id = authId(config);
db.prepare(
"INSERT OR IGNORE INTO login_state(id,window_start) VALUES (?,?)",
).run(id, now);
const state = db
.prepare("SELECT * FROM login_state WHERE id=?")
.get(id) as {
window_start: number;
attempts: number;
last_step: number;
};
const expired = now - state.window_start >= 300000;
const attempts = expired ? 0 : state.attempts;
let result: "ok" | "invalid" | "limited" = "limited";
if (attempts < 20) {
db.prepare(
"UPDATE login_state SET attempts=?,window_start=? WHERE id=?",
).run(attempts + 1, expired ? now : state.window_start, id);
const step = matchingStep(config.secret, code, now);
result = step !== null && step > state.last_step ? "ok" : "invalid";
if (result === "ok")
db.prepare("UPDATE login_state SET last_step=? WHERE id=?").run(
step!,
id,
);
}
db.exec("COMMIT");
return result;
} catch (error) {
try {
db.exec("ROLLBACK");
} catch {
/* Transaction may not have started. */
}
throw error;
} finally {
db.close();
}
}
export function initializeStore() {
const db = database();
db.close();
}