@immediately-run/preauth-core 0.1.8 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -37,6 +37,32 @@ export interface ParsedGrantKey {
37
37
  * (first + last), so the cascade fails safe (child self-revokes) rather than
38
38
  * crashing. */
39
39
  export declare const parseGrantKey: (key: string) => ParsedGrantKey;
40
+ /** The doc-id delimiter between a qualifying principal and the spaceId. Safe: a
41
+ * named principal is lowercase-dotted/hyphenated (CA-3 reserves `~`) and a
42
+ * Firestore spaceId is alphanumeric, so `~` appears in NEITHER — a single,
43
+ * unambiguous split point. */
44
+ export declare const GRANT_DOCID_DELIM = "~";
45
+ /** Build a space-grant doc-id (design 05a §3.1 step 2). Pass the QUALIFYING named
46
+ * principal to get `${principal}~${spaceId}`; pass `undefined` (stage / legacy /
47
+ * no principal) for the bare `spaceId`. The caller resolves "does this principal
48
+ * qualify" (site-main maps stage/legacy → undefined) so this stays a pure string
49
+ * builder with no sentinel knowledge. */
50
+ export declare const grantDocId: (spaceId: string, qualifyingPrincipal?: string) => string;
51
+ /** A parsed grant doc-id — the §3.5 reader-parse discipline. `principal` is set
52
+ * only for a QUALIFIED (`${principal}~${spaceId}`) id; a bare id (a stage/legacy
53
+ * grant) yields `{ spaceId }` with `principal` undefined. */
54
+ export interface ParsedGrantDocId {
55
+ /** The qualifying principal, or undefined for a bare (stage/legacy) doc-id. */
56
+ principal?: string;
57
+ spaceId: string;
58
+ }
59
+ /** Parse a space-grant doc-id back into `{ principal?, spaceId }` — the §3.5
60
+ * reader-parse discipline every app-space-grant collection reader routes `d.id`
61
+ * through so it never mistakes `${principal}~${spaceId}` for a bare spaceId (which
62
+ * would corrupt the derived `mountId` and leak grants across principals). Splits
63
+ * on the FIRST delimiter; a named principal never contains `~`, so this recovers
64
+ * the exact principal + spaceId. A bare id (no delimiter) ⇒ `{ spaceId }`. */
65
+ export declare const parseGrantDocId: (docId: string) => ParsedGrantDocId;
40
66
  /** Durable elevated/app-scoped grants expire after 90 days WITHOUT USE; first
41
67
  * use after expiry re-prompts. Baseline needs no grant record, so this never
42
68
  * touches it. */
@@ -55,7 +81,12 @@ export declare const spacePath: (spaceId: string) => DocPath;
55
81
  export declare const memberPath: (spaceId: string, grantee: string) => DocPath;
56
82
  export declare const userSpacePath: (uid: string, spaceId: string) => DocPath;
57
83
  export declare const appKeyPath: (uid: string, appKey: string) => DocPath;
58
- export declare const appSpacePath: (uid: string, appKey: string, spaceId: string) => DocPath;
84
+ /** `user-app-spaces/{uid}/apps/{appKey}/spaces/{docId}` the durable §8.7 grant
85
+ * doc. R3-98 S5: the doc-id is principal-qualified — pass the QUALIFYING named
86
+ * principal for `${principal}~${spaceId}`, or omit it (stage / legacy) for the
87
+ * bare `spaceId`. Backward-compatible: a 3-arg call (no principal) yields exactly
88
+ * the pre-S5 path, so the backend/CLI stage mint is byte-identical. */
89
+ export declare const appSpacePath: (uid: string, appKey: string, spaceId: string, qualifyingPrincipal?: string) => DocPath;
59
90
  export declare const userCountPath: (uid: string) => DocPath;
60
91
  export declare const appCountPath: (uid: string, appKey: string) => DocPath;
61
92
  /** `spaces/{spaceId}` — the root doc (written WITHOUT merge). */
package/dist/docLayout.js CHANGED
@@ -16,7 +16,7 @@
16
16
  // `.set()`/`.update()` is the only thing each adapter does itself. Drift is then
17
17
  // impossible without editing a helper both consume.
18
18
  Object.defineProperty(exports, "__esModule", { value: true });
19
- exports.appCapabilitiesGrantFields = exports.mergeCapabilities = exports.netFetchGrantFields = exports.mergeNetFetchHosts = exports.appSpaceGrantFields = exports.appKeyTouchFields = exports.appCountFields = exports.userCountFields = exports.ownerUserSpaceFields = exports.ownerMemberFields = exports.spaceDocFields = exports.appCountPath = exports.userCountPath = exports.appSpacePath = exports.appKeyPath = exports.userSpacePath = exports.memberPath = exports.spacePath = exports.defined = exports.granteeId = exports.GRANT_EXPIRY_MS = exports.parseGrantKey = exports.grantKeyWithPrincipal = exports.grantKey = void 0;
19
+ exports.appCapabilitiesGrantFields = exports.mergeCapabilities = exports.netFetchGrantFields = exports.mergeNetFetchHosts = exports.appSpaceGrantFields = exports.appKeyTouchFields = exports.appCountFields = exports.userCountFields = exports.ownerUserSpaceFields = exports.ownerMemberFields = exports.spaceDocFields = exports.appCountPath = exports.userCountPath = exports.appSpacePath = exports.appKeyPath = exports.userSpacePath = exports.memberPath = exports.spacePath = exports.defined = exports.granteeId = exports.GRANT_EXPIRY_MS = exports.parseGrantDocId = exports.grantDocId = exports.GRANT_DOCID_DELIM = exports.parseGrantKey = exports.grantKeyWithPrincipal = exports.grantKey = void 0;
20
20
  /** Stable per-user identifier for a grant `(appKey, spaceId)`, used as the value
21
21
  * of a delegated grant's `parentGrantId`. `::` is delimiter-safe: `appKey` uses
22
22
  * `__` separators and a Firestore `spaceId` is alphanumeric. */
@@ -45,6 +45,41 @@ const parseGrantKey = (key) => {
45
45
  return { appKey: parts[0], spaceId: parts[parts.length - 1] };
46
46
  };
47
47
  exports.parseGrantKey = parseGrantKey;
48
+ // --- R3-98 S5 — the principal-qualified space-grant doc-id (design 05a §3.1/§3.5) --
49
+ //
50
+ // A space grant's Firestore doc-id encodes the named principal it was minted
51
+ // under, so two principals granting the SAME space live at DIFFERENT docs and are
52
+ // invisible to each other (structural disjointness). The rule (design 05a §3.1
53
+ // step 2): a **qualifying** (real, named) principal → `${principal}~${spaceId}`;
54
+ // the **stage** principal, a **legacy** (no-principal) grant, or none → the bare
55
+ // `spaceId`, so no existing/stage doc ever moves. This module is GRAMMAR ONLY: the
56
+ // caller decides which principals qualify (site-main owns the stage/legacy
57
+ // sentinels — a principal it treats as non-qualifying is passed as `undefined`).
58
+ /** The doc-id delimiter between a qualifying principal and the spaceId. Safe: a
59
+ * named principal is lowercase-dotted/hyphenated (CA-3 reserves `~`) and a
60
+ * Firestore spaceId is alphanumeric, so `~` appears in NEITHER — a single,
61
+ * unambiguous split point. */
62
+ exports.GRANT_DOCID_DELIM = '~';
63
+ /** Build a space-grant doc-id (design 05a §3.1 step 2). Pass the QUALIFYING named
64
+ * principal to get `${principal}~${spaceId}`; pass `undefined` (stage / legacy /
65
+ * no principal) for the bare `spaceId`. The caller resolves "does this principal
66
+ * qualify" (site-main maps stage/legacy → undefined) so this stays a pure string
67
+ * builder with no sentinel knowledge. */
68
+ const grantDocId = (spaceId, qualifyingPrincipal) => qualifyingPrincipal ? `${qualifyingPrincipal}${exports.GRANT_DOCID_DELIM}${spaceId}` : spaceId;
69
+ exports.grantDocId = grantDocId;
70
+ /** Parse a space-grant doc-id back into `{ principal?, spaceId }` — the §3.5
71
+ * reader-parse discipline every app-space-grant collection reader routes `d.id`
72
+ * through so it never mistakes `${principal}~${spaceId}` for a bare spaceId (which
73
+ * would corrupt the derived `mountId` and leak grants across principals). Splits
74
+ * on the FIRST delimiter; a named principal never contains `~`, so this recovers
75
+ * the exact principal + spaceId. A bare id (no delimiter) ⇒ `{ spaceId }`. */
76
+ const parseGrantDocId = (docId) => {
77
+ const i = docId.indexOf(exports.GRANT_DOCID_DELIM);
78
+ return i === -1
79
+ ? { spaceId: docId }
80
+ : { principal: docId.slice(0, i), spaceId: docId.slice(i + 1) };
81
+ };
82
+ exports.parseGrantDocId = parseGrantDocId;
48
83
  /** Durable elevated/app-scoped grants expire after 90 days WITHOUT USE; first
49
84
  * use after expiry re-prompts. Baseline needs no grant record, so this never
50
85
  * touches it. */
@@ -85,13 +120,18 @@ const appKeyPath = (uid, appKey) => [
85
120
  appKey,
86
121
  ];
87
122
  exports.appKeyPath = appKeyPath;
88
- const appSpacePath = (uid, appKey, spaceId) => [
123
+ /** `user-app-spaces/{uid}/apps/{appKey}/spaces/{docId}` the durable §8.7 grant
124
+ * doc. R3-98 S5: the doc-id is principal-qualified — pass the QUALIFYING named
125
+ * principal for `${principal}~${spaceId}`, or omit it (stage / legacy) for the
126
+ * bare `spaceId`. Backward-compatible: a 3-arg call (no principal) yields exactly
127
+ * the pre-S5 path, so the backend/CLI stage mint is byte-identical. */
128
+ const appSpacePath = (uid, appKey, spaceId, qualifyingPrincipal) => [
89
129
  'user-app-spaces',
90
130
  uid,
91
131
  'apps',
92
132
  appKey,
93
133
  'spaces',
94
- spaceId,
134
+ (0, exports.grantDocId)(spaceId, qualifyingPrincipal),
95
135
  ];
96
136
  exports.appSpacePath = appSpacePath;
97
137
  const userCountPath = (uid) => ['space-counts', uid];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@immediately-run/preauth-core",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "The shared §8.9 pre-auth target check + the single grant-mint path (mintConsentedGrants) + the capability vocabulary + the byte-faithful grant/space/net-fetch document layout. Consumed by site-main (browser Firestore) and the backend (admin Firestore) so there is ONE gate, ONE mint path, ONE wire layout.",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {