[READ-ONLY] Mirror of https://github.com/flo-bit/contrail. atproto backend in a bottle flo-bit.dev/contrail/
0

Configure Feed

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

switch permission set to authFull

Florian (Apr 29, 2026, 6:48 PM +0200) 5ff7536e 0e6ba77f

+48 -9
+17 -5
packages/lexicons/src/generate.ts
··· 10 10 import type { ContrailConfig } from "@atmo-dev/contrail"; 11 11 12 12 /** Return the sorted list of XRPC method NSIDs (queries + procedures) in a 13 - * generated lexicon set — the same list that ends up in `<ns>.permissionSet`'s 13 + * generated lexicon set — the same list that ends up in `<ns>.authFull`'s 14 14 * `lxm`. Use when the permission set isn't published yet and you need the 15 15 * scoped method list inline (e.g. in an OAuth client config). 16 16 * ··· 956 956 methodNsids.sort(); 957 957 958 958 const psConfig = config.permissionSet ?? {}; 959 + const nsPrefix = `${ns}.`; 960 + 961 + // Auto-include any configured collections that live under the namespace. 962 + // Cross-namespace collections (e.g. `app.bsky.*`) can't appear here — 963 + // permission-set lexicons can only reference NSIDs in their own namespace — 964 + // so callers must declare those as separate scopes in the OAuth client config. 965 + const autoCollections = Object.values(config.collections) 966 + .map((c) => c.collection) 967 + .filter((nsid) => nsid === ns || nsid.startsWith(nsPrefix)) 968 + .sort(); 959 969 960 970 // Permission-set lexicons can only reference NSIDs under their own namespace. 961 971 // Validate `additional` entries before we emit and produce an invalid schema. 962 - const nsPrefix = `${ns}.`; 963 972 for (const [i, perm] of (psConfig.additional ?? []).entries()) { 964 973 const p = perm as { resource?: string; lxm?: string[]; collection?: string[] }; 965 974 const offending: string[] = []; ··· 979 988 } 980 989 } 981 990 982 - writeLexicon(`${ns}.permissionSet`, { 991 + writeLexicon(`${ns}.authFull`, { 983 992 lexicon: 1, 984 - id: `${ns}.permissionSet`, 993 + id: `${ns}.authFull`, 985 994 defs: { 986 995 main: { 987 996 type: "permission-set", 988 997 title: psConfig.title ?? ns, 989 998 description: 990 - psConfig.description ?? `All XRPC methods exposed by the ${ns} service.`, 999 + psConfig.description ?? `Full access to the ${ns} service`, 991 1000 permissions: [ 992 1001 { 993 1002 type: "permission", ··· 1000 1009 aud: "*", 1001 1010 lxm: methodNsids, 1002 1011 }, 1012 + ...(autoCollections.length > 0 1013 + ? [{ type: "permission" as const, resource: "repo" as const, collection: autoCollections }] 1014 + : []), 1003 1015 ...(psConfig.additional ?? []), 1004 1016 ], 1005 1017 },
+30 -3
packages/lexicons/tests/generate.test.ts
··· 232 232 expect(methods).toContain("test.app.post.getRecord"); 233 233 expect(methods).toContain("test.app.getProfile"); 234 234 // Does not include non-method defs (e.g. `defs`, records, permission-set). 235 - expect(methods).not.toContain("test.app.permissionSet"); 235 + expect(methods).not.toContain("test.app.authFull"); 236 236 }); 237 237 238 - it("listXrpcMethods matches the permissionSet lxm list for the same config", () => { 238 + it("listXrpcMethods matches the authFull lxm list for the same config", () => { 239 239 const methods = listXrpcMethods(BASIC_CONFIG, { rootDir: ROOT_DIR, lexiconDirs: [] }); 240 240 const lexicons = generate(BASIC_CONFIG); 241 - const ps = (lexicons["test.app.permissionSet"] as any).defs.main.permissions[0]; 241 + const ps = (lexicons["test.app.authFull"] as any).defs.main.permissions[0]; 242 242 expect(ps.lxm).toEqual(methods); 243 + }); 244 + 245 + it("authFull auto-includes same-namespace collections in a repo permission", () => { 246 + const config: ContrailConfig = { 247 + namespace: "test.app", 248 + collections: { 249 + local: { collection: "test.app.thing" }, 250 + external: { collection: "xyz.other.thing" }, 251 + }, 252 + }; 253 + const lexicons = generate(config); 254 + const perms = (lexicons["test.app.authFull"] as any).defs.main.permissions; 255 + const repoEntry = perms.find((p: any) => p.resource === "repo"); 256 + expect(repoEntry).toBeDefined(); 257 + expect(repoEntry.collection).toEqual(["test.app.thing"]); 258 + }); 259 + 260 + it("authFull omits the repo permission entry when no collections match the namespace", () => { 261 + const config: ContrailConfig = { 262 + namespace: "test.app", 263 + collections: { 264 + external: { collection: "xyz.other.thing" }, 265 + }, 266 + }; 267 + const lexicons = generate(config); 268 + const perms = (lexicons["test.app.authFull"] as any).defs.main.permissions; 269 + expect(perms.find((p: any) => p.resource === "repo")).toBeUndefined(); 243 270 }); 244 271 245 272 it("includes realtime + community + spaces endpoints when those modules are enabled", () => {
+1 -1
packages/contrail/src/core/types.ts
··· 166 166 * and hydrates `record.labels` onto `listRecords` / `getRecord` / profile 167 167 * responses gated by the caller's `atproto-accept-labelers` header. */ 168 168 labels?: import("./labels/types").LabelsConfig; 169 - /** Customize the auto-generated `<namespace>.permissionSet` lexicon. */ 169 + /** Customize the auto-generated `<namespace>.authFull` lexicon. */ 170 170 permissionSet?: PermissionSetConfig; 171 171 } 172 172