From b31a4835ee9e88a82bb7c2bf00927dd6b8195613 Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Mon, 2 Nov 2026 00:02:32 +0800 Subject: [PATCH] test(auth): verify recovery endpoints over HTTP with real storage --- apps/waterfall-wiki/tests/account-integration.ts | 16 +++++++++++++++- apps/waterfall-wiki/tests/integration.test.ts | 2 +- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/apps/waterfall-wiki/tests/account-integration.ts b/apps/waterfall-wiki/tests/account-integration.ts index 85609b1..ea8c971 100644 --- a/apps/waterfall-wiki/tests/account-integration.ts +++ b/apps/waterfall-wiki/tests/account-integration.ts @@ -3,7 +3,7 @@ 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() { +export async function verifyEmailAccounts(base: string) { 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 { @@ -31,6 +31,20 @@ export async function verifyEmailAccounts() { 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 post=(action:string,body:object,origin=process.env.ACCOUNT_PUBLIC_ORIGIN||base)=>fetch(base+'/api/account/'+action,{method:'POST',headers:{'Content-Type':'application/json',Origin:origin},body:JSON.stringify(body)}); + assert.equal((await post('forgot',{email},'https://untrusted.invalid')).status,403,'Cross-origin recovery is rejected'); + const unknown=await post('forgot',{email:randomUUID()+'@example.com'}); + assert.equal(unknown.status,200,'Unknown email receives the same non-enumerating response without sending mail'); + assert.match((await unknown.json()).message,/若该邮箱/); + const httpToken=newToken(),httpPassword='http replacement secure password'; + const priorHttpSession=await loginAccount(email,replacement);assert.ok(priorHttpSession); + await db().query("INSERT INTO account_tokens VALUES ($1,$2,'reset',now()+interval '30 minutes')",[tokenDigest(httpToken),id]); + const changed=await post('reset',{token:httpToken,password:httpPassword}); + assert.equal(changed.status,200); + assert.match(changed.headers.get('set-cookie')||'',/Max-Age=0/); + assert.equal(await currentAccount(request(priorHttpSession)),null); + assert.ok(await loginAccount(email,httpPassword),'HTTP recovery changes the stored password'); + assert.equal((await post('reset',{token:httpToken,password:replacement})).status,400,'HTTP recovery rejects a consumed token'); 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]);} diff --git a/apps/waterfall-wiki/tests/integration.test.ts b/apps/waterfall-wiki/tests/integration.test.ts index 478cd5c..34fac0c 100644 --- a/apps/waterfall-wiki/tests/integration.test.ts +++ b/apps/waterfall-wiki/tests/integration.test.ts @@ -42,7 +42,7 @@ test('real PostgreSQL + S3 notes, flat tags, resource sharing and video processi let ready=false;for(let i=0;i<60;i++){try{if((await fetch(base+'/api/health')).ok){ready=true;break;}}catch{}await setTimeout(250);}assert.ok(ready); for(const route of ['/','/images','/videos','/audio','/articles','/motion','/library/import','/tools/doodle','/tools/skills','/tools/skills/street-people','/tools/skills/shadow-puppet'])assert.equal((await fetch(base+route)).status,200,route); if(!external)await verifyBrandUI(base); - await verifyEmailAccounts(); + await verifyEmailAccounts(base); await verifyCanvasWorkspace(base); if(process.env.VERIFY_CANVAS_UI==='1')await verifyCanvasAgentUI(base); await verifyGuidancePublishing(base);