Files
probably-rendering-one-more…/apps/waterfall-wiki/tests/account-integration.ts
T
codex-bot 6800a1b1c4
creative workspace review checks / verify-deployed-canvas (pull_request) Successful in 58s
creative workspace review checks / verify (pull_request) Has been cancelled
feat(auth): add single-use email password recovery and session revocation
2026-11-01 23:43:26 +08:00

38 lines
3.3 KiB
TypeScript

import assert from 'node:assert/strict';
import { randomUUID } from 'node:crypto';
import { db } from '../src/lib/db';
import { hashPassword,newToken,tokenDigest } from '../src/lib/account-crypto';
import { resetPassword,loginAccount,verifyAccount,currentAccount,logoutAccount,rateLimit } from '../src/lib/account';
export async function verifyEmailAccounts() {
const id=randomUUID(), email=id+'@example.com', password='a secure test password';
const request=(token:string)=>new Request('https://wiki.example',{headers:{cookie:'wiki_account='+token}});
try {
await db().query('INSERT INTO account_users(id,email,password_hash) VALUES ($1,$2,$3)',[id,email,await hashPassword(password)]);
assert.equal(await loginAccount(email,password),null,'unverified accounts cannot log in');
const verification=newToken();await db().query("INSERT INTO account_tokens VALUES ($1,$2,'verify',now()+interval '1 minute')",[tokenDigest(verification),id]);
const results=await Promise.all([verifyAccount(verification),verifyAccount(verification)]);assert.deepEqual(results.sort(),[false,true]);
assert.equal(await loginAccount(email,'incorrect password'),null);
const token=await loginAccount(email,password);assert.ok(token);
assert.equal((await currentAccount(request(token))).id,id);
await db().query("UPDATE account_tokens SET expires_at=now()-interval '1 second' WHERE digest=$1",[tokenDigest(token)]);
assert.equal(await currentAccount(request(token)),null,'expired sessions rejected');
const fresh=await loginAccount(email,password);assert.ok(fresh);await logoutAccount(request(fresh));assert.equal(await currentAccount(request(fresh)),null,'logout revokes token');
const oldSession=await loginAccount(email,password);assert.ok(oldSession);
const resets=[newToken(),newToken()],replacement='a new secure test password';
for(const value of resets)await db().query("INSERT INTO account_tokens VALUES ($1,$2,'reset',now()+interval '30 minutes')",[tokenDigest(value),id]);
const resetResults=await Promise.all(resets.map(value=>resetPassword(value,replacement)));
assert.deepEqual(resetResults.sort(),[false,true],'Only one concurrent reset may succeed for one account');
assert.equal(await currentAccount(request(oldSession)),null,'Reset revokes existing sessions');
assert.equal(await loginAccount(email,password),null,'Original password is no longer accepted');
assert.ok(await loginAccount(email,replacement));
assert.equal(await resetPassword(resets[0],password),false,'Reset links are single-use, including sibling links');
const expired=newToken();await db().query("INSERT INTO account_tokens VALUES ($1,$2,'reset',now()-interval '1 minute')",[tokenDigest(expired),id]);
assert.equal(await resetPassword(expired,password),false);
const wrongKind=newToken();await db().query("INSERT INTO account_tokens VALUES ($1,$2,'verify',now()+interval '30 minutes')",[tokenDigest(wrongKind),id]);
assert.equal(await resetPassword(wrongKind,password),false,'Email verification cannot reset a password');
assert.equal(await resetPassword(newToken(),'short'),false);
const key=randomUUID();assert.deepEqual(await Promise.all(Array.from({length:5},()=>rateLimit(key,3))).then(a=>a.sort()),[false,false,true,true,true]);
await db().query('DELETE FROM account_limits WHERE key=$1',[tokenDigest(key)]);
} finally {await db().query('DELETE FROM account_users WHERE id=$1',[id]);}
}