atproto pds in zig pds.zat.dev
pds atproto
25

Configure Feed

Select the types of activity you want to include in your feed.

Make OAuth passkey login one step

zzstoatzz (May 27, 2026, 12:28 PM -0500) 31e6a28b 23012fb2

+10 -3
+10 -3
src/atproto/oauth.zig
··· 195 195 \\form{{border-top:1px solid var(--line);padding:0 15px 15px}} 196 196 \\.actions{{display:grid;grid-template-columns:1fr 1fr;gap:12px}} 197 197 \\.deny{{background:transparent;color:var(--danger);border:1px solid var(--line);box-shadow:none}} 198 + \\.password-fallback{{margin-top:16px;border-color:var(--line);background:color-mix(in srgb,var(--panel-2) 46%,transparent)}} 199 + \\.password-fallback summary{{padding:11px 13px;color:var(--muted);font-size:13px}} 200 + \\.password-fallback .password-inner{{padding:0 13px 13px}} 198 201 \\.foot{{font-size:12px;margin:16px 0 0;color:var(--dim);overflow-wrap:anywhere}} 199 202 \\@media (min-width:680px){{.stack{{grid-template-columns:minmax(0,1fr) minmax(280px,.8fr);align-items:start}}.auth{{grid-column:1 / -1}}}} 200 203 \\@media (max-width:520px){{.shell{{padding:18px 14px 26px}}.brand{{margin-bottom:42px}}.actions{{grid-template-columns:1fr}}.deny{{order:2;margin-top:0}}button{{margin-top:18px}}}} ··· 217 220 \\<input type="hidden" name="request_uri" value="{s}{s}"> 218 221 \\<label for="username">account</label> 219 222 \\<input id="username" name="username" autocomplete="username" placeholder="bufo.uk" value="{s}" autofocus> 223 + \\<button id="passkey-login" type="button">continue with passkey</button> 224 + \\<details class="password-fallback"> 225 + \\<summary>use password instead</summary> 226 + \\<div class="password-inner"> 220 227 \\<label for="password">password</label> 221 228 \\<input id="password" name="password" type="password" autocomplete="current-password"> 222 - \\<button id="passkey-login" type="button">use passkey</button> 223 229 \\<div class="actions"> 224 230 \\<button class="deny" name="deny" value="1" formnovalidate>deny</button> 225 231 \\<button>authorize</button> 226 232 \\</div> 233 + \\</div> 234 + \\</details> 227 235 \\</form> 228 236 \\</section> 229 237 \\</div> ··· 234 242 \\const bufToB64=(buf)=>btoa(String.fromCharCode(...new Uint8Array(buf))).replace(/\+/g,'-').replace(/\//g,'_').replace(/=+$/,''); 235 243 \\const prepGet=(o)=>{{const p=o.publicKey;p.challenge=b64ToBuf(p.challenge);p.allowCredentials=(p.allowCredentials||[]).map(c=>({{...c,id:b64ToBuf(c.id)}}));return o}}; 236 244 \\const serializeGet=(c)=>({{id:c.id,rawId:bufToB64(c.rawId),type:c.type,response:{{clientDataJSON:bufToB64(c.response.clientDataJSON),authenticatorData:bufToB64(c.response.authenticatorData),signature:bufToB64(c.response.signature),userHandle:c.response.userHandle?bufToB64(c.response.userHandle):null}}}}); 237 - \\let pendingPasskey=null; 238 - \\document.querySelector('#passkey-login')?.addEventListener('click',async()=>{{const button=document.querySelector('#passkey-login');const label='use passkey';try{{const request_uri=document.querySelector('input[name=request_uri]').value;if(!pendingPasskey){{const username=document.querySelector('#username').value;button.disabled=true;button.textContent='checking passkeys...';const options=await fetch('/oauth/passkey/options',{{method:'POST',headers:{{'content-type':'application/json'}},body:JSON.stringify({{request_uri,identifier:username}})}}).then(async r=>{{if(!r.ok)throw new Error((await r.json()).error||'passkey unavailable');return r.json()}});pendingPasskey=prepGet(options);button.disabled=false;button.textContent='open passkey prompt';return}}const options=pendingPasskey;pendingPasskey=null;button.textContent='waiting for browser prompt...';const credential=await navigator.credentials.get(options);const result=await fetch('/oauth/passkey/finish',{{method:'POST',headers:{{'content-type':'application/json'}},body:JSON.stringify({{request_uri,credential:serializeGet(credential)}})}}).then(async r=>{{if(!r.ok)throw new Error((await r.json()).error||'passkey login failed');return r.json()}});location.href=result.redirect_uri}}catch(err){{pendingPasskey=null;button.disabled=false;button.textContent=err.message||label;setTimeout(()=>button.textContent=label,2800)}}}}); 245 + \\document.querySelector('#passkey-login')?.addEventListener('click',async()=>{{const button=document.querySelector('#passkey-login');const label='continue with passkey';try{{const request_uri=document.querySelector('input[name=request_uri]').value;const username=document.querySelector('#username').value;button.disabled=true;button.textContent='checking passkeys...';const options=await fetch('/oauth/passkey/options',{{method:'POST',headers:{{'content-type':'application/json'}},body:JSON.stringify({{request_uri,identifier:username}})}}).then(async r=>{{if(!r.ok)throw new Error((await r.json()).error||'passkey unavailable');return r.json()}});button.textContent='waiting for browser prompt...';const credential=await navigator.credentials.get(prepGet(options));const result=await fetch('/oauth/passkey/finish',{{method:'POST',headers:{{'content-type':'application/json'}},body:JSON.stringify({{request_uri,credential:serializeGet(credential)}})}}).then(async r=>{{if(!r.ok)throw new Error((await r.json()).error||'passkey login failed');return r.json()}});location.href=result.redirect_uri}}catch(err){{button.disabled=false;button.textContent=err.message||label;setTimeout(()=>button.textContent=label,2800)}}}}); 239 246 \\</script> 240 247 \\</body> 241 248 \\</html>