~james/core

core/web/src/lib/auth/agent.ts -rw-r--r-- 1.9 KiB
452f17b7 — james chore(import): snapshot james.dad.town/core at f70c71c7696a 15 hours ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import { Client, ok } from "@atcute/client";
import { mainSchema as getServiceAuthSchema } from "@atcute/atproto/types/server/getServiceAuth";
import { isDid, type AtprotoAudience } from "@atcute/lexicons/syntax";
import { didOf, type Did, type Nsid } from "#lib/api/syntax.ts";
import type { OAuthUserAgent } from "@atcute/oauth-browser-client";

export const createClient = (agent: OAuthUserAgent, proxy?: AtprotoAudience): Client =>
	new Client({ handler: agent, proxy: proxy ?? null });

export interface ServiceAuthOptions {
	// service did for the jwt aud claim
	aud: Did;
	lxm: Nsid;
	// clamped to at least 60s, matching appview's service client.
	expiresInSeconds?: number;
	signal?: AbortSignal;
}

// pds refuses did:web:https%3A//host as an invalid did
const hostOf = (hostOrUrl: string): string =>
	/^[a-z][a-z0-9+.-]*:\/\//i.test(hostOrUrl)
		? new URL(hostOrUrl).host
		: hostOrUrl.replace(/\/+$/, "");

// did:web service id, with ports percent-encoded like serviceauth didweb.
export const serviceDidForHost = (hostOrUrl: string): Did =>
	didOf(`did:web:${hostOf(hostOrUrl).replace(/:/g, "%3A")}`);

export const hostForServiceDid = (did: string): string | null => {
	if (!isDid(did) || !did.startsWith("did:web:")) return null;
	const encoded = did.slice("did:web:".length);
	if (/%(?![0-9A-Fa-f]{2})/.test(encoded)) return null;
	const host = encoded.replace(/%3A/gi, ":").replace(/\.+$/, "").toLowerCase();
	return host || null;
};

// mint a service-auth jwt for knot/spindle xrpc calls.
export const mintServiceAuth = async (
	agent: OAuthUserAgent,
	{ aud, lxm, expiresInSeconds = 60, signal }: ServiceAuthOptions
): Promise<string> => {
	const client = createClient(agent);
	const exp = Math.floor(Date.now() / 1000) + Math.max(expiresInSeconds, 60);
	const { token } = await ok(
		client.call(getServiceAuthSchema, {
			params: { aud, exp, lxm },
			signal
		})
	);
	return token;
};