Protect Tabularium with persistent authenticator-code login
Verify and deploy Tabularium / verify-and-deploy (push) Successful in 5m28s
Verify and deploy Tabularium / verify-and-deploy (push) Successful in 5m28s
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { mkdtempSync, writeFileSync, rmSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { tmpdir } from "node:os";
|
||||
import {
|
||||
authConfig,
|
||||
base32,
|
||||
matchingStep,
|
||||
newAuthConfig,
|
||||
safeReturn,
|
||||
sessionCookie,
|
||||
signSession,
|
||||
totp,
|
||||
validSession,
|
||||
} from "../src/lib/auth";
|
||||
import { consumeCode } from "../src/lib/auth-store";
|
||||
|
||||
test("RFC6238 SHA1 vectors, format and narrow time window", () => {
|
||||
const secret = base32(Buffer.from("12345678901234567890"));
|
||||
for (const [time, expected] of [
|
||||
[59, "94287082"],
|
||||
[1111111109, "07081804"],
|
||||
[2000000000, "69279037"],
|
||||
] as const) {
|
||||
assert.equal(totp(secret, Math.floor(time / 30), 8), expected);
|
||||
}
|
||||
assert.equal(matchingStep(secret, "287082", 59000), 1);
|
||||
assert.equal(matchingStep(secret, "123", 59000), null);
|
||||
assert.equal(matchingStep(secret, totp(secret, 10), 1000000), null);
|
||||
});
|
||||
test("signed sessions reject tampering, expiration, future issuance and excessive lifetime", () => {
|
||||
const config = newAuthConfig();
|
||||
const token = signSession(config, 100);
|
||||
assert.equal(validSession(token, config, 101), true);
|
||||
assert.equal(validSession(token + "x", config, 101), false);
|
||||
assert.equal(validSession(token, newAuthConfig(), 101), false);
|
||||
assert.equal(validSession(signSession(config, 100, 1), config, 101), false);
|
||||
assert.equal(validSession(signSession(config, 200), config, 100), false);
|
||||
assert.equal(
|
||||
validSession(signSession(config, 100, 604801), config, 101),
|
||||
false,
|
||||
);
|
||||
assert.equal(validSession("a.b.c", config), false);
|
||||
assert.equal(safeReturn("//untrusted.example"), "/");
|
||||
assert.equal(safeReturn("/\\untrusted.example"), "/");
|
||||
assert.equal(safeReturn("/docs/example"), "/docs/example");
|
||||
assert.match(sessionCookie("test"), /HttpOnly; SameSite=Lax/);
|
||||
assert.match(sessionCookie("", true), /Max-Age=0/);
|
||||
});
|
||||
test("durable replay protection and rate limit survive reopened SQLite connections", () => {
|
||||
const directory = mkdtempSync(join(tmpdir(), "tabularium-auth-state-"));
|
||||
const previous = process.env.AUTH_CONFIG_FILE;
|
||||
try {
|
||||
const config = newAuthConfig();
|
||||
process.env.AUTH_CONFIG_FILE = join(directory, "config.json");
|
||||
writeFileSync(process.env.AUTH_CONFIG_FILE, JSON.stringify(config), {
|
||||
mode: 0o600,
|
||||
});
|
||||
assert.deepEqual(authConfig(), config);
|
||||
const now = 2000000000000;
|
||||
const code = totp(config.secret, Math.floor(now / 30000));
|
||||
assert.equal(consumeCode(config, code, now), "ok");
|
||||
assert.equal(consumeCode(config, code, now), "invalid");
|
||||
for (let i = 0; i < 18; i++)
|
||||
assert.equal(consumeCode(config, "invalid", now), "invalid");
|
||||
assert.equal(consumeCode(config, code, now), "limited");
|
||||
const later = now + 300001;
|
||||
assert.equal(
|
||||
consumeCode(
|
||||
config,
|
||||
totp(config.secret, Math.floor(later / 30000)),
|
||||
later,
|
||||
),
|
||||
"ok",
|
||||
);
|
||||
writeFileSync(process.env.AUTH_CONFIG_FILE, "{}");
|
||||
assert.throws(() => authConfig());
|
||||
} finally {
|
||||
if (previous === undefined) delete process.env.AUTH_CONFIG_FILE;
|
||||
else process.env.AUTH_CONFIG_FILE = previous;
|
||||
rmSync(directory, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user