verifyServiceAuth
uses Buffer
Check a service-auth token: signed by its issuer's repository key (#atproto, from their DID document), for one
of audiences (this server, as serviceDid names it), for the method lxm, and not expired. The DID it's
from; else it throws Refused, saying why.
import { isDid, Refused, SIGNING_KEYS, signingKey } from "#codebase/tcmts/atproto"; export async function verifyServiceAuth( jwt: string, o: { audiences: string[]; lxm: string }, ): Promise<string> { const [h, p, sig, extra] = jwt.split("."); if (!h || !p || !sig || extra !== undefined) throw new Refused("that isn't a service-auth token"); let header: { alg?: unknown }; let claims: { iss?: unknown; aud?: unknown; lxm?: unknown; exp?: unknown }; try { header = JSON.parse(Buffer.from(h, "base64url").toString("utf8")); claims = JSON.parse(Buffer.from(p, "base64url").toString("utf8")); } catch { throw new Refused("that isn't a service-auth token"); } if (header.alg !== "ES256" && header.alg !== "ES256K") throw new Refused(`a service-auth token signed with ${String(header.alg)} isn't taken`); const iss = claims.iss; if (typeof iss !== "string" || !isDid(iss)) throw new Refused("the token doesn't say whose it is"); if (typeof claims.aud !== "string" || !o.audiences.includes(claims.aud)) throw new Refused( `the token is for ${String(claims.aud)}, not this server (${o.audiences.join(", ") || "it has no URL: start it with --url"})`, ); if (claims.lxm !== o.lxm) throw new Refused(`the token is for ${String(claims.lxm)}, not ${o.lxm}`); if (typeof claims.exp !== "number" || claims.exp * 1000 < Date.now()) throw new Refused("the token has expired"); const data = new Uint8Array(Buffer.from(`${h}.${p}`, "utf8")); const signature = new Uint8Array(Buffer.from(sig, "base64url")); const keyOf = async (fresh: boolean) => { const kept = SIGNING_KEYS.get(iss); if (kept && !fresh && Date.now() - kept.at < 5 * 60_000) return kept.key; const { key } = await signingKey(iss); SIGNING_KEYS.set(iss, { key, at: Date.now() }); return key; }; const checks = async (fresh: boolean) => (await keyOf(fresh)).verify(signature, data); if (!(await checks(false)) && !(await checks(true))) throw new Refused(`the token isn't signed by ${iss}'s key`); return iss; }