@foir/sdk 0.22.0 → 0.23.0

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.
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * const events = createEventsClient({
9
9
  * apiUrl: process.env.PLATFORM_API_URL!,
10
- * apiKey: process.env.FOIR_SECRET_KEY!, // sk_…, holding events:read
10
+ * apiKey: process.env.FOIR_SECRET_KEY!, // an admin key, holding events:read
11
11
  * });
12
12
  *
13
13
  * const page = await events.list({ after: lastSeq, limit: 500 });
@@ -21,9 +21,9 @@
21
21
  * PROJECT SURFACE. Your app's users do not reach it.
22
22
  * ---------------------------------------------------------------------------
23
23
  *
24
- * DECIDED 2026-08-04. The event log is reachable by an `sk_` api key
24
+ * DECIDED 2026-08-04. The event log is reachable by an admin key
25
25
  * and by a console session. It is NOT reachable by a user token, there is no
26
- * user lane for it on api-public, and a `pk_` key can never be minted
26
+ * user lane for it on api-public, and a publishable key can never be minted
27
27
  * holding `events:read` at all.
28
28
  *
29
29
  * The reason is a predicate asymmetry rather than a policy preference.
@@ -39,19 +39,19 @@
39
39
  *
40
40
  * 1. **It lives on `@foir/sdk/server` and nowhere else.** Not `./data`, not
41
41
  * `./client`, not `./react`. Those subpaths are the ones a storefront
42
- * bundles, and `./data` in particular is routinely constructed with a `pk_`
42
+ * bundles, and `./data` in particular is routinely constructed with a publishable key
43
43
  * key in browser-reachable code. `src/server/__tests__/events-client.test.ts`
44
44
  * walks the import graph of every published subpath and fails if this
45
45
  * module becomes reachable from one that ships to a browser — because
46
46
  * "we only documented it on the server" is not a property anything checks.
47
47
  *
48
- * 2. **A `pk_` key is refused at construction**, before a request is made. The
48
+ * 2. **A publishable key is refused at construction**, before a request is made. The
49
49
  * server would refuse it anyway (403), but a 403 at 3am in a deploy log is
50
50
  * a worse teacher than a thrown error naming the key type at the line that
51
51
  * built the client.
52
52
  *
53
53
  * 3. **There is no `bearerToken` option, and an `Authorization` header is
54
- * refused.** On api-public a request presenting `sk_` plus a user
54
+ * refused.** On api-public a request presenting an admin key plus a user
55
55
  * bearer resolves the USER as the authority, and `events` is stripped
56
56
  * from every user authority upstream — so attaching a signed-in
57
57
  * shopper's token to a project read turns a working call into a 403 that
@@ -77,7 +77,7 @@ import { type FoirReconnectPolicy, type FoirSubscription, type FoirSubscriptionH
77
77
  /** The path `GET /v1/events` is mounted on, inside api-public's api-key group. */
78
78
  export declare const FOIR_EVENTS_PATH = "/v1/events";
79
79
  /**
80
- * The scope an `sk_` key must hold. Never mintable on a `pk_` — `events` is
80
+ * The scope an admin key must hold. Never mintable on a publishable key: `events` is
81
81
  * absent from the platform's `publicSafeScopeResources` — and never present in a
82
82
  * user's resolved authority.
83
83
  */
@@ -7,7 +7,7 @@
7
7
  export interface ServerClientOptions {
8
8
  /** Base URL for the platform API (e.g., 'https://api.example.com'). */
9
9
  baseUrl: string;
10
- /** Project-scoped API key (sk_* format). */
10
+ /** A project's admin key (foir_adm_). */
11
11
  apiKey: string;
12
12
  /**
13
13
  * Optional config key. Retained for backwards compatibility with older
package/dist/server.js CHANGED
@@ -74,123 +74,123 @@ async function verifyWebhookSignature(payload, signature, secret) {
74
74
 
75
75
  // src/auth/grant-table.generated.ts
76
76
  var GRANT_TABLE = [
77
- ["records:read", true, ["own", "all"], []],
78
- ["records:create", true, [], []],
79
- ["records:write", true, ["own", "all"], []],
80
- ["records:delete", true, ["own", "all"], []],
81
- ["records:publish", true, ["own", "all"], []],
82
- ["records:attribute", true, [], []],
83
- ["search:read", true, [], []],
84
- ["embeddings:read", false, [], []],
85
- ["embeddings:write", false, [], []],
86
- ["schemas:read", false, [], []],
87
- ["drafts:read", false, [], []],
88
- ["files:read", false, ["own", "all"], []],
89
- ["files:write", false, ["own", "all"], []],
90
- ["files:delete", false, ["own", "all"], []],
91
- ["notifications:read", false, ["own", "all"], []],
92
- ["notifications:write", false, ["own", "named", "all"], []],
93
- ["notifications:consent", false, [], []],
94
- ["pushcredentials:read", false, [], []],
95
- ["pushcredentials:write", false, [], []],
96
- ["notes:read", false, ["own", "all"], []],
97
- ["notes:write", false, ["own", "all"], []],
98
- ["notes:create", false, [], []],
99
- ["approvals:read", false, [], []],
100
- ["approvals:write", false, [], []],
101
- ["rooms:read", true, [], []],
102
- ["rooms:write", true, [], []],
103
- ["presence:read", true, [], []],
104
- ["operations:read", false, [], []],
105
- ["operations:execute", true, [], []],
106
- ["operations:write", false, [], []],
107
- ["operations:complete", false, [], []],
108
- ["status:write", false, [], []],
109
- ["appconfig:read", false, [], []],
110
- ["schedules:read", false, [], []],
111
- ["schedules:write", false, [], []],
112
- ["hooks:read", false, [], []],
113
- ["hooks:write", false, [], []],
114
- ["relyingparties:read", false, [], []],
115
- ["relyingparties:write", false, [], []],
116
- ["domains:read", false, [], []],
117
- ["domains:write", false, [], []],
118
- ["secrets:read", false, [], []],
119
- ["secrets:list", false, [], []],
120
- ["secrets:put", false, [], []],
121
- ["secrets:delete", false, [], []],
122
- ["appsecrets:read", false, [], []],
123
- ["appsecrets:list", false, [], []],
124
- ["appsecrets:put", false, [], []],
125
- ["appsecrets:delete", false, [], []],
126
- ["opsecrets:read", false, [], []],
127
- ["opsecrets:write", false, [], []],
128
- ["settings:read", false, [], []],
129
- ["settings:write", false, [], []],
130
- ["designtokens:read", false, [], []],
131
- ["designtokens:write", false, [], []],
132
- ["models:read", false, [], []],
133
- ["models:write", false, [], []],
134
- ["configs:read", false, [], []],
135
- ["configs:write", false, [], []],
136
- ["apps:read", false, [], []],
137
- ["apps:write", false, [], []],
138
- ["events:read", false, [], []],
139
- ["targeting:debug", false, [], []],
140
- ["visitors:forward", false, [], []],
141
- ["apikeys:read", false, [], []],
142
- ["apikeys:write", false, [], []],
143
- ["organizations:read", false, [], []],
144
- ["organizations:write", false, [], []],
145
- ["organizations:create", false, [], []],
146
- ["organizations:delete", false, [], []],
147
- ["organizations:reach", false, [], []],
148
- ["members:read", false, [], []],
149
- ["members:write", false, [], []],
150
- ["roles:read", false, [], []],
151
- ["roles:write", false, [], []],
152
- ["projects:read", false, [], []],
153
- ["projects:write", false, [], []],
154
- ["project:stand", false, [], []],
155
- ["project:manage", false, [], ["project:stand", "records:read.all", "records:create", "records:write.all", "records:delete.all", "records:publish.all", "records:attribute", "search:read", "embeddings:read", "embeddings:write", "schemas:read", "drafts:read", "files:read.all", "files:write.all", "files:delete.all", "users:read.all", "identities:read", "users:create", "identities:delete", "users:write.all", "users:setup", "users:delete.all", "notifications:read.all", "notifications:write.all", "notifications:consent", "pushcredentials:read", "pushcredentials:write", "notes:read.all", "notes:write.all", "notes:create", "approvals:read", "approvals:write", "rooms:read", "rooms:write", "presence:read", "operations:read", "operations:execute", "operations:write", "appconfig:read", "schedules:read", "schedules:write", "hooks:read", "hooks:write", "auth:login_with_foir", "relyingparties:read", "relyingparties:write", "domains:read", "domains:write", "secrets:read", "secrets:list", "secrets:put", "secrets:delete", "appsecrets:read", "appsecrets:list", "appsecrets:put", "appsecrets:delete", "usersecrets:read", "usersecrets:list", "usersecrets:put", "usersecrets:delete", "opsecrets:read", "opsecrets:write", "settings:read", "settings:write", "designtokens:read", "designtokens:write", "models:read", "models:write", "configs:read", "configs:write", "signinproviders:read", "signinproviders:write", "apps:read", "apps:write", "events:read", "targeting:debug", "visitors:forward", "apikeys:read", "apikeys:write", "organizations:read", "organizations:create", "organizations:delete", "organizations:write", "organizations:reach", "members:read", "members:write", "roles:read", "roles:write", "projects:read", "projects:write", "audit:read"]],
156
- ["projects:create", false, [], []],
157
- ["projects:delete", false, [], []],
158
- ["tenants:read", false, [], []],
159
- ["tenants:write", false, [], []],
160
- ["tenants:delete", false, [], []],
161
- ["audit:read", false, [], []],
162
- ["export:run", false, [], []],
163
- ["billing:read", false, [], []],
164
- ["billing:manage", false, [], []],
165
- ["usage:read", false, [], []],
166
- ["controlkeys:read", false, [], []],
167
- ["controlkeys:write", false, [], []],
168
- ["tenantsigninproviders:read", false, [], []],
169
- ["tenantsigninproviders:write", false, [], []],
170
- ["tenanthooks:read", false, [], []],
171
- ["tenanthooks:write", false, [], []],
172
- ["apps:install", false, [], []],
173
- ["tenant:manage", false, [], ["project:manage", "projects:create", "projects:delete", "tenants:read", "tenants:write", "tenants:delete", "export:run", "billing:read", "billing:manage", "usage:read", "controlkeys:read", "controlkeys:write", "tenanthooks:read", "tenanthooks:write", "tenantsigninproviders:read", "tenantsigninproviders:write", "apps:install"]],
174
- ["platform:read", false, [], []],
175
- ["platform:manage", false, [], ["tenant:manage", "platform:read", "platform:create_workspace"]],
176
- ["platform:create_workspace", false, [], []],
177
- ["users:read", false, ["own", "named", "all"], []],
178
- ["users:write", false, ["own", "all"], []],
179
- ["users:setup", false, [], []],
180
- ["users:delete", false, ["own", "all"], []],
181
- ["users:create", false, [], []],
182
- ["identities:read", false, [], []],
183
- ["identities:delete", false, [], []],
184
- ["usersecrets:read", false, [], []],
185
- ["usersecrets:list", false, [], []],
186
- ["usersecrets:put", false, [], []],
187
- ["usersecrets:delete", false, [], []],
188
- ["auth:login_with_foir", false, [], []],
189
- ["signinproviders:read", false, [], []],
190
- ["signinproviders:write", false, [], []]
77
+ ["records:read", "model", ["own", "all"], []],
78
+ ["records:create", "model", [], []],
79
+ ["records:write", "model", ["own", "all"], []],
80
+ ["records:delete", "model", ["own", "all"], []],
81
+ ["records:publish", "model", ["own", "all"], []],
82
+ ["records:attribute", "model", [], []],
83
+ ["search:read", "model", [], []],
84
+ ["embeddings:read", "none", [], []],
85
+ ["embeddings:write", "none", [], []],
86
+ ["schemas:read", "none", [], []],
87
+ ["drafts:read", "none", [], []],
88
+ ["files:read", "none", ["own", "all"], []],
89
+ ["files:write", "none", ["own", "all"], []],
90
+ ["files:delete", "none", ["own", "all"], []],
91
+ ["notifications:read", "none", ["own", "all"], []],
92
+ ["notifications:write", "none", ["own", "named", "all"], []],
93
+ ["notifications:consent", "none", [], []],
94
+ ["pushcredentials:read", "none", [], []],
95
+ ["pushcredentials:write", "none", [], []],
96
+ ["notes:read", "none", ["own", "all"], []],
97
+ ["notes:write", "none", ["own", "all"], []],
98
+ ["notes:create", "none", [], []],
99
+ ["approvals:read", "none", [], []],
100
+ ["approvals:write", "none", [], []],
101
+ ["rooms:read", "model", [], []],
102
+ ["rooms:write", "model", [], []],
103
+ ["presence:read", "model", [], []],
104
+ ["operations:read", "none", [], []],
105
+ ["operations:execute", "operation", [], []],
106
+ ["operations:write", "none", [], []],
107
+ ["operations:complete", "none", [], []],
108
+ ["status:write", "none", [], []],
109
+ ["appconfig:read", "none", [], []],
110
+ ["schedules:read", "none", [], []],
111
+ ["schedules:write", "none", [], []],
112
+ ["hooks:read", "none", [], []],
113
+ ["hooks:write", "none", [], []],
114
+ ["authclients:read", "none", [], []],
115
+ ["authclients:write", "none", [], []],
116
+ ["domains:read", "none", [], []],
117
+ ["domains:write", "none", [], []],
118
+ ["secrets:read", "none", [], []],
119
+ ["secrets:list", "none", [], []],
120
+ ["secrets:put", "none", [], []],
121
+ ["secrets:delete", "none", [], []],
122
+ ["appsecrets:read", "none", [], []],
123
+ ["appsecrets:list", "none", [], []],
124
+ ["appsecrets:put", "none", [], []],
125
+ ["appsecrets:delete", "none", [], []],
126
+ ["opsecrets:read", "none", [], []],
127
+ ["opsecrets:write", "none", [], []],
128
+ ["settings:read", "none", [], []],
129
+ ["settings:write", "none", [], []],
130
+ ["designtokens:read", "none", [], []],
131
+ ["designtokens:write", "none", [], []],
132
+ ["models:read", "none", [], []],
133
+ ["models:write", "none", [], []],
134
+ ["configs:read", "none", [], []],
135
+ ["configs:write", "none", [], []],
136
+ ["apps:read", "none", [], []],
137
+ ["apps:write", "none", [], []],
138
+ ["events:read", "none", [], []],
139
+ ["targeting:debug", "none", [], []],
140
+ ["visitors:forward", "none", [], []],
141
+ ["apikeys:read", "none", [], []],
142
+ ["apikeys:write", "none", [], []],
143
+ ["organizations:read", "none", [], []],
144
+ ["organizations:write", "none", [], []],
145
+ ["organizations:create", "none", [], []],
146
+ ["organizations:delete", "none", [], []],
147
+ ["organizations:reach", "none", [], []],
148
+ ["members:read", "none", [], []],
149
+ ["members:write", "none", [], []],
150
+ ["roles:read", "none", [], []],
151
+ ["roles:write", "none", [], []],
152
+ ["projects:read", "none", [], []],
153
+ ["projects:write", "none", [], []],
154
+ ["project:stand", "none", [], []],
155
+ ["project:manage", "none", [], ["project:stand", "records:read.all", "records:create", "records:write.all", "records:delete.all", "records:publish.all", "records:attribute", "search:read", "embeddings:read", "embeddings:write", "schemas:read", "drafts:read", "files:read.all", "files:write.all", "files:delete.all", "users:read.all", "identities:read", "users:create", "identities:delete", "users:write.all", "users:setup", "users:delete.all", "notifications:read.all", "notifications:write.all", "notifications:consent", "pushcredentials:read", "pushcredentials:write", "notes:read.all", "notes:write.all", "notes:create", "approvals:read", "approvals:write", "rooms:read", "rooms:write", "presence:read", "operations:read", "operations:execute", "operations:write", "appconfig:read", "schedules:read", "schedules:write", "hooks:read", "hooks:write", "auth:login_with_foir", "authclients:read", "authclients:write", "domains:read", "domains:write", "secrets:read", "secrets:list", "secrets:put", "secrets:delete", "appsecrets:read", "appsecrets:list", "appsecrets:put", "appsecrets:delete", "usersecrets:read", "usersecrets:list", "usersecrets:put", "usersecrets:delete", "opsecrets:read", "opsecrets:write", "settings:read", "settings:write", "designtokens:read", "designtokens:write", "models:read", "models:write", "configs:read", "configs:write", "signinproviders:read", "signinproviders:write", "apps:read", "apps:write", "events:read", "targeting:debug", "visitors:forward", "apikeys:read", "apikeys:write", "organizations:read", "organizations:create", "organizations:delete", "organizations:write", "organizations:reach", "members:read", "members:write", "roles:read", "roles:write", "projects:read", "projects:write", "audit:read"]],
156
+ ["projects:create", "none", [], []],
157
+ ["projects:delete", "none", [], []],
158
+ ["tenants:read", "none", [], []],
159
+ ["tenants:write", "none", [], []],
160
+ ["tenants:delete", "none", [], []],
161
+ ["audit:read", "none", [], []],
162
+ ["export:run", "none", [], []],
163
+ ["billing:read", "none", [], []],
164
+ ["billing:manage", "none", [], []],
165
+ ["usage:read", "none", [], []],
166
+ ["controlkeys:read", "none", [], []],
167
+ ["controlkeys:write", "none", [], []],
168
+ ["tenanthooks:read", "none", [], []],
169
+ ["tenanthooks:write", "none", [], []],
170
+ ["apps:install", "none", [], []],
171
+ ["tenant:manage", "none", [], ["project:manage", "projects:create", "projects:delete", "tenants:read", "tenants:write", "tenants:delete", "export:run", "billing:read", "billing:manage", "usage:read", "controlkeys:read", "controlkeys:write", "tenanthooks:read", "tenanthooks:write", "apps:install"]],
172
+ ["platform:read", "none", [], []],
173
+ ["platform:manage", "none", [], ["tenant:manage", "platform:read", "platform:create_workspace"]],
174
+ ["platform:create_workspace", "none", [], []],
175
+ ["users:read", "none", ["own", "named", "all"], []],
176
+ ["users:write", "none", ["own", "all"], []],
177
+ ["users:setup", "none", [], []],
178
+ ["users:delete", "none", ["own", "all"], []],
179
+ ["users:create", "none", [], []],
180
+ ["identities:read", "none", [], []],
181
+ ["identities:delete", "none", [], []],
182
+ ["usersecrets:read", "none", [], []],
183
+ ["usersecrets:list", "none", [], []],
184
+ ["usersecrets:put", "none", [], []],
185
+ ["usersecrets:delete", "none", [], []],
186
+ ["auth:login_with_foir", "none", [], []],
187
+ ["signinproviders:read", "none", [], []],
188
+ ["signinproviders:write", "none", [], []]
191
189
  ];
192
190
 
193
191
  // src/auth/grants.ts
192
+ var FIELD_PERMISSIONS = ["records:read", "records:write"];
193
+ var FIELD_KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
194
194
  var index;
195
195
  function catalogue() {
196
196
  if (!index) {
@@ -226,8 +226,12 @@ function parseGrant(s) {
226
226
  let target = "";
227
227
  if (rest.startsWith(":")) {
228
228
  target = rest.slice(1);
229
- if (!permission[1]) return null;
229
+ if (permission[1] === "none") return null;
230
230
  if (target === "" || /[: \t\n*]/.test(target)) return null;
231
+ const dot = target.indexOf(".");
232
+ if (permission[1] === "model" && dot >= 0) {
233
+ if (!FIELD_PERMISSIONS.includes(name) || dot === 0 || !FIELD_KEY.test(target.slice(dot + 1))) return null;
234
+ }
231
235
  }
232
236
  return { permission, reach, target };
233
237
  }
@@ -1040,10 +1044,7 @@ async function hmacSha256Hex(secret, body) {
1040
1044
  var KEY_PREFIXES = [
1041
1045
  ["foir_pub_", "publishable"],
1042
1046
  ["foir_srv_", "server"],
1043
- ["foir_adm_", "admin"],
1044
- ["pk_", "publishable"],
1045
- ["sk_", "older"],
1046
- ["tc_", "older"]
1047
+ ["foir_adm_", "admin"]
1047
1048
  ];
1048
1049
  function foirKeyKind(apiKey) {
1049
1050
  if (typeof apiKey !== "string") return void 0;
@@ -1513,7 +1514,7 @@ function assertNoBearer(headers) {
1513
1514
  for (const name of Object.keys(headers)) {
1514
1515
  if (name.toLowerCase() === "authorization") {
1515
1516
  throw new FoirRequestError(
1516
- `[foir] createEventsClient: an Authorization header is not accepted. A user bearer makes the user the resolved authority on api-public, and no user authority can contain ${FOIR_EVENTS_SCOPE}. The sk_ key alone is the credential for this surface.`
1517
+ `[foir] createEventsClient: an Authorization header is not accepted. A user bearer makes the user the resolved authority on api-public, and no user authority can contain ${FOIR_EVENTS_SCOPE}. The admin key alone is the credential for this surface.`
1517
1518
  );
1518
1519
  }
1519
1520
  }
@@ -31,7 +31,7 @@ export interface FoirEnv {
31
31
  *
32
32
  * Preview is selected at the environment level, not per-request: load a
33
33
  * preview env file that sets `FOIR_PREVIEW=true` + the preview environment's
34
- * own `pk_…` key holding `drafts:read` as `PLATFORM_PREVIEW_API_KEY`, instead
34
+ * own publishable key holding `drafts:read` as `PLATFORM_PREVIEW_API_KEY`, instead
35
35
  * of the default env.
36
36
  * The site-wide mode keeps draft semantics consistent — every loader sees
37
37
  * the same preview/published view of the world. Intended for non-production
@@ -6,30 +6,19 @@
6
6
  * app's pages. An admin key is a machine's own: a sync job, a pipeline, a tool.
7
7
  * A key says its kind in its first letters, so this is decided without a
8
8
  * request.
9
- *
10
- * A key minted before a key had a kind says an older type. A `pk_` key is a
11
- * publishable key. An `sk_` or a `tc_` key says only that it is not public:
12
- * the platform holds it as an admin key, and it is reported here as `older`,
13
- * so that a caller who refuses an admin key by name does not refuse a key
14
- * whose holder has not yet been able to mint a server key in its place.
15
9
  */
16
10
  export type FoirKeyKind = 'publishable' | 'server' | 'admin';
17
- /** What a key's prefix says of it: its kind, or that it is an older secret. */
18
- export type FoirKeyPrefixKind = FoirKeyKind | 'older';
19
11
  /** Where each credential is for, and which key an app holds. */
20
12
  export declare const FOIR_CREDENTIALS_DOC = "https://docs.foir.dev/credentials";
21
13
  /**
22
14
  * What a key says of itself by its prefix, or `undefined` for a string that
23
15
  * is no key Fóir made.
24
16
  */
25
- export declare function foirKeyKind(apiKey: string | null | undefined): FoirKeyPrefixKind | undefined;
17
+ export declare function foirKeyKind(apiKey: string | null | undefined): FoirKeyKind | undefined;
26
18
  /**
27
19
  * What an app's client says when it is handed an admin key, naming the client
28
20
  * it was handed to. An admin key is a machine's own, so every client an app
29
21
  * builds refuses one before any request, in the same words.
30
- *
31
- * Only a key that says it is an admin key by its prefix is refused. A key
32
- * minted before a key had a kind does not say, and is let through.
33
22
  */
34
23
  export declare function adminKeyRefusal(client: string): string;
35
24
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@foir/sdk",
3
- "version": "0.22.0",
3
+ "version": "0.23.0",
4
4
  "description": "Official Foir SDK — editor iframe client + server-side platform helpers",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -177,8 +177,8 @@
177
177
  "ws": "^8.18.0",
178
178
  "y-websocket": "^3.0.0",
179
179
  "yjs": "^13.6.30",
180
- "@foir/proto-ts": "0.109.0",
181
- "@foir/access-vocab": "0.0.0"
180
+ "@foir/access-vocab": "0.0.0",
181
+ "@foir/proto-ts": "0.109.0"
182
182
  },
183
183
  "engines": {
184
184
  "node": ">=18.0.0"