@frockbot/cloudflare 0.7.294 → 0.7.295

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.
@@ -121,9 +121,13 @@ the local Worker, and every generated config overrides it from the profile.
121
121
 
122
122
  Some fields keep a placeholder rather than nothing: wrangler's validator refuses a `services` entry with no target and a `vectorize` entry with no index even in a config it never deploys, so the app Worker's two services and its Vectorize binding, and the admin portal's one service, all say `named-by-deployment-config`. Nothing reads those values — `wrangler dev --env development` and the `e2e` harness resolve their own environments, and the generator writes the deployment's own names. A bucket or database name is left out entirely, because wrangler does not ask for one.
123
123
 
124
- `adminEmails` is in the schema and in no generated config. It is the
125
- `FROCKBOT_ADMIN_EMAILS` secret the installer sets; the hosted deployment already
126
- carries it as a repository secret, which is why `hosted.json` omits it.
124
+ `adminEmails` and `adminUserIds` are in the schema and in no generated config.
125
+ They are the `FROCKBOT_ADMIN_EMAILS` and `FROCKBOT_ADMIN_USER_IDS` secrets the
126
+ installer sets; the hosted deployment already carries the first as a repository
127
+ secret, which is why `hosted.json` omits it. An email makes an admin only once
128
+ the auth Package's provider verified it, so a deployment whose people have no
129
+ email names its admins by User id; `frockbot-deployment-config secrets` carries
130
+ `FROCKBOT_ADMIN_USER_IDS` like any other optional secret.
127
131
 
128
132
  ## Brand
129
133
 
@@ -181,13 +181,23 @@ export const DEPLOYMENT_PROFILE_SCHEMA_V1 = {
181
181
  },
182
182
  adminEmails: {
183
183
  description:
184
- "The deployment's admins: they bypass admission, and a debug Turn may act as one of them. The debug surface itself is gated on `DEBUG_TOKEN`. The installer sets it as the Worker secret `FROCKBOT_ADMIN_EMAILS`; no generated config carries it, which is why the hosted profile leaves it to the repository secret it already has.",
184
+ "The deployment's admins: they bypass admission, and a debug Turn may act as one of them. The debug surface itself is gated on `DEBUG_TOKEN`. Only a verified email counts. The installer sets it as the Worker secret `FROCKBOT_ADMIN_EMAILS`; no generated config carries it, which is why the hosted profile leaves it to the repository secret it already has.",
185
185
  type: "array",
186
186
  minItems: 1,
187
187
  items: {
188
188
  $ref: "#/$defs/email",
189
189
  },
190
190
  },
191
+ adminUserIds: {
192
+ description:
193
+ "Admins by User id, beside `adminEmails`, for a deployment whose people have no verified email: an id here is an admin exactly as a verified email on `adminEmails` is. The installer sets it as the Worker secret `FROCKBOT_ADMIN_USER_IDS`; no generated config carries it.",
194
+ type: "array",
195
+ minItems: 1,
196
+ items: {
197
+ type: "string",
198
+ pattern: "^[^,\\s]+$",
199
+ },
200
+ },
191
201
  d1DatabaseId: {
192
202
  description:
193
203
  "The `AUTH_DB` database. Required by the `better-auth` Package unless the deploy supplies it with `--d1-database-id`, which is how a disposable stage names a database it creates in the same job.",
@@ -164,13 +164,22 @@
164
164
  }
165
165
  },
166
166
  "adminEmails": {
167
- "description": "The deployment's admins: they bypass admission, and a debug Turn may act as one of them. The debug surface itself is gated on `DEBUG_TOKEN`. The installer sets it as the Worker secret `FROCKBOT_ADMIN_EMAILS`; no generated config carries it, which is why the hosted profile leaves it to the repository secret it already has.",
167
+ "description": "The deployment's admins: they bypass admission, and a debug Turn may act as one of them. The debug surface itself is gated on `DEBUG_TOKEN`. Only a verified email counts. The installer sets it as the Worker secret `FROCKBOT_ADMIN_EMAILS`; no generated config carries it, which is why the hosted profile leaves it to the repository secret it already has.",
168
168
  "type": "array",
169
169
  "minItems": 1,
170
170
  "items": {
171
171
  "$ref": "#/$defs/email"
172
172
  }
173
173
  },
174
+ "adminUserIds": {
175
+ "description": "Admins by User id, beside `adminEmails`, for a deployment whose people have no verified email: an id here is an admin exactly as a verified email on `adminEmails` is. The installer sets it as the Worker secret `FROCKBOT_ADMIN_USER_IDS`; no generated config carries it.",
176
+ "type": "array",
177
+ "minItems": 1,
178
+ "items": {
179
+ "type": "string",
180
+ "pattern": "^[^,\\s]+$"
181
+ }
182
+ },
174
183
  "d1DatabaseId": {
175
184
  "description": "The `AUTH_DB` database. Required by the `better-auth` Package unless the deploy supplies it with `--d1-database-id`, which is how a disposable stage names a database it creates in the same job.",
176
185
  "type": "string",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frockbot/cloudflare",
3
- "version": "0.7.294",
3
+ "version": "0.7.295",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -23,12 +23,12 @@
23
23
  "typecheck": "tsc --noEmit -p tsconfig.json && tsc --noEmit -p tsconfig.access.json && tsc --noEmit -p test/tsconfig.json && tsc --noEmit -p e2e/tsconfig.json"
24
24
  },
25
25
  "dependencies": {
26
- "@frockbot/app": "0.7.294",
27
- "@frockbot/applets": "0.7.294",
28
- "@frockbot/computer": "0.7.294",
29
- "@frockbot/core": "0.7.294",
30
- "@frockbot/frock-compose": "0.7.294",
31
- "@frockbot/providers": "0.7.294",
26
+ "@frockbot/app": "0.7.295",
27
+ "@frockbot/applets": "0.7.295",
28
+ "@frockbot/computer": "0.7.295",
29
+ "@frockbot/core": "0.7.295",
30
+ "@frockbot/frock-compose": "0.7.295",
31
+ "@frockbot/providers": "0.7.295",
32
32
  "@rive-app/flutter-native-wasm": "44.0.0",
33
33
  "agents": "0.24.0",
34
34
  "ai": "^7.0.107",
@@ -13,14 +13,38 @@ export interface GatewayIdentityV1 {
13
13
  mode: "better-auth" | "development";
14
14
  }
15
15
 
16
+ /**
17
+ * The deployment's administrators by User id, as `FROCKBOT_ADMIN_USER_IDS`
18
+ * spells them: comma-separated and trimmed. A User id is opaque, so it is
19
+ * compared exactly. It is how a deployment whose people have no email names
20
+ * an admin.
21
+ */
22
+ export function adminUserIdsV1(value: string | undefined): ReadonlySet<string> {
23
+ return new Set(
24
+ (value ?? "")
25
+ .split(",")
26
+ .map((id) => id.trim())
27
+ .filter((id) => id.length > 0),
28
+ );
29
+ }
30
+
31
+ /** The two allowlists, as the deployment's secrets carry them. */
32
+ export interface DeploymentAdminsV1 {
33
+ emails?: string;
34
+ userIds?: string;
35
+ }
36
+
16
37
  export function isDeploymentAdminV1(
17
38
  identity: GatewayIdentityV1,
18
- configuredEmails: string | undefined,
39
+ configured: DeploymentAdminsV1,
19
40
  ): boolean {
20
41
  if (identity.mode === "development" && identity.id === DEVELOPMENT_USER_ID) {
21
42
  return true;
22
43
  }
23
- const emails = adminEmailsV1(configuredEmails);
44
+ const userIds = adminUserIdsV1(configured.userIds);
45
+ // A development id is whatever `?as_user=` said, so only a signed-in one counts.
46
+ if (identity.mode === "better-auth" && userIds.has(identity.id)) return true;
47
+ const emails = adminEmailsV1(configured.emails);
24
48
  if (
25
49
  identity.emailVerified === true &&
26
50
  identity.email &&
@@ -28,5 +52,7 @@ export function isDeploymentAdminV1(
28
52
  ) {
29
53
  return true;
30
54
  }
31
- return identity.mode === "development" && emails.size === 0;
55
+ return (
56
+ identity.mode === "development" && emails.size === 0 && userIds.size === 0
57
+ );
32
58
  }
package/src/contracts.ts CHANGED
@@ -785,8 +785,9 @@ export interface GatewayDependencies {
785
785
  admitAccount(
786
786
  identity: AdmissionIdentityV1,
787
787
  ): Promise<AccountAdmissionDecisionV1>;
788
- /** Raw deployment secret; only the derived `isAdmin` boolean reaches clients. */
788
+ /** Raw deployment secrets; only the derived `isAdmin` boolean reaches clients. */
789
789
  adminEmails?: string;
790
+ adminUserIds?: string;
790
791
  applicationHashFor(userId: string): Promise<string>;
791
792
  botStateFor(userId: string): UserBotStateBinding;
792
793
  userConfigurationFor(userId: string): UserConfigurationBinding;
package/src/gateway.ts CHANGED
@@ -435,7 +435,10 @@ async function identifyRequest(
435
435
  emailVerified: session?.user.emailVerified === true,
436
436
  mode: development.userId ? "development" : "better-auth",
437
437
  },
438
- dependencies.adminEmails,
438
+ {
439
+ emails: dependencies.adminEmails,
440
+ userIds: dependencies.adminUserIds,
441
+ },
439
442
  );
440
443
  // Every authenticated request asks, so pausing an account takes effect on
441
444
  // its next request rather than at its next sign-in. An admin is never
package/src/index.ts CHANGED
@@ -163,6 +163,7 @@ import {
163
163
  import {
164
164
  DEVELOPMENT_USER_ID,
165
165
  isDeploymentAdminV1,
166
+ type DeploymentAdminsV1,
166
167
  } from "./admin-identities.js";
167
168
  import type { DebugGatewaySurface } from "./debug.js";
168
169
  import type { BotDebugQueryV1 } from "@frockbot/app/shell/debug-protocol";
@@ -369,6 +370,8 @@ interface Env {
369
370
  MACHINE_TOKEN_SECRET?: string;
370
371
  ALLOW_DEVELOPMENT_AUTH?: string;
371
372
  FROCKBOT_ADMIN_EMAILS?: string;
373
+ /** Admins by User id, for a deployment whose people have no email. */
374
+ FROCKBOT_ADMIN_USER_IDS?: string;
372
375
  ALLOWED_CLIENT_ORIGINS?: string;
373
376
  /** Authorizes `/api/debug/*`. Absent disables the surface entirely. */
374
377
  DEBUG_TOKEN?: string;
@@ -432,7 +435,7 @@ function debugSurface(env: Env): DebugGatewaySurface {
432
435
  (await getAgentByName(env.VOICE_ASSISTANTS, userId)).debugSnapshot(),
433
436
  isAdminUser: async (userId) => {
434
437
  // The auth Package's store is the durable identity source. The path's
435
- // User id is never trusted on its own: it must resolve to a stored email,
438
+ // User id is never trusted on its own: it must resolve to a stored identity,
436
439
  // and that identity is evaluated by the same allowlist policy as the
437
440
  // signed-in gateway. A Package that stores nothing resolves nothing, so
438
441
  // the one write on this surface is refused rather than granted on the
@@ -447,7 +450,7 @@ function debugSurface(env: Env): DebugGatewaySurface {
447
450
  emailVerified: identity.emailVerified,
448
451
  mode: "better-auth",
449
452
  },
450
- env.FROCKBOT_ADMIN_EMAILS,
453
+ deploymentAdmins(env),
451
454
  )
452
455
  );
453
456
  },
@@ -841,6 +844,13 @@ async function admitAccount(
841
844
  );
842
845
  }
843
846
 
847
+ function deploymentAdmins(env: Env): DeploymentAdminsV1 {
848
+ return {
849
+ emails: env.FROCKBOT_ADMIN_EMAILS,
850
+ userIds: env.FROCKBOT_ADMIN_USER_IDS,
851
+ };
852
+ }
853
+
844
854
  async function storedAdmissionIdentity(
845
855
  env: Env,
846
856
  userId: string,
@@ -860,7 +870,7 @@ async function storedAdmissionIdentity(
860
870
  emailVerified: identity.emailVerified,
861
871
  mode: "better-auth",
862
872
  },
863
- env.FROCKBOT_ADMIN_EMAILS,
873
+ deploymentAdmins(env),
864
874
  ),
865
875
  };
866
876
  }
@@ -930,7 +940,8 @@ async function mayCreateIdentity(
930
940
  emailVerified: candidate.emailVerified,
931
941
  mode: "better-auth",
932
942
  },
933
- env.FROCKBOT_ADMIN_EMAILS,
943
+ // No User exists yet, so only the email list can name one.
944
+ { emails: env.FROCKBOT_ADMIN_EMAILS },
934
945
  )
935
946
  ) {
936
947
  return true;
@@ -2754,6 +2765,9 @@ export default {
2754
2765
  ...(env.FROCKBOT_ADMIN_EMAILS
2755
2766
  ? { adminEmails: env.FROCKBOT_ADMIN_EMAILS }
2756
2767
  : {}),
2768
+ ...(env.FROCKBOT_ADMIN_USER_IDS
2769
+ ? { adminUserIds: env.FROCKBOT_ADMIN_USER_IDS }
2770
+ : {}),
2757
2771
  applicationHashFor: async () => env.DEFAULT_APPLICATION_HASH,
2758
2772
  botStateFor: (userId) =>
2759
2773
  runtimeExports.UserBotState({ props: { userId } }),
@@ -221,6 +221,12 @@ export const OPTIONAL_PRODUCTION_SECRETS_V1: readonly OptionalProductionSecretV1
221
221
  degraded:
222
222
  "nobody can administer beta access, so admission stays closed and only accounts already granted access can sign in",
223
223
  },
224
+ {
225
+ name: "FROCKBOT_ADMIN_USER_IDS",
226
+ why: "Admins by User id, beside `FROCKBOT_ADMIN_EMAILS`, for a deployment whose people have no verified email.",
227
+ degraded:
228
+ "only a verified email on `FROCKBOT_ADMIN_EMAILS` makes an admin",
229
+ },
224
230
  {
225
231
  name: "DEBUG_TOKEN",
226
232
  why: "Authorizes the read-only `/api/debug` operator surface.",