diff --git a/frontend/login.js b/frontend/login.js index da67bac..88b379e 100644 --- a/frontend/login.js +++ b/frontend/login.js @@ -4,7 +4,7 @@ }(typeof self !== 'undefined' ? self : this, function createLoginController(options) { function validShareContinuation(value) { if (typeof value !== 'string' || !value.startsWith('./?') || value.includes('#')) return './'; - const limits = { title: 200, text: 8000, url: 2048, launch: 8, shared: 5, search: 200, + const limits = { title: 200, text: 8000, url: 2048, launch: 8, shared: 6, search: 200, preview: 200, search_kind: 5, search_state: 6, search_repository: 161 }; const params = new URLSearchParams(value.slice(3)); const entries = Array.from(params.entries()); @@ -35,7 +35,9 @@ const launch = params.get('launch'); const shared = params.get('shared'); if (launch && !['continue', 'new', 'agenda'].includes(launch)) return './'; - if (shared !== null && (shared !== 'image' || launch !== 'new')) return './'; + if (shared === 'bundle') { + if (launch !== 'new' || entries.some(([name]) => !['launch', 'shared'].includes(name))) return './'; + } else if (shared !== null && (shared !== 'image' || launch !== 'new')) return './'; return value; } diff --git a/tests/test_login_frontend.py b/tests/test_login_frontend.py index e517ac2..e77a8e2 100644 --- a/tests/test_login_frontend.py +++ b/tests/test_login_frontend.py @@ -569,6 +569,51 @@ async function destination(continuation) {{ } +def test_token_and_passkey_login_resume_only_the_private_share_bundle(): + harness = f""" +const createLoginController = require({json.dumps(str(LOGIN_JS))}); +const credential = {{ + id:'credential-id', type:'public-key', rawId:Uint8Array.from([1]).buffer, + response:{{authenticatorData:Uint8Array.from([2]).buffer, + clientDataJSON:Uint8Array.from([3]).buffer, + signature:Uint8Array.from([4]).buffer,userHandle:null}}, +}}; +async function destination(continuation, authentication) {{ + let replaced = null; + const controller = createLoginController({{ + form:{{reset:()=>{{}}}}, status:{{textContent:''}}, button:{{disabled:false}}, + passkeyButton:{{disabled:false}}, credentials:{{get:async()=>credential}}, + fetchImpl:async url => url.endsWith('/options') + ? new Response(JSON.stringify({{challenge:'AQID',allowCredentials:[]}}), + {{status:200,headers:{{'Content-Type':'application/json'}}}}) + : new Response('{{}}',{{status:200}}), + location:{{replace:value=>replaced=value}}, continuation, + }}); + if (authentication === 'passkey') await controller.signInWithPasskey('Phone'); + else await controller.submit('operator-token'); + return replaced; +}} +(async()=>process.stdout.write(JSON.stringify({{ + token:await destination('./?launch=new&shared=bundle','token'), + passkey:await destination('./?launch=new&shared=bundle','passkey'), + extra:await destination('./?launch=new&shared=bundle&title=leak','token'), + duplicate:await destination('./?launch=new&shared=bundle&shared=bundle','token'), + wrongLaunch:await destination('./?launch=agenda&shared=bundle','token'), +}})))().catch(error=>{{console.error(error);process.exit(1);}}); +""" + result = subprocess.run( + ["node", "-e", harness], text=True, capture_output=True, check=True + ) + + assert json.loads(result.stdout) == { + "token": "./?launch=new&shared=bundle", + "passkey": "./?launch=new&shared=bundle", + "extra": "./", + "duplicate": "./", + "wrongLaunch": "./", + } + + @pytest.mark.anyio async def test_login_page_loads_rate_limit_controller(): html = await login()