atproto pds in zig pds.zat.dev
pds atproto
26

Configure Feed

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

Show password auth when passkeys unavailable

zzstoatzz (May 27, 2026, 1:46 PM -0500) 0bfb5c01 d3aac10a

+39 -17
+39 -17
src/atproto/oauth.zig
··· 147 147 return oauthError(request, .bad_request, "invalid_scope", "Failed to summarize requested permission set"); 148 148 }; 149 149 const issuer = config.publicUrl(); 150 - const passkey_html = try passkeyLoginHtml(allocator, oauth_request.login_hint); 150 + const passkey_available = try passkeyLoginAvailable(allocator, oauth_request.login_hint); 151 + const auth_method_html = try authMethodHtml(allocator, passkey_available); 151 152 const html = try std.fmt.allocPrint(allocator, 152 153 \\<!doctype html> 153 154 \\<html lang="en"> ··· 199 200 \\.password-fallback{{margin-top:16px;border-color:var(--line);background:color-mix(in srgb,var(--panel-2) 46%,transparent)}} 200 201 \\.password-fallback summary{{padding:11px 13px;color:var(--muted);font-size:13px}} 201 202 \\.password-fallback .password-inner{{padding:0 13px 13px}} 203 + \\.password-primary{{margin-top:16px;border-top:1px solid var(--line);padding-top:2px}} 204 + \\.security-link{{margin-top:12px;font-size:12px;color:var(--dim)}}.security-link a{{color:var(--green)}} 202 205 \\.foot{{font-size:12px;margin:16px 0 0;color:var(--dim);overflow-wrap:anywhere}} 203 206 \\@media (min-width:680px){{.stack{{grid-template-columns:minmax(0,1fr) minmax(280px,.8fr);align-items:start}}.auth{{grid-column:1 / -1}}}} 204 207 \\@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}}}} ··· 222 225 \\<label for="username">account</label> 223 226 \\<input id="username" name="username" autocomplete="username" placeholder="bufo.uk" value="{s}" autofocus> 224 227 \\{s} 225 - \\<details class="password-fallback"> 226 - \\<summary>use password instead</summary> 227 - \\<div class="password-inner"> 228 - \\<label for="password">password</label> 229 - \\<input id="password" name="password" type="password" autocomplete="current-password"> 230 - \\<div class="actions"> 231 - \\<button class="deny" name="deny" value="1" formnovalidate>deny</button> 232 - \\<button>authorize</button> 233 - \\</div> 234 - \\</div> 235 - \\</details> 236 228 \\</form> 237 229 \\</section> 238 230 \\</div> ··· 258 250 request_uri_prefix, 259 251 oauth_request.request_id, 260 252 try htmlEscape(allocator, oauth_request.login_hint orelse ""), 261 - passkey_html, 253 + auth_method_html, 262 254 try htmlEscape(allocator, issuer), 263 255 }); 264 256 try respondHtml(request, .ok, html); 265 257 } 266 258 267 - fn passkeyLoginHtml(allocator: std.mem.Allocator, login_hint: ?[]const u8) ![]const u8 { 259 + fn authMethodHtml(allocator: std.mem.Allocator, passkey_available: bool) ![]const u8 { 260 + if (passkey_available) { 261 + return allocator.dupe(u8, 262 + \\<button id="passkey-login" type="button">continue with passkey</button> 263 + \\<details class="password-fallback"> 264 + \\<summary>use password instead</summary> 265 + \\<div class="password-inner"> 266 + \\<label for="password">password</label> 267 + \\<input id="password" name="password" type="password" autocomplete="current-password"> 268 + \\<div class="actions"> 269 + \\<button class="deny" name="deny" value="1" formnovalidate>deny</button> 270 + \\<button>authorize</button> 271 + \\</div> 272 + \\</div> 273 + \\</details> 274 + ); 275 + } 276 + return allocator.dupe(u8, 277 + \\<div class="password-primary"> 278 + \\<label for="password">password</label> 279 + \\<input id="password" name="password" type="password" autocomplete="current-password"> 280 + \\<div class="actions"> 281 + \\<button class="deny" name="deny" value="1" formnovalidate>deny</button> 282 + \\<button>authorize</button> 283 + \\</div> 284 + \\<p class="security-link">Want passkey sign-in next time? Add one from <a href="/security">security</a>.</p> 285 + \\</div> 286 + ); 287 + } 288 + 289 + fn passkeyLoginAvailable(allocator: std.mem.Allocator, login_hint: ?[]const u8) !bool { 268 290 if (login_hint) |identifier| { 269 - const account = (try store.findAccount(allocator, identifier)) orelse return ""; 291 + const account = (try store.findAccount(allocator, identifier)) orelse return false; 270 292 const passkeys = try store.listPasskeys(allocator, account.did); 271 - if (passkeys.len == 0) return ""; 293 + return passkeys.len != 0; 272 294 } 273 - return allocator.dupe(u8, "<button id=\"passkey-login\" type=\"button\">continue with passkey</button>"); 295 + return true; 274 296 } 275 297 276 298 pub fn authorizePost(request: *http.Server.Request) !void {