const std = @import("std"); const auth = @import("../auth/tokens.zig"); const clock = @import("../core/clock.zig"); const config = @import("../core/config.zig"); const http_api = @import("../http/api.zig"); const store = @import("../storage/store.zig"); const webauthn = @import("webauthn"); const zat = @import("zat"); const http = std.http; const request_uri_prefix = "urn:ietf:params:oauth:request_uri:"; const challenge_ttl_seconds: i64 = 300; const LoginChallenge = struct { value: []const u8, expires_at: i64, named: bool, }; pub const CredentialKey = struct { bytes: []const u8, pub fn validate(self: CredentialKey) !void { _ = try webauthn.cose.parseEc2PublicKey(self.bytes); } }; pub fn buildAssertionMessage(allocator: std.mem.Allocator, authenticator_data: []const u8, client_data_json: []const u8) ![]u8 { var client_hash: [32]u8 = undefined; std.crypto.hash.sha2.Sha256.hash(client_data_json, &client_hash, .{}); const out = try allocator.alloc(u8, authenticator_data.len + client_hash.len); @memcpy(out[0..authenticator_data.len], authenticator_data); @memcpy(out[authenticator_data.len..], &client_hash); return out; } pub fn verifyAssertionSignature(credential_public_key: []const u8, signature_der: []const u8, authenticator_data: []const u8, client_data_json: []const u8, allocator: std.mem.Allocator) !void { const message = try buildAssertionMessage(allocator, authenticator_data, client_data_json); defer allocator.free(message); try webauthn.crypto.verifyWebAuthnEs256(credential_public_key, signature_der, message); } pub fn redirectToSecurity(request: *http_api.Request) !void { const headers = [_]http.Header{ .{ .name = "location", .value = "/account/security" }, .{ .name = "access-control-allow-origin", .value = "*" }, .{ .name = "connection", .value = "close" }, }; try http_api.respondNowClose(request, .see_other, "", &headers); } pub fn adminSessionsPage(request: *http_api.Request) !void { var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena.deinit(); const allocator = arena.allocator(); const public_url = config.publicUrl(); const public_host = try htmlEscape(allocator, displayHost(public_url)); const body = try std.fmt.allocPrint(allocator, \\ \\ \\ \\ \\ \\zds sessions \\ \\ \\ \\ \\
\\zds \\

sessions

\\

Operator view for app sign-ins and direct API token families across accounts hosted on {s}.

\\
\\
\\ \\ \\ \\
\\

\\

\\ \\ \\
\\

OAuth app sessions

\\

App sign-ins issued by the OAuth flow. New rows show whether authorization used a passkey or password.

\\
\\
\\
\\
\\
\\ \\ \\ , .{ public_host, adminSessionsScript }); try respondHtml(request, .ok, body); } pub fn xrpcStartRegistration(request: *http_api.Request) !void { var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena.deinit(); const allocator = arena.allocator(); const account = requireBearerAccount(request, allocator) catch return; const parsed = try readJson(request, allocator); const challenge = try newChallenge(allocator); try store.putWebAuthnChallenge(account.did, "registration", challenge, "{}", now() + challenge_ttl_seconds); const rp_id = try rpId(allocator); const user_id = try userId(allocator, account.did); const passkeys = try store.listPasskeys(allocator, account.did); const exclude = try credentialsJson(allocator, passkeys); const friendly = requiredString(parsed.value, "friendlyName") orelse account.handle; const body = try std.fmt.allocPrint(allocator, \\{{"options":{{"publicKey":{{"rp":{{"name":"zds","id":{f}}},"user":{{"id":{f},"name":{f},"displayName":{f}}},"challenge":{f},"pubKeyCredParams":[{{"type":"public-key","alg":-7}}],"timeout":60000,"excludeCredentials":{s},"authenticatorSelection":{{"residentKey":"required","requireResidentKey":true,"userVerification":"preferred"}},"attestation":"none"}}}}}} , .{ std.json.fmt(rp_id, .{}), std.json.fmt(user_id, .{}), std.json.fmt(account.handle, .{}), std.json.fmt(friendly, .{}), std.json.fmt(challenge, .{}), exclude, }); try http_api.json(request, .ok, body); } pub fn xrpcFinishRegistration(request: *http_api.Request) !void { var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena.deinit(); const allocator = arena.allocator(); const account = requireBearerAccount(request, allocator) catch return; const parsed = try readJson(request, allocator); const challenge = (try store.getWebAuthnChallenge(allocator, account.did, "registration")) orelse return jsonError(request, .bad_request, "Missing registration challenge"); if (challenge.expires_at < now()) return jsonError(request, .bad_request, "Registration challenge expired"); const credential = try registrationResponse(parsed.value); const verified = webauthn.registration.verify(allocator, credential, .{ .challenge = challenge.challenge, .origin = config.publicUrl(), .rp_id = try rpId(allocator), .user_verification = .preferred, }) catch return jsonError(request, .bad_request, "Invalid passkey registration response"); const friendly = requiredString(parsed.value, "friendlyName"); const id = try store.savePasskey(allocator, account.did, verified.credential_id, verified.credential_public_key, verified.sign_count, friendly); try store.deleteWebAuthnChallenge(account.did, "registration"); const credential_id = try webauthn.base64url.encodeAlloc(allocator, verified.credential_id); const body = try std.fmt.allocPrint(allocator, "{{\"id\":{f},\"credentialId\":{f}}}", .{ std.json.fmt(id, .{}), std.json.fmt(credential_id, .{}) }); try http_api.json(request, .ok, body); } pub fn xrpcList(request: *http_api.Request) !void { var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena.deinit(); const allocator = arena.allocator(); const account = requireBearerAccount(request, allocator) catch return; const passkeys = try store.listPasskeys(allocator, account.did); const body = try std.fmt.allocPrint(allocator, "{{\"passkeys\":{s}}}", .{try passkeysMetadataJson(allocator, passkeys)}); try http_api.json(request, .ok, body); } pub fn xrpcDelete(request: *http_api.Request) !void { var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena.deinit(); const allocator = arena.allocator(); const account = requireBearerAccount(request, allocator) catch return; const parsed = try readJson(request, allocator); const id = requiredString(parsed.value, "id") orelse return jsonError(request, .bad_request, "Missing passkey id"); try store.deletePasskey(account.did, id); try http_api.json(request, .ok, "{}"); } pub fn xrpcUpdate(request: *http_api.Request) !void { var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena.deinit(); const allocator = arena.allocator(); const account = requireBearerAccount(request, allocator) catch return; const parsed = try readJson(request, allocator); const id = requiredString(parsed.value, "id") orelse return jsonError(request, .bad_request, "Missing passkey id"); const friendly_name = requiredString(parsed.value, "friendlyName") orelse return jsonError(request, .bad_request, "Missing friendlyName"); try store.updatePasskeyName(account.did, id, friendly_name); try http_api.json(request, .ok, "{}"); } pub fn loginStart(request: *http_api.Request) !void { var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena.deinit(); const allocator = arena.allocator(); const parsed = try readJson(request, allocator); const request_uri = requiredString(parsed.value, "request_uri") orelse return jsonError(request, .bad_request, "Missing request_uri"); const request_id = requestIdFromUri(request_uri) orelse return jsonError(request, .bad_request, "Invalid request_uri"); const oauth_request = (try store.getOAuthRequest(allocator, request_id)) orelse return jsonError(request, .bad_request, "Unknown request_uri"); if (oauth_request.expires_at < now()) return jsonError(request, .bad_request, "Expired request_uri"); const identifier = nonEmptyString(parsed.value, "identifier") orelse oauth_request.login_hint; const challenge = try newChallenge(allocator); if (identifier == null) { try store.putDiscoverableWebAuthnChallenge(request_id, challenge, now() + challenge_ttl_seconds); const body = try std.fmt.allocPrint(allocator, \\{{"publicKey":{{"challenge":{f},"rpId":{f},"timeout":60000,"userVerification":"required"}}}} , .{ std.json.fmt(challenge, .{}), std.json.fmt(try rpId(allocator), .{}) }); try http_api.json(request, .ok, body); return; } const account = (try store.findAccount(allocator, identifier.?)) orelse return jsonError(request, .not_found, "Account not found"); const passkeys = try store.listPasskeys(allocator, account.did); if (passkeys.len == 0) return jsonError(request, .bad_request, "No passkeys are registered for this account"); try store.putWebAuthnChallenge(account.did, "login", challenge, request_id, now() + challenge_ttl_seconds); const allow = try credentialsJson(allocator, passkeys); const body = try std.fmt.allocPrint(allocator, \\{{"publicKey":{{"challenge":{f},"rpId":{f},"allowCredentials":{s},"timeout":60000,"userVerification":"required"}}}} , .{ std.json.fmt(challenge, .{}), std.json.fmt(try rpId(allocator), .{}), allow }); try http_api.json(request, .ok, body); } pub fn loginFinish(request: *http_api.Request) !void { var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena.deinit(); const allocator = arena.allocator(); const parsed = try readJson(request, allocator); const request_uri = requiredString(parsed.value, "request_uri") orelse return jsonError(request, .bad_request, "Missing request_uri"); const request_id = requestIdFromUri(request_uri) orelse return jsonError(request, .bad_request, "Invalid request_uri"); const oauth_request = (try store.getOAuthRequest(allocator, request_id)) orelse return jsonError(request, .bad_request, "Unknown request_uri"); if (oauth_request.expires_at < now()) return jsonError(request, .bad_request, "Expired request_uri"); const credential = try assertionResponse(parsed.value); const credential_id = try webauthn.base64url.decodeAlloc(allocator, credential.raw_id); const passkey = (try store.getPasskeyByCredentialId(allocator, credential_id)) orelse return jsonError(request, .unauthorized, "Unknown passkey"); const login_challenge = findLoginChallenge(allocator, passkey.did, request_id) catch |err| switch (err) { error.MissingLoginChallenge => return jsonError(request, .bad_request, "Missing login challenge"), else => return err, }; if (login_challenge.expires_at < now()) return jsonError(request, .bad_request, "Login challenge expired"); const assertion = webauthn.assertion.verify(allocator, credential, .{ .challenge = login_challenge.value, .origin = config.publicUrl(), .rp_id = try rpId(allocator), .credential_public_key = passkey.public_key, .known_sign_count = passkey.sign_count, .user_verification = .required, }) catch return jsonError(request, .unauthorized, "Invalid passkey assertion"); const code = try store.randomToken(allocator, "", 16); try store.updatePasskeyUse(passkey.id, assertion.recommended_sign_count); if (login_challenge.named) { try store.deleteWebAuthnChallenge(passkey.did, "login"); } else { try store.deleteDiscoverableWebAuthnChallenge(request_id); } try store.authorizeOAuthRequest(oauth_request.request_id, passkey.did, code, "passkey"); const redirect_uri = try authorizationRedirect(allocator, oauth_request.redirect_uri, code, oauth_request.state, oauth_request.response_mode); const body = try std.fmt.allocPrint(allocator, "{{\"redirect_uri\":{f}}}", .{std.json.fmt(redirect_uri, .{})}); try http_api.json(request, .ok, body); } fn readJson(request: *http_api.Request, allocator: std.mem.Allocator) !std.json.Parsed(std.json.Value) { const body = try http_api.readBodyAlloc(request, allocator, 64 * 1024); return std.json.parseFromSlice(std.json.Value, allocator, body, .{}) catch { try jsonError(request, .bad_request, "Expected JSON body"); return error.InvalidJson; }; } fn requiredString(value: std.json.Value, key: []const u8) ?[]const u8 { return zat.json.getString(value, key); } fn nonEmptyString(value: std.json.Value, key: []const u8) ?[]const u8 { const raw = requiredString(value, key) orelse return null; const trimmed = std.mem.trim(u8, raw, " \t\r\n"); if (trimmed.len == 0) return null; return trimmed; } fn findLoginChallenge(allocator: std.mem.Allocator, did: []const u8, request_id: []const u8) !LoginChallenge { if (try store.getWebAuthnChallenge(allocator, did, "login")) |candidate| { if (std.mem.eql(u8, candidate.state_json, request_id)) { return .{ .value = candidate.challenge, .expires_at = candidate.expires_at, .named = true, }; } } if (try store.getDiscoverableWebAuthnChallenge(allocator, request_id)) |discoverable| { return .{ .value = discoverable.challenge, .expires_at = discoverable.expires_at, .named = false, }; } return error.MissingLoginChallenge; } fn registrationResponse(value: std.json.Value) !webauthn.registration.Response { const root = try objectValue(value); const credential = root.get("credential") orelse return error.MissingField; const credential_object = try objectValue(credential); const response = credential_object.get("response") orelse return error.MissingField; return .{ .id = zat.json.getString(credential, "id") orelse return error.MissingField, .raw_id = zat.json.getString(credential, "rawId") orelse return error.MissingField, .client_data_json = zat.json.getString(response, "clientDataJSON") orelse return error.MissingField, .attestation_object = zat.json.getString(response, "attestationObject") orelse return error.MissingField, }; } fn assertionResponse(value: std.json.Value) !webauthn.assertion.Response { const root = try objectValue(value); const credential = root.get("credential") orelse return error.MissingField; const credential_object = try objectValue(credential); const response = credential_object.get("response") orelse return error.MissingField; return .{ .id = zat.json.getString(credential, "id") orelse return error.MissingField, .raw_id = zat.json.getString(credential, "rawId") orelse return error.MissingField, .client_data_json = zat.json.getString(response, "clientDataJSON") orelse return error.MissingField, .authenticator_data = zat.json.getString(response, "authenticatorData") orelse return error.MissingField, .signature = zat.json.getString(response, "signature") orelse return error.MissingField, }; } fn objectValue(value: std.json.Value) !std.json.ObjectMap { return switch (value) { .object => |object| object, else => error.MissingField, }; } fn credentialsJson(allocator: std.mem.Allocator, passkeys: []const store.Passkey) ![]const u8 { const Descriptor = struct { type: []const u8 = "public-key", id: []const u8, }; var descriptors: std.ArrayList(Descriptor) = .empty; for (passkeys) |passkey| { const id = try webauthn.base64url.encodeAlloc(allocator, passkey.credential_id); try descriptors.append(allocator, .{ .id = id }); } return std.json.Stringify.valueAlloc(allocator, descriptors.items, .{}); } fn passkeysMetadataJson(allocator: std.mem.Allocator, passkeys: []const store.Passkey) ![]const u8 { const Metadata = struct { id: []const u8, credentialId: []const u8, friendlyName: []const u8, createdAt: i64, lastUsed: ?i64, }; var items: std.ArrayList(Metadata) = .empty; for (passkeys) |passkey| { const credential_id = try webauthn.base64url.encodeAlloc(allocator, passkey.credential_id); const friendly_name = passkey.friendly_name orelse ""; try items.append(allocator, .{ .id = passkey.id, .credentialId = credential_id, .friendlyName = friendly_name, .createdAt = passkey.created_at, .lastUsed = passkey.last_used, }); } return std.json.Stringify.valueAlloc(allocator, items.items, .{}); } fn newChallenge(allocator: std.mem.Allocator) ![]u8 { var bytes: [32]u8 = undefined; store.randomBytes(&bytes); return webauthn.base64url.encodeAlloc(allocator, &bytes); } fn userId(allocator: std.mem.Allocator, did: []const u8) ![]u8 { var hash: [32]u8 = undefined; std.crypto.hash.sha2.Sha256.hash(did, &hash, .{}); return webauthn.base64url.encodeAlloc(allocator, &hash); } fn rpId(allocator: std.mem.Allocator) ![]const u8 { const url = config.publicUrl(); const without_scheme = if (std.mem.startsWith(u8, url, "https://")) url["https://".len..] else if (std.mem.startsWith(u8, url, "http://")) url["http://".len..] else url; const host_end = std.mem.indexOfAny(u8, without_scheme, "/:") orelse without_scheme.len; return allocator.dupe(u8, without_scheme[0..host_end]); } fn displayHost(url: []const u8) []const u8 { const without_scheme = if (std.mem.startsWith(u8, url, "https://")) url["https://".len..] else if (std.mem.startsWith(u8, url, "http://")) url["http://".len..] else url; const end = std.mem.indexOfScalar(u8, without_scheme, '/') orelse without_scheme.len; return without_scheme[0..end]; } fn requestIdFromUri(request_uri: []const u8) ?[]const u8 { if (!std.mem.startsWith(u8, request_uri, request_uri_prefix)) return null; return request_uri[request_uri_prefix.len..]; } fn authorizationRedirect(allocator: std.mem.Allocator, redirect_uri: []const u8, code: []const u8, state: []const u8, response_mode: []const u8) ![]const u8 { const sep: u8 = if (std.mem.eql(u8, response_mode, "fragment")) '#' else if (std.mem.indexOfScalar(u8, redirect_uri, '?') == null) '?' else '&'; return std.fmt.allocPrint(allocator, "{s}{c}code={s}&iss={s}&state={s}", .{ redirect_uri, sep, try percentEncode(allocator, code), try percentEncode(allocator, config.publicUrl()), try percentEncode(allocator, state), }); } fn percentEncode(allocator: std.mem.Allocator, input: []const u8) ![]const u8 { var out: std.ArrayList(u8) = .empty; for (input) |c| { if ((c >= 'a' and c <= 'z') or (c >= 'A' and c <= 'Z') or (c >= '0' and c <= '9') or c == '-' or c == '_' or c == '.' or c == '~') { try out.append(allocator, c); } else { const encoded = try std.fmt.allocPrint(allocator, "%{X:0>2}", .{c}); try out.appendSlice(allocator, encoded); } } return out.toOwnedSlice(allocator); } fn now() i64 { return clock.now(); } fn jsonError(request: *http_api.Request, status: http.Status, message: []const u8) !void { var buf: [512]u8 = undefined; const body = try std.fmt.bufPrint(&buf, "{{\"error\":{f}}}", .{std.json.fmt(message, .{})}); try http_api.json(request, status, body); } fn requireBearerAccount(request: *http_api.Request, allocator: std.mem.Allocator) !auth.Account { return http_api.requireBearerAccount(request, allocator) catch |err| { switch (err) { error.AuthRequired => try http_api.xrpcError(request, .unauthorized, "AuthenticationRequired", "Authentication required"), error.InvalidToken => try http_api.xrpcError(request, .unauthorized, "InvalidToken", "Invalid token"), } return error.HandledResponse; }; } fn respondHtml(request: *http_api.Request, status: http.Status, body: []const u8) !void { const headers = [_]http.Header{ .{ .name = "content-type", .value = "text/html; charset=utf-8" }, .{ .name = "access-control-allow-origin", .value = "*" }, .{ .name = "connection", .value = "close" }, }; try http_api.respond(request, status, body, &headers); } fn htmlEscape(allocator: std.mem.Allocator, value: []const u8) ![]const u8 { var out: std.ArrayList(u8) = .empty; for (value) |c| switch (c) { '&' => try out.appendSlice(allocator, "&"), '<' => try out.appendSlice(allocator, "<"), '>' => try out.appendSlice(allocator, ">"), '"' => try out.appendSlice(allocator, """), '\'' => try out.appendSlice(allocator, "'"), else => try out.append(allocator, c), }; return out.toOwnedSlice(allocator); } const adminSessionsScript = \\const status=document.querySelector('#status'),form=document.querySelector('#admin-form'),chooser=document.querySelector('#kind-chooser'),filters=document.querySelector('#filters'),itemsRoot=document.querySelector('#items'),pagerRoot=document.querySelector('#pager'),title=document.querySelector('#list-title'),hint=document.querySelector('#list-hint'),timezone=document.querySelector('#timezone'),grantCount=document.querySelector('#grant-count'),sessionCount=document.querySelector('#session-count'); \\const pageSize=8; \\let sessions=[],grants=[],kind='grants',filter='usable',page=0; \\timezone.textContent=`times shown in ${Intl.DateTimeFormat().resolvedOptions().timeZone || 'your local timezone'}`; \\const fail=async(r,msg)=>{if(r.ok)return r.json();let body={};try{body=await r.json()}catch{}throw new Error(body.message||body.error||msg)}; \\const empty=(text)=>{itemsRoot.textContent='';pagerRoot.textContent='';const p=document.createElement('div');p.className='empty';p.textContent=text;itemsRoot.append(p)}; \\const time=(value)=>{if(!value)return{short:'never',full:'never'};const d=new Date(value);return{short:d.toLocaleString([], {month:'short',day:'numeric',hour:'2-digit',minute:'2-digit'}),full:d.toLocaleString([], {dateStyle:'full',timeStyle:'long'})}}; \\const methodLabel=(s)=>{if(s.appPasswordName)return`app password: ${s.appPasswordName}`;if(s.authMethod==='password')return'account password token';if(s.authMethod==='app_password')return'app password token';if(s.authMethod==='app_password_privileged')return'privileged app password token';return s.authMethod}; \\const shownItems=()=>{const source=kind==='grants'?grants:sessions;if(filter==='all')return source;if(filter==='usable')return source.filter(item=>item.active);return source.filter(item=>!item.active)}; \\const activeTitle=(isGrant)=>isGrant?'OAuth app session is active when its current refresh token has not been revoked. The one-hour access token may still need refresh.':'Direct API token family is active when it has not been revoked and either its access token or refresh token is still unexpired.'; \\const inactiveTitle=(isGrant)=>isGrant?'OAuth app session is inactive when its token row has been revoked, usually by refresh rotation or explicit revocation.':'Direct API token family is inactive when it is revoked or both access and refresh tokens are expired.'; \\const pill=(active,isGrant)=>{const span=document.createElement('span');span.className=`pill ${active?'usable':'ended'}`;span.textContent=active?'active':'inactive';span.title=active?activeTitle(isGrant):inactiveTitle(isGrant);return span}; \\const stamp=(label,value)=>{const div=document.createElement('div');div.className='stamp';const span=document.createElement('span');span.textContent=label;const t=time(value);const body=document.createElement('time');body.textContent=t.short;body.title=t.full;body.dateTime=value||'';div.append(span,body);return div}; \\const oauthAuthMethod=(g)=>g.authMethod==='passkey'?'passkey':g.authMethod==='password'?'password':'unknown method'; \\const card=(item)=>{const isGrant=kind==='grants';const el=document.createElement('article');el.className=`session-card ${item.active?'usable':'ended'}`;const top=document.createElement('div');top.className='card-top';const main=document.createElement('div');const who=document.createElement('div');who.className='who';who.textContent=item.handle||item.did||'unknown account';const sub=document.createElement('span');sub.className='did';sub.textContent=item.did||'';main.append(who,sub);top.append(main,pill(item.active,isGrant));const what=document.createElement('div');what.className='what';what.textContent=isGrant?item.clientId:methodLabel(item);const scope=document.createElement('span');scope.className='scope';scope.textContent=isGrant?`${oauthAuthMethod(item)} authorization ยท ${item.scope}`:(item.controllerDid?`controller ${item.controllerDid}`:'com.atproto.server.createSession token family');what.append(scope);const grid=document.createElement('div');grid.className='card-grid';if(isGrant){grid.append(stamp('authorized',item.createdAt),stamp('access token expires',item.expiresAt),stamp('revoked',item.revokedAt))}else{grid.append(stamp('created',item.createdAt),stamp('last used',item.lastUsedAt),stamp('refresh token expires',item.refreshExpiresAt))}el.append(top,what,grid);return el}; \\const renderPager=(total)=>{pagerRoot.textContent='';if(total<=pageSize)return;const pages=Math.ceil(total/pageSize);const prev=document.createElement('button');prev.type='button';prev.textContent='prev';prev.disabled=page===0;prev.onclick=()=>{page--;render()};const label=document.createElement('span');label.textContent=`${page+1}/${pages}`;const next=document.createElement('button');next.type='button';next.textContent='next';next.disabled=page>=pages-1;next.onclick=()=>{page++;render()};pagerRoot.append(prev,label,next)}; \\const renderControls=()=>{grantCount.textContent=`${grants.filter(g=>g.active).length} active / ${grants.length} total`;grantCount.title='OAuth active means the current refresh token has not been revoked; the access token may need refresh.';sessionCount.textContent=`${sessions.filter(s=>s.active).length} active / ${sessions.length} total`;sessionCount.title='Direct API active means the token family has not been revoked and either access or refresh token is still unexpired.';for(const b of chooser.querySelectorAll('button'))b.setAttribute('aria-pressed',String(b.dataset.kind===kind));for(const b of filters.querySelectorAll('button'))b.setAttribute('aria-pressed',String(b.dataset.filter===filter))}; \\const render=()=>{renderControls();const isGrant=kind==='grants';title.textContent=isGrant?'OAuth app sessions':'direct API tokens';hint.textContent=isGrant?'App sign-ins issued by the OAuth flow. Active means the current refresh token has not been revoked; the access token may still need refresh. New rows show passkey or password authorization; older rows may show unknown method.':'Token families from com.atproto.server.createSession. Active means the family has not been revoked and still has an unexpired access or refresh token. These are separate from OAuth app sign-ins.';const items=shownItems();if(!items.length)return empty(filter==='usable'?'No active rows.':filter==='ended'?'No inactive rows.':'No entries.');const maxPage=Math.max(0,Math.ceil(items.length/pageSize)-1);if(page>maxPage)page=maxPage;itemsRoot.textContent='';const list=document.createElement('div');list.className='list';for(const item of items.slice(page*pageSize,page*pageSize+pageSize))list.append(card(item));itemsRoot.append(list);renderPager(items.length)}; \\chooser.addEventListener('click',(e)=>{const b=e.target.closest('button[data-kind]');if(!b)return;kind=b.dataset.kind;page=0;render()}); \\filters.addEventListener('click',(e)=>{const b=e.target.closest('button[data-filter]');if(!b)return;filter=b.dataset.filter;page=0;render()}); \\form.addEventListener('submit',async(e)=>{e.preventDefault();status.className='status';try{status.textContent='loading token rows...';const token=form.token.value.trim();const data=await fail(await fetch('/xrpc/dev.zat.admin.listSessions?active=false&limit=500',{headers:{authorization:`Bearer ${token}`}}),'failed to load token rows');sessions=data.sessions||[];grants=data.oauthGrants||[];chooser.hidden=false;filters.hidden=false;kind='grants';filter=grants.some(g=>g.active)?'usable':grants.length?'ended':'all';page=0;render();status.textContent=''}catch(err){status.className='status error';status.textContent=err.message||String(err)}}); ; test "validates webauthn credential key through tangled dependency" { const key = try webauthn.base64url.decodeAlloc(std.testing.allocator, "pQECAyYgASFYIDNDxl6djmZTEhKfw1B5jiSdcFUsTKuyPpks-4jTpA5aIlggF5oAEvUgwjYE6o0sPzL6G27d72m3lM2-yPAMOajmYoE"); defer std.testing.allocator.free(key); try (CredentialKey{ .bytes = key }).validate(); } test "verifies real webauthn assertion signature through zds adapter" { const allocator = std.testing.allocator; const key = try webauthn.base64url.decodeAlloc(allocator, "pQECAyYgASFYIDNDxl6djmZTEhKfw1B5jiSdcFUsTKuyPpks-4jTpA5aIlggF5oAEvUgwjYE6o0sPzL6G27d72m3lM2-yPAMOajmYoE"); defer allocator.free(key); const auth_data = try webauthn.base64url.decodeAlloc(allocator, "SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MdAAAAAA"); defer allocator.free(auth_data); const client_data_json = try webauthn.base64url.decodeAlloc(allocator, "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoibGgwR1c2OEZKZW03NWxBNV9sRTZKTmU4dlo2ODdsdmhaQmtrY0RzUVB5byIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODA4MCIsImNyb3NzT3JpZ2luIjpmYWxzZX0"); defer allocator.free(client_data_json); const signature = try webauthn.base64url.decodeAlloc(allocator, "MEYCIQDQ-pXZQT9yjPsXT_m47W-iTFAIRgBVOCBhwl6kU--0RwIhAKcJJhxipw6tsIR0ULRgvQAhTaeIXk_V29wKOqbfP1oL"); defer allocator.free(signature); try verifyAssertionSignature(key, signature, auth_data, client_data_json, allocator); }