@protobase/server 0.33.4 → 0.34.1

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 (80) hide show
  1. package/dist/assistant/query-tools.js +1 -1
  2. package/dist/auth/jwt.d.ts +15 -1
  3. package/dist/auth/jwt.js +41 -5
  4. package/dist/better-auth/authenticator.d.ts +16 -1
  5. package/dist/better-auth/authenticator.js +41 -24
  6. package/dist/better-auth/create-auth.d.ts +3063 -363
  7. package/dist/better-auth/create-auth.js +39 -20
  8. package/dist/better-auth/mcp/connected-apps.d.ts +13 -0
  9. package/dist/better-auth/mcp/connected-apps.js +86 -0
  10. package/dist/better-auth/mcp/consent-check.js +38 -0
  11. package/dist/better-auth/mcp/consent-reference.js +16 -0
  12. package/dist/better-auth/mcp/discovery-routes.js +19 -0
  13. package/dist/better-auth/mcp/grant-reference.js +25 -0
  14. package/dist/better-auth/mcp/mcp-plugins.d.ts +13 -0
  15. package/dist/better-auth/mcp/mcp-plugins.js +107 -0
  16. package/dist/better-auth/mcp/mcp-token-claims.js +39 -0
  17. package/dist/better-auth/mcp/request-defaults.js +57 -0
  18. package/dist/better-auth/mcp/revoke-grants.js +75 -0
  19. package/dist/better-auth/mcp/setup-gate.js +38 -0
  20. package/dist/better-auth/mcp/signed-query-guard.js +29 -0
  21. package/dist/better-auth/new-account-password.d.ts +6 -0
  22. package/dist/better-auth/new-account-password.js +6 -3
  23. package/dist/better-auth/origins.d.ts +7 -0
  24. package/dist/better-auth/origins.js +8 -1
  25. package/dist/better-auth/password-reset.js +4 -3
  26. package/dist/better-auth/policy-plugin.js +1 -24
  27. package/dist/better-auth/sign-in-enforcement.js +6 -1
  28. package/dist/better-auth/sign-in-setup.js +38 -0
  29. package/dist/better-auth/sign-up/sign-up-plugin.js +1 -1
  30. package/dist/better-auth/snake-case-names.js +102 -1
  31. package/dist/better-auth/staff-checks.js +9 -2
  32. package/dist/better-auth/staff-plugin.js +9 -2
  33. package/dist/better-auth/status-route.js +2 -1
  34. package/dist/better-auth/token-claims.js +19 -0
  35. package/dist/better-auth/users.d.ts +4 -1
  36. package/dist/better-auth/users.js +6 -1
  37. package/dist/check-roles.js +1 -1
  38. package/dist/create-admin.js +8 -0
  39. package/dist/deps.d.ts +1 -0
  40. package/dist/encryption/apply.d.ts +40 -0
  41. package/dist/encryption/apply.js +99 -0
  42. package/dist/encryption/binding.d.ts +2 -0
  43. package/dist/encryption/binding.js +53 -0
  44. package/dist/encryption/capture-off.js +12 -0
  45. package/dist/encryption/cascading-keys.d.ts +9 -0
  46. package/dist/encryption/cascading-keys.js +21 -0
  47. package/dist/encryption/envelope-open.d.ts +5 -0
  48. package/dist/encryption/envelope-open.js +57 -0
  49. package/dist/encryption/envelope-seal.js +38 -0
  50. package/dist/encryption/keyring.js +60 -0
  51. package/dist/encryption/mask-stored.js +16 -0
  52. package/dist/encryption/rebind-check.js +28 -0
  53. package/dist/encryption/remnants.js +53 -0
  54. package/dist/encryption/runtime.d.ts +1 -0
  55. package/dist/encryption/runtime.js +18 -0
  56. package/dist/encryption/scan.d.ts +4 -0
  57. package/dist/encryption/scan.js +61 -0
  58. package/dist/encryption/seal-values.js +30 -0
  59. package/dist/encryption/shared-columns.js +19 -0
  60. package/dist/encryption/status.d.ts +39 -0
  61. package/dist/encryption/status.js +92 -0
  62. package/dist/encryption/stored-value.js +16 -0
  63. package/dist/encryption/tools.d.ts +26 -0
  64. package/dist/encryption/tools.js +34 -0
  65. package/dist/forms/check-forms.js +1 -1
  66. package/dist/gotrue/user-routes.js +6 -0
  67. package/dist/index.d.ts +10 -5
  68. package/dist/index.js +4 -2
  69. package/dist/records.js +3 -3
  70. package/dist/request-access.js +9 -5
  71. package/dist/rest/encrypted-columns.js +37 -0
  72. package/dist/rest/table-routes.js +25 -13
  73. package/dist/reveal-service.js +17 -3
  74. package/dist/types.d.ts +5 -0
  75. package/dist/validate-body.js +4 -2
  76. package/dist/webhooks/subscriber-view.js +14 -1
  77. package/dist/write-pipeline.js +14 -2
  78. package/dist/write-shared.js +7 -3
  79. package/dist/write-update.js +12 -2
  80. package/package.json +11 -7
@@ -1,11 +1,13 @@
1
1
  import { HttpProblem, badRequest } from "./problem.js";
2
2
  import { tenantScope } from "./tenant.js";
3
3
  import { requestAccess, requireOperation, targetFilter, writableFields } from "./request-access.js";
4
- import { deleteRecord, fetchRecord, insertRecord, restoreRecord } from "./records.js";
4
+ import { bindingOf, pendingBinding } from "./encryption/binding.js";
5
+ import { deleteRecord, fetchRecord, insertRecord, restoreRecord, updateRecord } from "./records.js";
5
6
  import { writeTransaction } from "./transactions.js";
6
7
  import { blockedDelete } from "./blocked-delete.js";
7
8
  import { isDatabaseError } from "./error-handler.js";
8
9
  import { attachFiles } from "./files/attach.js";
10
+ import { encryptedValuesOf, hasEncryptedValues, sealValues } from "./encryption/seal-values.js";
9
11
  import { actorSettings, wakeDispatch } from "./changes/actor.js";
10
12
  import { validateBody } from "./validate-body.js";
11
13
  import { assertWritten, checkParents, checkPrecondition, decide, denied, firstKeyField, keyOf, missing, notifier, shown } from "./write-shared.js";
@@ -21,6 +23,14 @@ const withOwner = (access, values) => {
21
23
  [owner]: caller
22
24
  };
23
25
  };
26
+ const bindEncrypted = async (trx, deps, access, row, inserted, shape) => {
27
+ const { entry, full } = access;
28
+ if (!hasEncryptedValues(full, row)) return inserted;
29
+ const values = sealValues(deps.encryption, full, encryptedValuesOf(full, row), (field) => bindingOf(full, field, inserted.record));
30
+ const bound = await updateRecord(trx, entry, keyOf(access, inserted.record), tenantScope(entry, access.session)?.tenantValue, values, shape);
31
+ if (!bound) throw new Error(`A ${entry.name} record was created whose key the API cannot address, so its encrypted fields cannot be bound to it`);
32
+ return bound;
33
+ };
24
34
  const create = async (trx, deps, access, body) => {
25
35
  const { entry, full } = access;
26
36
  const scope = tenantScope(entry, access.session);
@@ -33,7 +43,9 @@ const create = async (trx, deps, access, body) => {
33
43
  const filter = await decide(access, "create", void 0, row);
34
44
  const parents = checkParents(trx, access, "create");
35
45
  await parents(row);
36
- const created = await insertRecord(trx, entry, row, { etagFields: Object.keys(access.model.fields) });
46
+ const shape = { etagFields: Object.keys(access.model.fields) };
47
+ const inserted = await insertRecord(trx, entry, sealValues(deps.encryption, full, row, (field) => pendingBinding(full, field)), shape);
48
+ const created = await bindEncrypted(trx, deps, access, row, inserted, shape);
37
49
  await assertWritten(trx, access, created, "create", access.resolved.rowFilter.create, filter);
38
50
  await parents(created.record);
39
51
  if (full.position) await checkRankAfter(trx, deps, access, full.position.field, [{
@@ -1,3 +1,4 @@
1
+ import { maskEncrypted } from "./encryption/mask-stored.js";
1
2
  import { forbidden, notFound, preconditionFailed, preconditionRequired } from "./problem.js";
2
3
  import { tenantScope } from "./tenant.js";
3
4
  import { andFilters, recordDecision } from "./request-access.js";
@@ -44,7 +45,10 @@ const assertWritten = async (trx, access, written, operation, ...filters) => {
44
45
  fields: [firstKeyField(access)]
45
46
  })) throw denied(access.entry, `leave in that state (${operation})`);
46
47
  };
47
- /** The caller sees the readable fields only, files as objects with their URL; hooks get the whole record. */
48
+ /**
49
+ * The caller sees the readable fields only, files as objects with their URL; hooks get the whole record, a set encrypted
50
+ * field as `encrypted`.
51
+ */
48
52
  const shown = async (access, written) => ({
49
53
  record: (await presentFiles(access, [pickFields(written.record, Object.keys(access.model.fields))]))[0],
50
54
  etag: written.etag
@@ -58,8 +62,8 @@ const notifier = (deps, access) => async (trx, kind, before, after) => {
58
62
  user: session.user,
59
63
  ...session.tenant !== void 0 && { tenant: session.tenant },
60
64
  ...session.anonymous && { anonymous: true },
61
- ...before && { before },
62
- ...after && { after }
65
+ ...before && { before: maskEncrypted(access.full, before) },
66
+ ...after && { after: maskEncrypted(access.full, after) }
63
67
  };
64
68
  for (const hook of deps.hooks) await hook(event, trx);
65
69
  await scheduleRemovedFiles(access, kind, before, after);
@@ -1,7 +1,10 @@
1
+ import { badRequest } from "./problem.js";
1
2
  import { tenantScope } from "./tenant.js";
2
3
  import { targetFilter } from "./request-access.js";
4
+ import { bindingOf } from "./encryption/binding.js";
3
5
  import { fetchRecord, updateRecord } from "./records.js";
4
6
  import { attachFiles } from "./files/attach.js";
7
+ import { sealValues } from "./encryption/seal-values.js";
5
8
  import { validateBody } from "./validate-body.js";
6
9
  import { assertWritten, checkParents, checkPrecondition, decide, denied, firstKeyField, missing, notifier } from "./write-shared.js";
7
10
  //#region src/write-update.ts
@@ -40,11 +43,18 @@ const planUpdate = async (trx, access, key, body, ifMatch, required = true) => {
40
43
  parents
41
44
  };
42
45
  };
46
+ const refuseKeyChange = (access, plan) => {
47
+ const { full, entry } = access;
48
+ if (!Object.values(full.fields).some((field) => field.encrypted)) return;
49
+ if (full.primaryKey.some((name) => Object.hasOwn(plan.values, name) && String(plan.values[name]) !== String(plan.current.record[name]))) throw badRequest("key-change-unsupported", `The key of a ${entry.name} record cannot change: its encrypted fields are bound to it`);
50
+ };
43
51
  /** Writes a planned update, checks the row as written against the filters, and runs the hooks. */
44
52
  const applyUpdate = async (trx, deps, access, key, plan) => {
45
- const { entry } = access;
53
+ const { entry, full } = access;
54
+ refuseKeyChange(access, plan);
46
55
  const tenant = tenantScope(entry, access.session)?.tenantValue;
47
- const updated = await updateRecord(trx, entry, key, tenant, plan.values, { etagFields: Object.keys(access.model.fields) });
56
+ const values = sealValues(deps.encryption, full, plan.values, (field) => bindingOf(full, field, plan.current.record));
57
+ const updated = await updateRecord(trx, entry, key, tenant, values, { etagFields: Object.keys(access.model.fields) });
48
58
  await assertWritten(trx, access, updated, "update", access.resolved.rowFilter.update, plan.filter);
49
59
  await plan.parents(updated.record);
50
60
  await notifier(deps, access)(trx, "update", plan.current.record, updated.record);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@protobase/server",
3
- "version": "0.33.4",
3
+ "version": "0.34.1",
4
4
  "description": "The Protobase REST API as a Hono app, with Better Auth sign-in.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -30,18 +30,22 @@
30
30
  "access": "public"
31
31
  },
32
32
  "dependencies": {
33
- "@better-auth/passkey": "^1.7.7",
34
- "@better-auth/sso": "^1.7.7",
35
- "@protobase/layout": "0.33.4",
36
- "@protobase/query": "0.33.4",
37
- "@protobase/schema": "0.33.4",
33
+ "@better-auth/cimd": "^1.7.12",
34
+ "@better-auth/mcp": "^1.7.12",
35
+ "@better-auth/oauth-provider": "^1.7.12",
36
+ "@better-auth/passkey": "^1.7.12",
37
+ "@better-auth/sso": "^1.7.12",
38
+ "@protobase/layout": "0.34.1",
39
+ "@protobase/query": "0.34.1",
40
+ "@protobase/schema": "0.34.1",
38
41
  "@react-email/render": "^2.1.0",
39
42
  "@scalar/hono-api-reference": "^0.12.10",
40
43
  "@sentry/core": "^11.7.0",
41
44
  "@simplewebauthn/server": "^13.3.1",
42
45
  "altcha-lib": "^2.6.0",
43
46
  "bcryptjs": "^3.0.3",
44
- "better-auth": "^1.7.7",
47
+ "better-auth": "^1.7.12",
48
+ "better-call": "1.4.4",
45
49
  "cron-parser": "^5.10.1",
46
50
  "file-type": "^22.1.1",
47
51
  "hono": "^4.13.13",