A replica of Keytrace
4
fork

Configure Feed

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

Track claims via linkedclaim records on keybase' atproto account

orta 369d2e7e 93164974

+98
+19
apps/keytrace.dev/server/api/claims/[rkey].patch.ts
··· 8 8 import { COLLECTION_NSID, createClaim, verifyClaim, ClaimStatus } from "@keytrace/runner"; 9 9 import { getSessionAgent } from "~/server/utils/session"; 10 10 import { createStatusAttestation } from "~/server/utils/attestation"; 11 + import { putLinkedClaim } from "~/server/utils/linkedclaims"; 11 12 12 13 export default defineEventHandler(async (event) => { 13 14 const { did, agent } = await getSessionAgent(event); ··· 104 105 rkey, 105 106 record, 106 107 }); 108 + 109 + // Update LinkedClaim record to reflect new status (best-effort) 110 + try { 111 + const confidence = record.status === "verified" ? 1.0 : 0.0; 112 + await putLinkedClaim(agent, { 113 + did, 114 + rkey, 115 + ketyraceAtUri: res.data.uri, 116 + subjectUri: record.identity?.profileUrl ?? claimUri, 117 + providerId: record.type, 118 + subjectLabel: record.identity?.subject ?? record.type, 119 + confidence, 120 + statusAt, 121 + createdAt: record.createdAt ?? now, 122 + }); 123 + } catch (error) { 124 + console.error("[claims] Failed to update LinkedClaim record:", error); 125 + } 107 126 108 127 return { 109 128 uri: res.data.uri,
+19
apps/keytrace.dev/server/api/claims/index.post.ts
··· 9 9 import { getSessionAgent } from "~/server/utils/session"; 10 10 import { createAttestation, createStatusAttestation } from "~/server/utils/attestation"; 11 11 import { addRecentClaim } from "~/server/utils/recent-claims"; 12 + import { putLinkedClaim } from "~/server/utils/linkedclaims"; 12 13 13 14 export default defineEventHandler(async (event) => { 14 15 const { did, agent } = await getSessionAgent(event); ··· 112 113 }); 113 114 114 115 console.log(`[claims] Success: uri=${result.data.uri} cid=${result.data.cid}`); 116 + 117 + // Create LinkedClaim record (best-effort, don't fail the request) 118 + try { 119 + const rkey = result.data.uri.split("/").pop()!; 120 + await putLinkedClaim(agent, { 121 + did, 122 + rkey, 123 + keytraceAtUri: result.data.uri, 124 + subjectUri: identity.profileUrl ?? body.claimUri, 125 + providerId: provider.id, 126 + subjectLabel: subject, 127 + confidence: 1.0, 128 + statusAt: now, 129 + createdAt: now, 130 + }); 131 + } catch (error) { 132 + console.error("[claims] Failed to create LinkedClaim record:", error); 133 + } 115 134 116 135 // Add to recent claims feed (best-effort, don't fail the request) 117 136 try {
+60
apps/keytrace.dev/server/utils/linkedclaims.ts
··· 1 + /** 2 + * LinkedClaims integration utilities. 3 + * 4 + * Creates and updates com.linkedclaims.claim records alongside keytrace claims. 5 + * The linkedclaim uses the same rkey as the corresponding keytrace claim so we 6 + * can always derive its AT-URI without storing a back-reference. 7 + * 8 + * https://identity.foundation/labs-linkedclaims/ 9 + */ 10 + 11 + const LINKED_CLAIMS_NSID = "com.linkedclaims.claim"; 12 + 13 + // Map keytrace provider IDs to LinkedClaims howKnown values; defaults to WEB_DOCUMENT 14 + const HOW_KNOWN: Record<string, string> = { 15 + pgp: "SIGNED_DOCUMENT", 16 + }; 17 + 18 + interface PutLinkedClaimOptions { 19 + did: string; 20 + rkey: string; 21 + /** AT-URI of the keytrace dev.keytrace.claim record */ 22 + keytraceAtUri: string; 23 + /** External identity URI (e.g. the GitHub profile URL) */ 24 + subjectUri: string; 25 + providerId: string; 26 + /** Human-readable identity subject (e.g. "orta" or "@orta") */ 27 + subjectLabel: string; 28 + confidence: number; 29 + /** ISO datetime of when the claim was verified / failed / retracted */ 30 + statusAt: string; 31 + createdAt: string; 32 + } 33 + 34 + export async function putLinkedClaim(agent: any, opts: PutLinkedClaimOptions) { 35 + const { did, rkey, keytraceAtUri, subjectUri, providerId, subjectLabel, confidence, statusAt, createdAt } = opts; 36 + 37 + const howKnown = HOW_KNOWN[providerId] ?? "WEB_DOCUMENT"; 38 + 39 + const record: Record<string, unknown> = { 40 + $type: LINKED_CLAIMS_NSID, 41 + subject: subjectUri, 42 + claimType: "identity", 43 + statement: `Verified ${providerId} identity: ${subjectLabel}`, 44 + confidence, 45 + source: { 46 + uri: keytraceAtUri, 47 + howKnown, 48 + dateObserved: statusAt, 49 + }, 50 + effectiveDate: statusAt, 51 + createdAt, 52 + }; 53 + 54 + await agent.com.atproto.repo.putRecord({ 55 + repo: did, 56 + collection: LINKED_CLAIMS_NSID, 57 + rkey, 58 + record, 59 + }); 60 + }