~james/core

core/web/src/lib/oauth-client-metadata.test.ts -rw-r--r-- 2.6 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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
import { readdirSync, readFileSync } from "node:fs";
import path from "node:path";
import type { Nsid } from "@atcute/lexicons/syntax";
import { describe, expect, it } from "vitest";
import { missingPermissions, type Permission } from "#lib/auth/scopes.ts";
import metadata, { loopbackClientId, loopbackScope } from "#lib/oauth-client-metadata.ts";

const KNOT_WRITE_LXMS = [
	"com.atproto.repo.createRecord",
	"com.atproto.repo.putRecord",
	"com.atproto.repo.deleteRecord",
	"com.atproto.repo.uploadBlob",
	"org.tangled.repo.deleteRepo",
	"org.tangled.review.submitPatch"
] as const;

describe("oauth client scope", () => {
	it("grants any-audience RPC scopes for every lxm used in knot writes", () => {
		const granted = new Set(metadata.scope.split(" "));
		for (const lxm of KNOT_WRITE_LXMS) {
			expect(
				granted.has(`rpc:${lxm}?aud=*`),
				`The grant must include rpc:${lxm}?aud=* or the PDS rejects tokens minted for it.`
			).toBe(true);
		}
	});

	it("grants repo scope over org.tangled.graph.attestation", () => {
		expect(metadata.scope.split(" ")).toContain("repo:org.tangled.graph.attestation");
	});

	it("requests no sh.tangled scopes", () => {
		const legacy = metadata.scope.split(" ").filter((scope) => scope.includes("sh.tangled."));
		expect(legacy).toEqual([]);
	});
});

const lexicons = path.resolve(import.meta.dirname, "../../../lexicons/org/tangled");

const recordCollections = (dir: string): string[] =>
	readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
		const file = path.join(dir, entry.name);
		if (entry.isDirectory()) return recordCollections(file);
		const lexicon = JSON.parse(readFileSync(file, "utf8")) as {
			id: string;
			defs: { main?: { type?: string } };
		};
		return lexicon.defs.main?.type === "record" ? [lexicon.id] : [];
	});

describe("liar migration scope", () => {
	it("grants repo scope over every org.tangled record, since the import writes whatever the repo holds", () => {
		const wanted = recordCollections(lexicons).map((collection): Permission => ({
			resource: "repo",
			collection: collection as Nsid,
			actions: ["create"]
		}));
		expect(wanted.length).toBeGreaterThan(20);
		expect(missingPermissions(metadata.scope, wanted)).toEqual([]);
	});
});

describe("loopbackClientId", () => {
	it("keeps authorize URLs within chromium's referrer length limit", () => {
		const url = new URL("https://tngl.sh/oauth/authorize");
		url.searchParams.set(
			"client_id",
			loopbackClientId("http://127.0.0.1:5173/oauth/callback", loopbackScope)
		);
		url.searchParams.set(
			"request_uri",
			`urn:ietf:params:oauth:request_uri:req-${"0".repeat(32)}`
		);
		expect(url.href.length).toBeLessThanOrEqual(4096);
	});
});