@immediately-run/preauth-core 0.1.7 → 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.
- package/dist/docLayout.d.ts +57 -2
- package/dist/docLayout.js +71 -3
- package/dist/port.d.ts +5 -0
- package/package.json +1 -1
package/dist/docLayout.d.ts
CHANGED
|
@@ -13,6 +13,56 @@ export interface MintSentinels {
|
|
|
13
13
|
* of a delegated grant's `parentGrantId`. `::` is delimiter-safe: `appKey` uses
|
|
14
14
|
* `__` separators and a Firestore `spaceId` is alphanumeric. */
|
|
15
15
|
export declare const grantKey: (appKey: string, spaceId: string) => string;
|
|
16
|
+
/** R3-98 S4 — the principal-aware grant key `(appKey, principal, spaceId)` (design
|
|
17
|
+
* 05a §3.1/§3.2). Additive: {@link grantKey} is retained for the legacy 2-field
|
|
18
|
+
* form. `::` stays delimiter-safe — `appKey` uses `__`, a `spaceId` is alphanumeric,
|
|
19
|
+
* and a named principal is lowercase-dotted/hyphenated (CA-3), none containing `::`. */
|
|
20
|
+
export declare const grantKeyWithPrincipal: (appKey: string, principal: string, spaceId: string) => string;
|
|
21
|
+
/** A parsed `parentGrantId` — the pieces the §8.15 revoke cascade reconstructs a
|
|
22
|
+
* grant doc path from. `principal` is present only for a 3-field (S4+) key. */
|
|
23
|
+
export interface ParsedGrantKey {
|
|
24
|
+
appKey: string;
|
|
25
|
+
spaceId: string;
|
|
26
|
+
/** The named principal for a 3-field {@link grantKeyWithPrincipal} key; undefined
|
|
27
|
+
* for a legacy 2-field {@link grantKey} (the caller defaults to its grandfather
|
|
28
|
+
* sentinel). */
|
|
29
|
+
principal?: string;
|
|
30
|
+
}
|
|
31
|
+
/** R3-98 S4 — ARITY-DETECTING parse of a grant key (design 05a §3.1 step 3 /
|
|
32
|
+
* MEDIUM-6). A 3-field key is `appKey::principal::spaceId`; a legacy 2-field key is
|
|
33
|
+
* `appKey::spaceId` (principal undefined). This lets the revoke cascade keep
|
|
34
|
+
* resolving BOTH legacy and keyed `parentGrantId`s after the re-key — a positional
|
|
35
|
+
* `split('::')` would mis-assign a legacy key's `spaceId` to `principal`. A
|
|
36
|
+
* malformed key (≠2/≠3 segments) degrades to best-effort `appKey::…::spaceId`
|
|
37
|
+
* (first + last), so the cascade fails safe (child self-revokes) rather than
|
|
38
|
+
* crashing. */
|
|
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;
|
|
16
66
|
/** Durable elevated/app-scoped grants expire after 90 days WITHOUT USE; first
|
|
17
67
|
* use after expiry re-prompts. Baseline needs no grant record, so this never
|
|
18
68
|
* touches it. */
|
|
@@ -31,7 +81,12 @@ export declare const spacePath: (spaceId: string) => DocPath;
|
|
|
31
81
|
export declare const memberPath: (spaceId: string, grantee: string) => DocPath;
|
|
32
82
|
export declare const userSpacePath: (uid: string, spaceId: string) => DocPath;
|
|
33
83
|
export declare const appKeyPath: (uid: string, appKey: string) => DocPath;
|
|
34
|
-
|
|
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;
|
|
35
90
|
export declare const userCountPath: (uid: string) => DocPath;
|
|
36
91
|
export declare const appCountPath: (uid: string, appKey: string) => DocPath;
|
|
37
92
|
/** `spaces/{spaceId}` — the root doc (written WITHOUT merge). */
|
|
@@ -50,7 +105,7 @@ export declare const appKeyTouchFields: (s: MintSentinels) => Record<string, unk
|
|
|
50
105
|
/** `user-app-spaces/{uid}/apps/{appKey}/spaces/{spaceId}` — the durable §8.7
|
|
51
106
|
* grant doc (merge). `mintPath` defaults to `interactive`; `grantedAt`/`lastUsedAt`
|
|
52
107
|
* drive the §8.15 90-day-unused expiry. */
|
|
53
|
-
export declare const appSpaceGrantFields: (params: Pick<GrantSpaceParams, "name" | "subtree" | "mode" | "rules" | "declaredUri" | "mintPath" | "parentGrantId">, s: MintSentinels) => Record<string, unknown>;
|
|
108
|
+
export declare const appSpaceGrantFields: (params: Pick<GrantSpaceParams, "name" | "subtree" | "mode" | "rules" | "declaredUri" | "mintPath" | "parentGrantId" | "principal">, s: MintSentinels) => Record<string, unknown>;
|
|
54
109
|
/** Union net:fetch host rules by origin (incoming wins) — the "consent
|
|
55
110
|
* accumulates" merge both adapters apply before writing the host set. */
|
|
56
111
|
export declare const mergeNetFetchHosts: (existing: readonly NetFetchHost[], incoming: readonly NetFetchHost[]) => NetFetchHost[];
|
package/dist/docLayout.js
CHANGED
|
@@ -16,12 +16,70 @@
|
|
|
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.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. */
|
|
23
23
|
const grantKey = (appKey, spaceId) => `${appKey}::${spaceId}`;
|
|
24
24
|
exports.grantKey = grantKey;
|
|
25
|
+
/** R3-98 S4 — the principal-aware grant key `(appKey, principal, spaceId)` (design
|
|
26
|
+
* 05a §3.1/§3.2). Additive: {@link grantKey} is retained for the legacy 2-field
|
|
27
|
+
* form. `::` stays delimiter-safe — `appKey` uses `__`, a `spaceId` is alphanumeric,
|
|
28
|
+
* and a named principal is lowercase-dotted/hyphenated (CA-3), none containing `::`. */
|
|
29
|
+
const grantKeyWithPrincipal = (appKey, principal, spaceId) => `${appKey}::${principal}::${spaceId}`;
|
|
30
|
+
exports.grantKeyWithPrincipal = grantKeyWithPrincipal;
|
|
31
|
+
/** R3-98 S4 — ARITY-DETECTING parse of a grant key (design 05a §3.1 step 3 /
|
|
32
|
+
* MEDIUM-6). A 3-field key is `appKey::principal::spaceId`; a legacy 2-field key is
|
|
33
|
+
* `appKey::spaceId` (principal undefined). This lets the revoke cascade keep
|
|
34
|
+
* resolving BOTH legacy and keyed `parentGrantId`s after the re-key — a positional
|
|
35
|
+
* `split('::')` would mis-assign a legacy key's `spaceId` to `principal`. A
|
|
36
|
+
* malformed key (≠2/≠3 segments) degrades to best-effort `appKey::…::spaceId`
|
|
37
|
+
* (first + last), so the cascade fails safe (child self-revokes) rather than
|
|
38
|
+
* crashing. */
|
|
39
|
+
const parseGrantKey = (key) => {
|
|
40
|
+
const parts = key.split('::');
|
|
41
|
+
if (parts.length === 3) {
|
|
42
|
+
return { appKey: parts[0], principal: parts[1], spaceId: parts[2] };
|
|
43
|
+
}
|
|
44
|
+
// Legacy 2-field, or malformed → first segment is the appKey, last the spaceId.
|
|
45
|
+
return { appKey: parts[0], spaceId: parts[parts.length - 1] };
|
|
46
|
+
};
|
|
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;
|
|
25
83
|
/** Durable elevated/app-scoped grants expire after 90 days WITHOUT USE; first
|
|
26
84
|
* use after expiry re-prompts. Baseline needs no grant record, so this never
|
|
27
85
|
* touches it. */
|
|
@@ -62,13 +120,18 @@ const appKeyPath = (uid, appKey) => [
|
|
|
62
120
|
appKey,
|
|
63
121
|
];
|
|
64
122
|
exports.appKeyPath = appKeyPath;
|
|
65
|
-
|
|
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) => [
|
|
66
129
|
'user-app-spaces',
|
|
67
130
|
uid,
|
|
68
131
|
'apps',
|
|
69
132
|
appKey,
|
|
70
133
|
'spaces',
|
|
71
|
-
spaceId,
|
|
134
|
+
(0, exports.grantDocId)(spaceId, qualifyingPrincipal),
|
|
72
135
|
];
|
|
73
136
|
exports.appSpacePath = appSpacePath;
|
|
74
137
|
const userCountPath = (uid) => ['space-counts', uid];
|
|
@@ -123,6 +186,11 @@ const appSpaceGrantFields = (params, s) => (0, exports.defined)({
|
|
|
123
186
|
grantedAt: s.serverTimestamp(),
|
|
124
187
|
lastUsedAt: s.serverTimestamp(),
|
|
125
188
|
name: params.name,
|
|
189
|
+
// R3-98 S3/S4 — the named principal this grant was minted under (design 05a
|
|
190
|
+
// §3.1). `defined()` omits it when absent, so a legacy/unkeyed mint writes no
|
|
191
|
+
// `principal` field and is grandfathered at the gate (both adapters stamp it
|
|
192
|
+
// identically, keeping the byte-identical-doc guarantee).
|
|
193
|
+
principal: params.principal,
|
|
126
194
|
// UI_AS_APPS_SPEC §8.7: `rules` is authoritative; `subtree`/`mode` are kept as the
|
|
127
195
|
// deprecated `rules[0]` mirror for not-yet-migrated readers. When no rule-set
|
|
128
196
|
// is given, derive a single-rule set from the legacy scope so the backend
|
package/dist/port.d.ts
CHANGED
|
@@ -60,6 +60,11 @@ export interface GrantSpaceParams {
|
|
|
60
60
|
mintPath?: MintPath;
|
|
61
61
|
/** §8.15 — parent `grantKey` for an M2 `delegated` grant. */
|
|
62
62
|
parentGrantId?: string;
|
|
63
|
+
/** R3-98 S3/S4 — the **named principal** this grant is minted under (design 05a
|
|
64
|
+
* §3.1). Written as the `principal` field so the mount-admission gate re-checks
|
|
65
|
+
* it (a grant fires only under the principal it was minted with). Optional +
|
|
66
|
+
* additive: omitted ⇒ no field ⇒ a legacy/grandfathered grant. */
|
|
67
|
+
principal?: string;
|
|
63
68
|
}
|
|
64
69
|
/** Parameters for `MintStore.grantNetFetchHosts` — the per-(user, app) granted
|
|
65
70
|
* host set, the grant half of the `manifest ∩ grant` net:fetch allowlist. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@immediately-run/preauth-core",
|
|
3
|
-
"version": "0.1.
|
|
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": {
|