Files
eden/scripts/smoke-restore-marker.ts
T

55 lines
3.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import assert from 'node:assert/strict';
import { ConvexStore } from '../src/lib/server/store';
import { hashSecret } from '../src/lib/server/security';
import {
parseRestoreMarkerPhase,
validateRestoreMarkerTarget,
SYNTHETIC_CHARACTER_ID_MIN,
validateRestoreMarkerWrite,
} from '../src/lib/server/restore-marker';
const marker = process.env.RESTORE_MARKER;
const phase = parseRestoreMarkerPhase(process.env.RESTORE_MARKER_PHASE);
const target = validateRestoreMarkerTarget(process.env.CONVEX_SELF_HOSTED_URL);
validateRestoreMarkerWrite({
phase,
allowWrite: process.env.SMOKE_ALLOW_WRITE,
confirmation: process.env.RESTORE_MARKER_CONFIRMATION,
});
const characterId = Number(process.env.RESTORE_MARKER_CHARACTER_ID ?? String(SYNTHETIC_CHARACTER_ID_MIN + 1));
const expiryRaw = process.env.RESTORE_MARKER_SESSION_EXPIRES_AT;
const sessionExpiresAt = expiryRaw === undefined ? Date.now() + 7 * 24 * 60 * 60_000 : Number(expiryRaw);
if (!marker || !/^[A-Za-z0-9._-]{8,100}$/.test(marker)) throw new Error('RESTORE_MARKER must be a synthetic 8–100 character identifier');
if (!Number.isSafeInteger(characterId) || characterId < SYNTHETIC_CHARACTER_ID_MIN) throw new Error(`RESTORE_MARKER_CHARACTER_ID must be a reserved synthetic ID >= ${SYNTHETIC_CHARACTER_ID_MIN}`);
if (!Number.isSafeInteger(sessionExpiresAt) || sessionExpiresAt <= Date.now()) throw new Error('RESTORE_MARKER_SESSION_EXPIRES_AT must be a future millisecond timestamp');
if (phase !== 'write' && expiryRaw === undefined) throw new Error('RESTORE_MARKER_SESSION_EXPIRES_AT is required when verifying or invalidating a marker');
const store = new ConvexStore(target.toString(), process.env.CONVEX_BRIDGE_SECRET!);
const sessionHash = hashSecret(`restore-marker-session:${marker}`);
const ownerHash = hashSecret(`restore-marker-owner:${marker}`);
if (phase === 'write') {
const now = Date.now();
const user = await store.completeLogin({
characterId,
name: `Restore marker ${marker}`,
ownerHash,
scopes: ['esi-assets.read_assets.v1'],
encryptedTokens: `restore-marker-placeholder:${marker}`,
tokenExpiresAt: sessionExpiresAt,
sessionHash,
sessionExpiresAt,
});
console.log(`Restore marker written: character=${characterId}, user=${user.userId}, expiresAt=${sessionExpiresAt}`);
} else if (phase === 'verify') {
const session = await store.session(sessionHash);
assert(session, 'restore marker session is missing');
const characters = await store.characters(session.userId);
assert(characters.some((character) => character.id === characterId && character.name === `Restore marker ${marker}`), 'restore marker character is missing or changed');
console.log(`Restore marker verified: character=${characterId}, user=${session.userId}, expiresAt=${sessionExpiresAt}`);
} else if (phase === 'expect-invalidated') {
assert.equal(await store.session(sessionHash), null, 'restore marker session remains usable; invalidation is incomplete');
console.log(`Restore marker invalidation verified: character=${characterId}, beforeExpiresAt=${sessionExpiresAt}`);
} else {
throw new Error('RESTORE_MARKER_PHASE must be write, verify, or expect-invalidated');
}