@protobase/server 0.6.0 → 0.8.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.
Files changed (105) hide show
  1. package/dist/assistant/conversations.d.ts +1 -1
  2. package/dist/assistant/tools.d.ts +1 -1
  3. package/dist/assistant/turn.d.ts +2 -2
  4. package/dist/audit/origin.js +12 -0
  5. package/dist/auth/jwt.d.ts +2 -0
  6. package/dist/auth/jwt.js +5 -3
  7. package/dist/batch/reorder.js +1 -1
  8. package/dist/better-auth/auth-schema.d.ts +4 -2
  9. package/dist/better-auth/auth-schema.js +18 -8
  10. package/dist/better-auth/authenticator.d.ts +7 -3
  11. package/dist/better-auth/authenticator.js +24 -3
  12. package/dist/better-auth/create-auth.d.ts +683 -10
  13. package/dist/better-auth/create-auth.js +72 -27
  14. package/dist/better-auth/organizations/accept-invitation.js +80 -0
  15. package/dist/better-auth/organizations/active-organization.js +12 -0
  16. package/dist/better-auth/organizations/admin-routes.js +83 -0
  17. package/dist/better-auth/organizations/app-roles.js +43 -0
  18. package/dist/better-auth/organizations/host.d.ts +53 -0
  19. package/dist/better-auth/organizations/host.js +251 -0
  20. package/dist/better-auth/organizations/invitation-endpoints.js +133 -0
  21. package/dist/better-auth/organizations/invitation-mail.js +17 -0
  22. package/dist/better-auth/organizations/invitation-token.js +27 -0
  23. package/dist/better-auth/organizations/member-endpoints.js +182 -0
  24. package/dist/better-auth/organizations/options.d.ts +48 -0
  25. package/dist/better-auth/organizations/options.js +23 -0
  26. package/dist/better-auth/organizations/organization-plugin.js +128 -0
  27. package/dist/better-auth/organizations/protobase-plugin.js +21 -0
  28. package/dist/better-auth/organizations/role-definitions.d.ts +15 -0
  29. package/dist/better-auth/organizations/role-definitions.js +56 -0
  30. package/dist/better-auth/organizations/session-organization.js +68 -0
  31. package/dist/better-auth/organizations/store.d.ts +19 -0
  32. package/dist/better-auth/organizations/store.js +73 -0
  33. package/dist/better-auth/policy-store.js +44 -35
  34. package/dist/better-auth/snake-case-migration.js +122 -0
  35. package/dist/better-auth/snake-case-names.js +72 -0
  36. package/dist/better-auth/staff-checks.js +5 -1
  37. package/dist/better-auth/staff-log.js +56 -45
  38. package/dist/better-auth/staff-sign-in-endpoints.js +1 -1
  39. package/dist/better-auth/status-route.js +14 -3
  40. package/dist/better-auth/users.d.ts +2 -1
  41. package/dist/better-auth/users.js +4 -1
  42. package/dist/create-admin.d.ts +8 -2
  43. package/dist/create-admin.js +31 -2
  44. package/dist/deps.d.ts +1 -0
  45. package/dist/files/attach.js +80 -0
  46. package/dist/files/cleanup-runner.d.ts +8 -0
  47. package/dist/files/cleanup-runner.js +58 -0
  48. package/dist/files/cleanup-schedule.d.ts +28 -0
  49. package/dist/files/cleanup-schedule.js +48 -0
  50. package/dist/files/detect.js +51 -0
  51. package/dist/files/download-url.js +43 -0
  52. package/dist/files/download.js +90 -0
  53. package/dist/files/duration.js +35 -0
  54. package/dist/files/file-cleanup.d.ts +18 -0
  55. package/dist/files/file-cleanup.js +17 -0
  56. package/dist/files/local-files.d.ts +17 -0
  57. package/dist/files/local-files.js +101 -0
  58. package/dist/files/media-types.js +99 -0
  59. package/dist/files/options.d.ts +15 -0
  60. package/dist/files/present.d.ts +15 -0
  61. package/dist/files/present.js +59 -0
  62. package/dist/files/processors.d.ts +15 -0
  63. package/dist/files/processors.js +39 -0
  64. package/dist/files/providers.js +70 -0
  65. package/dist/files/routes.js +26 -0
  66. package/dist/files/runtime.d.ts +3 -0
  67. package/dist/files/runtime.js +37 -0
  68. package/dist/files/signer.js +32 -0
  69. package/dist/files/store.d.ts +37 -0
  70. package/dist/files/ticket.js +87 -0
  71. package/dist/files/upload-body.js +85 -0
  72. package/dist/files/upload.js +124 -0
  73. package/dist/files/uri.js +47 -0
  74. package/dist/functions/define-function.d.ts +71 -0
  75. package/dist/functions/define-function.js +36 -0
  76. package/dist/functions/function-app.d.ts +17 -0
  77. package/dist/functions/function-app.js +19 -0
  78. package/dist/functions/read-json.d.ts +35 -0
  79. package/dist/functions/read-json.js +24 -0
  80. package/dist/functions/records.d.ts +43 -0
  81. package/dist/functions/records.js +31 -0
  82. package/dist/functions/require-env.d.ts +7 -0
  83. package/dist/functions/require-env.js +12 -0
  84. package/dist/functions/routes.js +60 -0
  85. package/dist/functions/verify-signature.d.ts +32 -0
  86. package/dist/functions/verify-signature.js +49 -0
  87. package/dist/index.d.ts +21 -3
  88. package/dist/index.js +13 -3
  89. package/dist/list-service.js +4 -2
  90. package/dist/openapi/resource-paths.js +78 -1
  91. package/dist/openapi/schemas.js +28 -3
  92. package/dist/project-config.d.ts +7 -1
  93. package/dist/project-config.js +37 -1
  94. package/dist/read-service.js +5 -1
  95. package/dist/request-access.d.ts +5 -0
  96. package/dist/request-access.js +9 -4
  97. package/dist/resource-source.d.ts +4 -1
  98. package/dist/routes/batch.js +1 -1
  99. package/dist/routes/collection.js +3 -1
  100. package/dist/routes/record.js +2 -5
  101. package/dist/types.d.ts +25 -5
  102. package/dist/write-pipeline.js +2 -1
  103. package/dist/write-shared.js +6 -3
  104. package/dist/write-update.js +2 -1
  105. package/package.json +6 -4
@@ -1,5 +1,5 @@
1
- import { ChatMessage } from "./chat-completions.js";
2
1
  import { Session } from "../types.js";
2
+ import { ChatMessage } from "./chat-completions.js";
3
3
  import { ConversationStore } from "./conversation-store.js";
4
4
  import { AssistantEvent, AssistantState } from "@protobase/schema";
5
5
  //#region src/assistant/conversations.d.ts
@@ -1,7 +1,7 @@
1
1
  import { Session } from "../types.js";
2
+ import { ToolRecords } from "./records.js";
2
3
  import { Conversation } from "./conversations.js";
3
4
  import { ApprovalAnswer, ApprovalRequest } from "./approval.js";
4
- import { ToolRecords } from "./records.js";
5
5
  import { AssistantPart } from "@protobase/schema";
6
6
  //#region src/assistant/tools.d.ts
7
7
  export type ToolContext = {
@@ -1,7 +1,7 @@
1
- import { ModelSettings, ReasoningEffort } from "./chat-completions.js";
2
1
  import { Session } from "../types.js";
3
- import { Conversation } from "./conversations.js";
4
2
  import { ToolRecords } from "./records.js";
3
+ import { ModelSettings, ReasoningEffort } from "./chat-completions.js";
4
+ import { Conversation } from "./conversations.js";
5
5
  import { AssistantTool } from "./tools.js";
6
6
  //#region src/assistant/turn.d.ts
7
7
  export type TurnOptions = {
@@ -0,0 +1,12 @@
1
+ //#region src/audit/origin.ts
2
+ /** Where a request came from, for an audit event: its user agent and the proxies' `X-Forwarded-For`, when sent. */
3
+ const auditOrigin = (headers) => {
4
+ const userAgent = headers.get("user-agent");
5
+ const forwardedFor = headers.get("x-forwarded-for");
6
+ return {
7
+ ...userAgent !== null && { userAgent },
8
+ ...forwardedFor !== null && { forwardedFor }
9
+ };
10
+ };
11
+ //#endregion
12
+ export { auditOrigin };
@@ -9,6 +9,8 @@ export type JwtOptions = {
9
9
  audience?: string;
10
10
  /** Claim holding the roles, an array, or a string separated by spaces or commas. Default `roles`. */
11
11
  rolesClaim?: string;
12
+ /** A second claim whose roles are added to those of `rolesClaim`, such as an organization membership's. */
13
+ extraRolesClaim?: string;
12
14
  /** Claim holding the tenant. Default `tenant`. */
13
15
  tenantClaim?: string;
14
16
  /** Accepted signature algorithms. Default: asymmetric ones only, so a token can never be "verified" with a public key used as an HMAC secret. */
package/dist/auth/jwt.js CHANGED
@@ -25,21 +25,23 @@ const jwtAuthenticator = (options) => {
25
25
  return async (request) => {
26
26
  const [scheme, token] = (request.headers.get("authorization") ?? "").split(" ");
27
27
  if (scheme?.toLowerCase() !== "bearer" || !token) throw unauthorized("Send a bearer token in the Authorization header");
28
- const { sub, [tenantClaim]: tenant, [rolesClaim]: roles } = (await jwtVerify(token, keys, {
28
+ const verified = await jwtVerify(token, keys, {
29
29
  algorithms: options.algorithms ?? asymmetric,
30
30
  ...options.issuer && { issuer: options.issuer },
31
31
  ...options.audience && { audience: options.audience }
32
32
  }).catch((error) => {
33
33
  if (error instanceof errors.JOSEError) throw unauthorized("The bearer token is invalid or expired");
34
34
  throw error;
35
- })).payload;
35
+ });
36
+ const { sub, [tenantClaim]: tenant, [rolesClaim]: roles } = verified.payload;
37
+ const extraRoles = options.extraRolesClaim === void 0 ? [] : rolesOf(verified.payload[options.extraRolesClaim]);
36
38
  if (!sub) throw unauthorized("The bearer token has no subject");
37
39
  const claimed = typeof tenant === "string" || typeof tenant === "number" ? tenant : void 0;
38
40
  const tenantValue = options.tenant === void 0 ? claimed : typeof options.tenant === "function" ? await options.tenant() : options.tenant;
39
41
  return {
40
42
  user: {
41
43
  id: sub,
42
- roles: rolesOf(roles)
44
+ roles: [.../* @__PURE__ */ new Set([...rolesOf(roles), ...extraRoles])]
43
45
  },
44
46
  ...tenantValue !== void 0 && { tenant: tenantValue }
45
47
  };
@@ -71,7 +71,7 @@ const reorderIn = async (trx, deps, access, input) => {
71
71
  const results = [];
72
72
  for (const [i, key] of input.keys.entries()) {
73
73
  const plan = plans.get(i);
74
- results.push(shown(access, plan ? await applyUpdate(trx, deps, access, key, plan) : current[i]));
74
+ results.push(await shown(access, plan ? await applyUpdate(trx, deps, access, key, plan) : current[i]));
75
75
  }
76
76
  return results;
77
77
  };
@@ -2,13 +2,15 @@ import { AdminAuth } from "./create-auth.js";
2
2
  //#region src/better-auth/auth-schema.d.ts
3
3
  /**
4
4
  * The SQL that brings the admin store up to what this version of Protobase and the project's Better Auth plugins need:
5
+ * for a store with the camelCase names of Protobase 0.6, their rename to snake_case (`snakeCaseMigration`); otherwise
5
6
  * new tables, columns and indexes, never a drop, each skipped where it exists. `undefined` when the store is current. A
6
7
  * change Better Auth cannot make safely, such as a required column without a default on a table with rows, is an error.
7
8
  */
8
9
  export declare const authSchemaMigration: (auth: AdminAuth) => Promise<string | undefined>;
9
10
  /**
10
- * Refuses to go on while the admin store lacks a table or column Better Auth needs, since every sign-in would fail.
11
- * The server never changes the schema itself: the project's migrations do, before the new version serves.
11
+ * Refuses to go on while the admin store lacks a table or column Better Auth needs, or still has the camelCase names
12
+ * of Protobase 0.6, since every sign-in would fail. The server never changes the schema itself: the project's
13
+ * migrations do, before the new version serves.
12
14
  */
13
15
  export declare const checkAuthSchema: (auth: AdminAuth) => Promise<void>;
14
16
  //#endregion
@@ -1,3 +1,4 @@
1
+ import { snakeCaseMigration } from "./snake-case-migration.js";
1
2
  import { getMigrations } from "better-auth/db/migration";
2
3
  //#region src/better-auth/auth-schema.ts
3
4
  /** The command that writes the pending changes as a migration file of the project. */
@@ -8,28 +9,37 @@ const authMigrationCommand = "protobase auth migration";
8
9
  */
9
10
  const skipExisting = (sql) => sql.split(";\n\n").map((statement) => statement.replace(/^create table (?!if not exists )/, "create table if not exists ").replace(/^create (unique )?index (?!if not exists )/, (_, unique) => `create ${unique ?? ""}index if not exists `).replace(/^(alter table .+? )add column (?!if not exists )/, "$1add column if not exists ")).join(";\n\n");
10
11
  /**
12
+ * Better Auth adds `email_verified` to a `user` table that exists only when the table still has the camelCase names
13
+ * of Protobase 0.6, whose `emailVerified` it does not know: such a store is renamed, never given new columns beside
14
+ * the old ones.
15
+ */
16
+ const hasCamelCaseNames = ({ toBeAdded }) => toBeAdded.some(({ table, fields }) => table === "user" && "email_verified" in fields);
17
+ const storeSchema = ({ options: { database } }) => database && "schemaName" in database ? database.schemaName : void 0;
18
+ /**
11
19
  * The SQL that brings the admin store up to what this version of Protobase and the project's Better Auth plugins need:
20
+ * for a store with the camelCase names of Protobase 0.6, their rename to snake_case (`snakeCaseMigration`); otherwise
12
21
  * new tables, columns and indexes, never a drop, each skipped where it exists. `undefined` when the store is current. A
13
22
  * change Better Auth cannot make safely, such as a required column without a default on a table with rows, is an error.
14
23
  */
15
24
  const authSchemaMigration = async (auth) => {
25
+ if (hasCamelCaseNames(await getMigrations(auth.options, { throwOnUnsafe: false }))) return snakeCaseMigration(storeSchema(auth));
16
26
  const { toBeCreated, toBeAdded, toBeAddedIndexes, compileMigrations } = await getMigrations(auth.options);
17
27
  if (toBeCreated.length === 0 && toBeAdded.length === 0 && toBeAddedIndexes.length === 0) return void 0;
18
28
  return skipExisting(await compileMigrations());
19
29
  };
20
30
  /** The tables and `table.column`s the admin store lacks; an index missing is not a reason to refuse requests. */
21
- const missingAuthSchema = async (auth) => {
22
- const { toBeCreated, toBeAdded } = await getMigrations(auth.options, { throwOnUnsafe: false });
23
- return [...toBeCreated.map(({ table }) => table), ...toBeAdded.flatMap(({ table, fields }) => Object.keys(fields).map((field) => `${table}.${field}`))];
24
- };
31
+ const missingTablesAndColumns = ({ toBeCreated, toBeAdded }) => [...toBeCreated.map(({ table }) => table), ...toBeAdded.flatMap(({ table, fields }) => Object.keys(fields).map((field) => `${table}.${field}`))];
25
32
  /**
26
- * Refuses to go on while the admin store lacks a table or column Better Auth needs, since every sign-in would fail.
27
- * The server never changes the schema itself: the project's migrations do, before the new version serves.
33
+ * Refuses to go on while the admin store lacks a table or column Better Auth needs, or still has the camelCase names
34
+ * of Protobase 0.6, since every sign-in would fail. The server never changes the schema itself: the project's
35
+ * migrations do, before the new version serves.
28
36
  */
29
37
  const checkAuthSchema = async (auth) => {
30
- const missing = await missingAuthSchema(auth);
38
+ const plan = await getMigrations(auth.options, { throwOnUnsafe: false });
39
+ if (hasCamelCaseNames(plan)) throw new Error(`The auth schema still has the camelCase names of Protobase 0.6, which this version names in snake_case. Write their rename as a migration with \`${authMigrationCommand}\`, apply it with the project's migrations, then start again.`);
40
+ const missing = missingTablesAndColumns(plan);
31
41
  if (missing.length === 0) return;
32
42
  throw new Error(`The auth schema is behind this version of Protobase (missing ${missing.join(", ")}). Write the change as a migration with \`${authMigrationCommand}\`, apply it with the project's migrations, then start again.`);
33
43
  };
34
44
  //#endregion
35
- export { authMigrationCommand, authSchemaMigration, checkAuthSchema, missingAuthSchema, skipExisting };
45
+ export { authMigrationCommand, authSchemaMigration, checkAuthSchema, skipExisting };
@@ -9,14 +9,18 @@ export type BetterAuthAuthenticatorOptions = {
9
9
  jwksUrl?: string | URL;
10
10
  /** Defaults to the auth instance's `baseURL`; it is both issuer and audience of its tokens. */
11
11
  issuer?: string;
12
- /** The one tenant everyone works in (organizations are not used yet); never read from the token. */
12
+ /** The one tenant everyone works in, for an app without organizations; never read from the token. */
13
13
  tenant?: TenantValue | (() => Promise<TenantValue | undefined>);
14
+ /** Whether the tokens carry organizations; read from `auth` when given. Needed with `jwksUrl` only. */
15
+ organizations?: boolean;
14
16
  /** How long the key set is cached; see `cachedKeys`. */
15
17
  cache?: JwksCacheOptions;
16
18
  };
17
19
  /**
18
- * Verifies the bearer JWT that Better Auth's jwt plugin issues. `sub` is the user id, the `role` claim
19
- * (a comma separated string from the admin plugin) becomes `roles`.
20
+ * Verifies the bearer JWT that Better Auth's jwt plugin issues. `sub` is the user id, the `role` claim (a comma
21
+ * separated string from the admin plugin) becomes `roles`. With organizations, the `org` claim is the tenant and the
22
+ * organization, and the membership's `app_roles` are added to the global roles; `user`, which stands for no global
23
+ * role, is left out.
20
24
  */
21
25
  export declare const betterAuthAuthenticator: (options: BetterAuthAuthenticatorOptions) => Authenticator;
22
26
  //#endregion
@@ -1,21 +1,42 @@
1
+ import { noGlobalRole } from "./organizations/role-definitions.js";
1
2
  import { jwtAuthenticator } from "../auth/jwt.js";
2
3
  import { cachedKeys } from "./jwks-cache.js";
3
4
  //#region src/better-auth/authenticator.ts
4
5
  /**
5
- * Verifies the bearer JWT that Better Auth's jwt plugin issues. `sub` is the user id, the `role` claim
6
- * (a comma separated string from the admin plugin) becomes `roles`.
6
+ * Verifies the bearer JWT that Better Auth's jwt plugin issues. `sub` is the user id, the `role` claim (a comma
7
+ * separated string from the admin plugin) becomes `roles`. With organizations, the `org` claim is the tenant and the
8
+ * organization, and the membership's `app_roles` are added to the global roles; `user`, which stands for no global
9
+ * role, is left out.
7
10
  */
8
11
  const betterAuthAuthenticator = (options) => {
9
12
  const issuer = options.issuer ?? options.auth?.options.baseURL;
10
13
  if (!issuer) throw new Error("betterAuthAuthenticator needs `issuer` (or an `auth` instance with a baseURL)");
11
14
  if (!options.auth && !options.jwksUrl) throw new Error("betterAuthAuthenticator needs `auth` or `jwksUrl`");
12
- return jwtAuthenticator({
15
+ const organizations = options.organizations ?? Boolean(options.auth?.organizations);
16
+ const verify = jwtAuthenticator({
13
17
  ...options.auth ? { keys: cachedKeys(() => options.auth.api.getJwks(), options.cache) } : { jwksUrl: options.jwksUrl },
14
18
  issuer,
15
19
  audience: issuer,
16
20
  rolesClaim: "role",
21
+ ...organizations && {
22
+ tenantClaim: "org",
23
+ extraRolesClaim: "app_roles"
24
+ },
17
25
  ...options.tenant !== void 0 && { tenant: options.tenant }
18
26
  });
27
+ if (!organizations) return verify;
28
+ return async (request) => {
29
+ const session = await verify(request);
30
+ const user = {
31
+ ...session.user,
32
+ roles: session.user.roles.filter((role) => role !== noGlobalRole)
33
+ };
34
+ return {
35
+ ...session,
36
+ user,
37
+ ...session.tenant !== void 0 && { organization: { id: session.tenant } }
38
+ };
39
+ };
19
40
  };
20
41
  //#endregion
21
42
  export { betterAuthAuthenticator };