@foir/sdk 0.21.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.
- package/CHANGELOG.md +135 -1
- package/README.md +15 -15
- package/dist/app/client.d.ts +93 -86
- package/dist/app/contract/proto/apps/v1/apps_pb.d.ts +14 -13
- package/dist/app/dispatch.d.ts +5 -5
- package/dist/app/errors.d.ts +17 -15
- package/dist/app/index.d.ts +4 -4
- package/dist/app/naming.d.ts +1 -1
- package/dist/app/ticket.d.ts +2 -2
- package/dist/app/vocabulary.generated.d.ts +2 -2
- package/dist/app-contract.js +3 -3
- package/dist/app.js +92 -90
- package/dist/auth/access.d.ts +19 -19
- package/dist/auth/auth.d.ts +251 -117
- package/dist/auth/grant-table.generated.d.ts +5 -4
- package/dist/auth/hosted.d.ts +38 -14
- package/dist/auth/index.d.ts +9 -9
- package/dist/auth/operations.d.ts +153 -33
- package/dist/auth/ops.d.ts +39 -21
- package/dist/auth/session-client.d.ts +3 -3
- package/dist/auth-session.js +3 -3
- package/dist/auth.js +400 -250
- package/dist/client.js +5 -8
- package/dist/codegen/preset.d.ts +1 -1
- package/dist/data/builtins.d.ts +26 -26
- package/dist/data/client.d.ts +3 -3
- package/dist/data/decimal.d.ts +1 -1
- package/dist/data/index.d.ts +1 -1
- package/dist/data/manifest.d.ts +1 -1
- package/dist/data/schema.d.ts +2 -2
- package/dist/data/transport.d.ts +14 -13
- package/dist/data/ws-transport.d.ts +1 -1
- package/dist/data.js +8 -11
- package/dist/graphql/content-client.d.ts +13 -13
- package/dist/index.js +1 -4
- package/dist/live/drift.d.ts +1 -1
- package/dist/media/screen-capture.d.ts +1 -1
- package/dist/next/index.d.ts +2 -2
- package/dist/next/session.d.ts +10 -10
- package/dist/next.js +78 -60
- package/dist/placement/index.d.ts +13 -13
- package/dist/placement.js +127 -116
- package/dist/push.js +62 -44
- package/dist/react/FoirFeedback.d.ts +2 -2
- package/dist/react/FoirProvider.d.ts +24 -24
- package/dist/react/SignIn.d.ts +4 -4
- package/dist/react/UserButton.d.ts +1 -1
- package/dist/react/feedback-notes.d.ts +8 -6
- package/dist/react/index.d.ts +3 -3
- package/dist/react.js +238 -209
- package/dist/realtime/index.d.ts +1 -1
- package/dist/server/callback-client.d.ts +4 -4
- package/dist/server/events-client.d.ts +21 -21
- package/dist/server/operation-types.d.ts +8 -8
- package/dist/server/require-scoped-token.d.ts +3 -3
- package/dist/server/types.d.ts +2 -2
- package/dist/server/verify-scoped-token.d.ts +3 -3
- package/dist/server.js +130 -126
- package/dist/transport/foir-http.d.ts +14 -14
- package/dist/transport/key-kind.d.ts +1 -12
- package/dist/types/index.d.ts +2 -2
- package/package.json +1 -1
package/dist/realtime/index.d.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* provider's url params current for whenever it next reconnects.
|
|
12
12
|
*
|
|
13
13
|
* Agnostic by design: token-agnostic (caller supplies `getToken` — an internal
|
|
14
|
-
* mint for
|
|
14
|
+
* mint for the console, a user access token for an app), persistence-agnostic
|
|
15
15
|
* (never persists), doc-schema-agnostic (caller owns the `Y.Doc` + shared types).
|
|
16
16
|
*
|
|
17
17
|
* `yjs` + `y-websocket` are OPTIONAL peer deps gated to this subpath, so a
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
* pushes a DLQ entry.
|
|
13
13
|
* - `progress({pct, message, metadata})` — non-terminal update; the
|
|
14
14
|
* monotonic guard at the platform DB layer drops out-of-order packets.
|
|
15
|
-
* - `cancel()
|
|
16
|
-
*
|
|
15
|
+
* - `cancel()`: the extension cancels itself (rare; a run is normally
|
|
16
|
+
* cancelled from the console).
|
|
17
17
|
*
|
|
18
18
|
* Each call carries:
|
|
19
19
|
* - `Authorization: Bearer <callback.token>` — Ed25519 scoped token.
|
|
@@ -42,7 +42,7 @@ export interface CallbackResult {
|
|
|
42
42
|
*/
|
|
43
43
|
status: string;
|
|
44
44
|
/**
|
|
45
|
-
* True when
|
|
45
|
+
* True when someone cancelled the execution while the extension was
|
|
46
46
|
* processing — the platform refused to overwrite the cancelled state.
|
|
47
47
|
* Extensions should stop work and (optionally) call `fail('CANCELLED')`.
|
|
48
48
|
*/
|
|
@@ -66,7 +66,7 @@ export interface CallbackClient<TResult = unknown> {
|
|
|
66
66
|
details?: Record<string, unknown>;
|
|
67
67
|
}): Promise<CallbackResult>;
|
|
68
68
|
/**
|
|
69
|
-
* Returns `{cancelled: true}` when
|
|
69
|
+
* Returns `{cancelled: true}` when someone has cancelled it: the extension
|
|
70
70
|
* must stop work. `applied: false` means the monotonic guard dropped
|
|
71
71
|
* the update because pct was lower than the persisted value.
|
|
72
72
|
*/
|
|
@@ -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!, //
|
|
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 });
|
|
@@ -18,12 +18,12 @@
|
|
|
18
18
|
* ```
|
|
19
19
|
*
|
|
20
20
|
* ---------------------------------------------------------------------------
|
|
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
|
|
25
|
-
* and by
|
|
26
|
-
*
|
|
24
|
+
* DECIDED 2026-08-04. The event log is reachable by an admin key
|
|
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 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.
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
* owner, admin_owner, access_mode, grantee, comp_parent, read_borrow and
|
|
32
32
|
* slice_parent terms on top of it. A containment-only read over the log
|
|
33
33
|
* therefore describes rows whose underlying records the same caller could not
|
|
34
|
-
* read — and every row carries `actor`, naming which OTHER
|
|
34
|
+
* read — and every row carries `actor`, naming which OTHER user acted. In a
|
|
35
35
|
* project with owner confinement that is a per-record activity feed of other
|
|
36
36
|
* people's actions.
|
|
37
37
|
*
|
|
@@ -39,25 +39,25 @@
|
|
|
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
|
|
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
|
|
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
|
|
55
|
-
* bearer resolves the
|
|
56
|
-
* from every
|
|
57
|
-
* shopper's token to
|
|
54
|
+
* refused.** On api-public a request presenting an admin key plus a user
|
|
55
|
+
* bearer resolves the USER as the authority, and `events` is stripped
|
|
56
|
+
* from every user authority upstream — so attaching a signed-in
|
|
57
|
+
* shopper's token to a project read turns a working call into a 403 that
|
|
58
58
|
* reads like a scope problem. Refusing it here says what actually happened.
|
|
59
59
|
*
|
|
60
|
-
* Widening this later is easy. Teaching
|
|
60
|
+
* Widening this later is easy. Teaching users a capability they do not have
|
|
61
61
|
* is a support burden that cannot be retracted.
|
|
62
62
|
*
|
|
63
63
|
* ---------------------------------------------------------------------------
|
|
@@ -77,9 +77,9 @@ 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
|
|
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
|
*/
|
|
84
84
|
export declare const FOIR_EVENTS_SCOPE = "events:read";
|
|
85
85
|
/**
|
|
@@ -91,9 +91,9 @@ export declare const FOIR_EVENTS_SCOPE = "events:read";
|
|
|
91
91
|
export declare const FOIR_EVENTS_RETENTION_DAYS = 90;
|
|
92
92
|
/** Who caused an event. `null` when the emit site attributed nobody. */
|
|
93
93
|
export interface FoirEventActor {
|
|
94
|
-
/** e.g. `"
|
|
94
|
+
/** e.g. `"user"`, `"api_key"`, `"system"`. */
|
|
95
95
|
kind?: string;
|
|
96
|
-
/** e.g. `"
|
|
96
|
+
/** e.g. `"user:usr_123"`. */
|
|
97
97
|
ref?: string;
|
|
98
98
|
[key: string]: unknown;
|
|
99
99
|
}
|
|
@@ -119,7 +119,7 @@ export interface FoirEvent {
|
|
|
119
119
|
* The event's own flat keys, plus `state` and `stateSchema`.
|
|
120
120
|
*
|
|
121
121
|
* `state` is the resource AS OF the event — self-contained, not a pointer to
|
|
122
|
-
* current state — for the
|
|
122
|
+
* current state — for the user, session, operation and workspace-plane
|
|
123
123
|
* families. It is null for `record.*` events, deliberately and permanently:
|
|
124
124
|
* `events.view` is plain tenant/project containment while `record.view`
|
|
125
125
|
* carries owner, grant, borrow and per-field terms, so a copy of record data
|
|
@@ -203,12 +203,12 @@ export interface FoirEventsClientConfig {
|
|
|
203
203
|
* An admin key holding `events:read`.
|
|
204
204
|
*
|
|
205
205
|
* A publishable or a server key is refused here rather than at the server:
|
|
206
|
-
* see the
|
|
206
|
+
* see the note at the top of this file.
|
|
207
207
|
*/
|
|
208
208
|
apiKey: string;
|
|
209
209
|
/**
|
|
210
|
-
* Extra headers. An `Authorization` header is refused: a
|
|
211
|
-
* flips api-public's resolved authority to the
|
|
210
|
+
* Extra headers. An `Authorization` header is refused: a user bearer
|
|
211
|
+
* flips api-public's resolved authority to the user, and no user
|
|
212
212
|
* authority can contain `events:read`.
|
|
213
213
|
*/
|
|
214
214
|
headers?: Readonly<Record<string, string>>;
|
|
@@ -147,17 +147,17 @@ export interface OperationContext {
|
|
|
147
147
|
/** Project ID */
|
|
148
148
|
projectId: string;
|
|
149
149
|
/**
|
|
150
|
-
*
|
|
151
|
-
*
|
|
150
|
+
* Who started the run. Always present, and the empty string when nobody
|
|
151
|
+
* did, as for a hook's run.
|
|
152
152
|
*/
|
|
153
|
-
|
|
153
|
+
actorId: string;
|
|
154
154
|
/**
|
|
155
|
-
* The
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
155
|
+
* The user the run acts for, when it acts for one: the signed-in user who
|
|
156
|
+
* ran it, or the owner of the record whose event fired the hook. The
|
|
157
|
+
* dispatch token carries the same user, so a client built from it acts for
|
|
158
|
+
* them too.
|
|
159
159
|
*/
|
|
160
|
-
|
|
160
|
+
userId?: string;
|
|
161
161
|
/** When the platform built this body, RFC 3339 in UTC to the second. */
|
|
162
162
|
timestamp: string;
|
|
163
163
|
}
|
|
@@ -120,12 +120,12 @@ export interface ScopedTokenIngress {
|
|
|
120
120
|
*/
|
|
121
121
|
orgId: string | null;
|
|
122
122
|
/**
|
|
123
|
-
* The
|
|
124
|
-
*
|
|
123
|
+
* The user the run acts for, when it acts for one: the signed-in
|
|
124
|
+
* user who ran the operation, or the owner of the record whose event
|
|
125
125
|
* fired it. Verified with the rest of the token, so it is the one to trust
|
|
126
126
|
* over anything in the input.
|
|
127
127
|
*/
|
|
128
|
-
|
|
128
|
+
userId?: string;
|
|
129
129
|
/** When the token stops being accepted. There is no refresh: finish, or report, before it. */
|
|
130
130
|
expiresAt: Date;
|
|
131
131
|
/** The token's own id (`jti`). The run's id on a dispatch that carries no other. */
|
package/dist/server/types.d.ts
CHANGED
|
@@ -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
|
-
/**
|
|
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
|
|
@@ -16,7 +16,7 @@ export interface ServerClientOptions {
|
|
|
16
16
|
configKey?: string;
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
19
|
-
* Where an editor iframe should appear in the
|
|
19
|
+
* Where an editor iframe should appear in the console's record editor.
|
|
20
20
|
*/
|
|
21
21
|
export interface EditorPlacement {
|
|
22
22
|
/** 'main-editor' replaces / augments the content tab; 'sidebar' renders in the sidebar. */
|
|
@@ -38,9 +38,9 @@ export interface ScopedTokenClaims {
|
|
|
38
38
|
iat: number;
|
|
39
39
|
ctx?: Record<string, string>;
|
|
40
40
|
/**
|
|
41
|
-
* The
|
|
42
|
-
*
|
|
43
|
-
* fired it. `requireScopedToken` answers it as `
|
|
41
|
+
* The user the token acts for, when it acts for one: the signed-in
|
|
42
|
+
* user who ran the operation, or the owner of the record whose event
|
|
43
|
+
* fired it. `requireScopedToken` answers it as `userId`.
|
|
44
44
|
*/
|
|
45
45
|
cid?: string;
|
|
46
46
|
}
|
package/dist/server.js
CHANGED
|
@@ -74,120 +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",
|
|
78
|
-
["records:create",
|
|
79
|
-
["records:write",
|
|
80
|
-
["records:delete",
|
|
81
|
-
["records:publish",
|
|
82
|
-
["records:attribute",
|
|
83
|
-
["search:read",
|
|
84
|
-
["embeddings:read",
|
|
85
|
-
["embeddings:write",
|
|
86
|
-
["schemas:read",
|
|
87
|
-
["drafts:read",
|
|
88
|
-
["files:read",
|
|
89
|
-
["files:write",
|
|
90
|
-
["files:delete",
|
|
91
|
-
["notifications:read",
|
|
92
|
-
["notifications:write",
|
|
93
|
-
["notifications:consent",
|
|
94
|
-
["pushcredentials:read",
|
|
95
|
-
["pushcredentials:write",
|
|
96
|
-
["notes:read",
|
|
97
|
-
["notes:write",
|
|
98
|
-
["notes:create",
|
|
99
|
-
["approvals:read",
|
|
100
|
-
["approvals:write",
|
|
101
|
-
["rooms:read",
|
|
102
|
-
["rooms:write",
|
|
103
|
-
["presence:read",
|
|
104
|
-
["operations:read",
|
|
105
|
-
["operations:execute",
|
|
106
|
-
["operations:write",
|
|
107
|
-
["operations:complete",
|
|
108
|
-
["status:write",
|
|
109
|
-
["appconfig:read",
|
|
110
|
-
["schedules:read",
|
|
111
|
-
["schedules:write",
|
|
112
|
-
["hooks:read",
|
|
113
|
-
["hooks:write",
|
|
114
|
-
["
|
|
115
|
-
["
|
|
116
|
-
["domains:read",
|
|
117
|
-
["domains:write",
|
|
118
|
-
["secrets:read",
|
|
119
|
-
["secrets:list",
|
|
120
|
-
["secrets:put",
|
|
121
|
-
["secrets:delete",
|
|
122
|
-
["appsecrets:read",
|
|
123
|
-
["appsecrets:list",
|
|
124
|
-
["appsecrets:put",
|
|
125
|
-
["appsecrets:delete",
|
|
126
|
-
["opsecrets:read",
|
|
127
|
-
["opsecrets:write",
|
|
128
|
-
["settings:read",
|
|
129
|
-
["settings:write",
|
|
130
|
-
["designtokens:read",
|
|
131
|
-
["designtokens:write",
|
|
132
|
-
["models:read",
|
|
133
|
-
["models:write",
|
|
134
|
-
["configs:read",
|
|
135
|
-
["configs:write",
|
|
136
|
-
["apps:read",
|
|
137
|
-
["apps:write",
|
|
138
|
-
["events:read",
|
|
139
|
-
["targeting:debug",
|
|
140
|
-
["visitors:forward",
|
|
141
|
-
["apikeys:read",
|
|
142
|
-
["apikeys:write",
|
|
143
|
-
["organizations:read",
|
|
144
|
-
["organizations:write",
|
|
145
|
-
["organizations:create",
|
|
146
|
-
["organizations:delete",
|
|
147
|
-
["organizations:reach",
|
|
148
|
-
["members:read",
|
|
149
|
-
["members:write",
|
|
150
|
-
["roles:read",
|
|
151
|
-
["roles:write",
|
|
152
|
-
["projects:read",
|
|
153
|
-
["projects:write",
|
|
154
|
-
["project:
|
|
155
|
-
["
|
|
156
|
-
["projects:
|
|
157
|
-
["
|
|
158
|
-
["tenants:
|
|
159
|
-
["tenants:
|
|
160
|
-
["
|
|
161
|
-
["
|
|
162
|
-
["
|
|
163
|
-
["billing:
|
|
164
|
-
["
|
|
165
|
-
["
|
|
166
|
-
["controlkeys:
|
|
167
|
-
["
|
|
168
|
-
["tenanthooks:
|
|
169
|
-
["
|
|
170
|
-
["
|
|
171
|
-
["
|
|
172
|
-
["platform:
|
|
173
|
-
["platform:
|
|
174
|
-
["
|
|
175
|
-
["users:
|
|
176
|
-
["users:
|
|
177
|
-
["users:
|
|
178
|
-
["users:
|
|
179
|
-
["
|
|
180
|
-
["identities:
|
|
181
|
-
["
|
|
182
|
-
["usersecrets:
|
|
183
|
-
["usersecrets:
|
|
184
|
-
["usersecrets:
|
|
185
|
-
["
|
|
186
|
-
["
|
|
187
|
-
["signinproviders:
|
|
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", [], []]
|
|
188
189
|
];
|
|
189
190
|
|
|
190
191
|
// src/auth/grants.ts
|
|
192
|
+
var FIELD_PERMISSIONS = ["records:read", "records:write"];
|
|
193
|
+
var FIELD_KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
191
194
|
var index;
|
|
192
195
|
function catalogue() {
|
|
193
196
|
if (!index) {
|
|
@@ -223,8 +226,12 @@ function parseGrant(s) {
|
|
|
223
226
|
let target = "";
|
|
224
227
|
if (rest.startsWith(":")) {
|
|
225
228
|
target = rest.slice(1);
|
|
226
|
-
if (
|
|
229
|
+
if (permission[1] === "none") return null;
|
|
227
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
|
+
}
|
|
228
235
|
}
|
|
229
236
|
return { permission, reach, target };
|
|
230
237
|
}
|
|
@@ -577,7 +584,7 @@ async function requireScopedToken(request, options) {
|
|
|
577
584
|
// header, so the client presents exactly what this guard accepted.
|
|
578
585
|
dispatch: { token: () => token, context: rawContext },
|
|
579
586
|
orgId: typeof level === "string" ? level : null,
|
|
580
|
-
...typeof claims.cid === "string" && claims.cid !== "" ? {
|
|
587
|
+
...typeof claims.cid === "string" && claims.cid !== "" ? { userId: claims.cid } : {},
|
|
581
588
|
expiresAt: new Date(claims.exp * 1e3),
|
|
582
589
|
tokenId: claims.jti
|
|
583
590
|
};
|
|
@@ -639,9 +646,9 @@ async function readDispatch(request) {
|
|
|
639
646
|
if (!isObject(context)) refuse(`body.context is ${describe(context)}, expected an object`);
|
|
640
647
|
requireString(context, "tenantId", "body.context");
|
|
641
648
|
requireString(context, "projectId", "body.context");
|
|
642
|
-
requireString(context, "
|
|
649
|
+
requireString(context, "actorId", "body.context", true);
|
|
643
650
|
requireString(context, "timestamp", "body.context");
|
|
644
|
-
if (context.
|
|
651
|
+
if (context.userId !== void 0) requireString(context, "userId", "body.context");
|
|
645
652
|
const input = body.input;
|
|
646
653
|
if (input !== null && !isObject(input)) {
|
|
647
654
|
refuse(`body.input is ${describe(input)}, expected an object`);
|
|
@@ -862,8 +869,8 @@ var FoirAuthDowngradedError = class extends FoirRequestError {
|
|
|
862
869
|
downgrade;
|
|
863
870
|
constructor(downgrade) {
|
|
864
871
|
super(
|
|
865
|
-
`[foir] your
|
|
866
|
-
{ graphQLErrors: [{ message: "
|
|
872
|
+
`[foir] your user token was ${downgrade.userToken} (${downgrade.reason}), so the request was served as ${downgrade.servedAs}`,
|
|
873
|
+
{ graphQLErrors: [{ message: "user token rejected", extensions: { code: "AUTH_DOWNGRADED" } }] }
|
|
867
874
|
);
|
|
868
875
|
this.name = "FoirAuthDowngradedError";
|
|
869
876
|
this.downgrade = downgrade;
|
|
@@ -871,7 +878,7 @@ var FoirAuthDowngradedError = class extends FoirRequestError {
|
|
|
871
878
|
};
|
|
872
879
|
var FoirSessionExpiredError = class extends FoirRequestError {
|
|
873
880
|
constructor(options) {
|
|
874
|
-
super("[foir] the
|
|
881
|
+
super("[foir] the user session has expired and could not be refreshed: sign in again", {
|
|
875
882
|
cause: options?.cause,
|
|
876
883
|
graphQLErrors: [{ message: "session expired", extensions: { code: "SESSION_EXPIRED" } }]
|
|
877
884
|
});
|
|
@@ -1037,10 +1044,7 @@ async function hmacSha256Hex(secret, body) {
|
|
|
1037
1044
|
var KEY_PREFIXES = [
|
|
1038
1045
|
["foir_pub_", "publishable"],
|
|
1039
1046
|
["foir_srv_", "server"],
|
|
1040
|
-
["foir_adm_", "admin"]
|
|
1041
|
-
["pk_", "publishable"],
|
|
1042
|
-
["sk_", "older"],
|
|
1043
|
-
["tc_", "older"]
|
|
1047
|
+
["foir_adm_", "admin"]
|
|
1044
1048
|
];
|
|
1045
1049
|
function foirKeyKind(apiKey) {
|
|
1046
1050
|
if (typeof apiKey !== "string") return void 0;
|
|
@@ -1131,7 +1135,7 @@ var FoirSocketTransport = class {
|
|
|
1131
1135
|
constructor(config) {
|
|
1132
1136
|
if (config.dispatch !== void 0) {
|
|
1133
1137
|
throw new FoirRequestError(
|
|
1134
|
-
"[foir] a client built from a dispatch token cannot subscribe. The subscription socket authenticates an api key,
|
|
1138
|
+
"[foir] a client built from a dispatch token cannot subscribe. The subscription socket authenticates an api key, a console token or a user bearer, and has no path for a dispatch token. Queries and mutations made as the operation go to /graphql and work as they are."
|
|
1135
1139
|
);
|
|
1136
1140
|
}
|
|
1137
1141
|
this.config = config;
|
|
@@ -1495,12 +1499,12 @@ function assertOperatorKey(apiKey) {
|
|
|
1495
1499
|
const kind = foirKeyKind(key);
|
|
1496
1500
|
if (kind === "publishable" || kind === "server") {
|
|
1497
1501
|
throw new FoirRequestError(
|
|
1498
|
-
`[foir] createEventsClient: the event log is
|
|
1502
|
+
`[foir] createEventsClient: the event log is a project surface and a ${kind} key can never hold ${FOIR_EVENTS_SCOPE}: it is not mintable on an app's key. Use an admin key from a machine of your own. The log names which user acted on records that whoever reads an app's key cannot themselves read.`
|
|
1499
1503
|
);
|
|
1500
1504
|
}
|
|
1501
1505
|
if (kind === void 0) {
|
|
1502
1506
|
throw new FoirRequestError(
|
|
1503
|
-
`[foir] createEventsClient: apiKey must be an admin key, got a key starting "${key.slice(0, 4)}". The event log is reachable by an admin key or
|
|
1507
|
+
`[foir] createEventsClient: apiKey must be an admin key, got a key starting "${key.slice(0, 4)}". The event log is reachable by an admin key or a console session only.`
|
|
1504
1508
|
);
|
|
1505
1509
|
}
|
|
1506
1510
|
return key;
|
|
@@ -1510,7 +1514,7 @@ function assertNoBearer(headers) {
|
|
|
1510
1514
|
for (const name of Object.keys(headers)) {
|
|
1511
1515
|
if (name.toLowerCase() === "authorization") {
|
|
1512
1516
|
throw new FoirRequestError(
|
|
1513
|
-
`[foir] createEventsClient: an Authorization header is not accepted. A
|
|
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.`
|
|
1514
1518
|
);
|
|
1515
1519
|
}
|
|
1516
1520
|
}
|