@protobase/server 0.46.2 → 0.46.4

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.
@@ -1,4 +1,5 @@
1
1
  import "./account-exists-email.js";
2
+ import "./change-email-code-email.js";
2
3
  import "./invitation-email.js";
3
4
  import "./no-account-email.js";
4
5
  import "./reset-password-email.js";
@@ -1,4 +1,5 @@
1
1
  import { accountExistsEmail } from "./account-exists-email.js";
2
+ import { changeEmailCodeEmail } from "./change-email-code-email.js";
2
3
  import { invitationEmail } from "./invitation-email.js";
3
4
  import { noAccountEmail } from "./no-account-email.js";
4
5
  import { resetPasswordEmail } from "./reset-password-email.js";
@@ -6,4 +7,4 @@ import { signInCodeEmail } from "./sign-in-code-email.js";
6
7
  import { signUpCodeEmail } from "./sign-up-code-email.js";
7
8
  import { staffSignInEmail } from "./staff-sign-in-email.js";
8
9
  import { twoFactorCodeEmail } from "./two-factor-code-email.js";
9
- export { accountExistsEmail, invitationEmail, noAccountEmail, resetPasswordEmail, signInCodeEmail, signUpCodeEmail, staffSignInEmail, twoFactorCodeEmail };
10
+ export { accountExistsEmail, changeEmailCodeEmail, invitationEmail, noAccountEmail, resetPasswordEmail, signInCodeEmail, signUpCodeEmail, staffSignInEmail, twoFactorCodeEmail };
@@ -0,0 +1 @@
1
+ import "../../index.js";
@@ -0,0 +1,32 @@
1
+ import { defineEmail } from "../../mail/define-email.js";
2
+ import { EmailCode, EmailLayout, EmailNote } from "../../mail/email-layout.js";
3
+ import { Text } from "react-email";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ //#region src/better-auth/auth-mail/change-email-code-email.tsx
6
+ const ChangeEmailCodeEmail = ({ app, code, lifetime }) => /* @__PURE__ */ jsxs(EmailLayout, {
7
+ app,
8
+ preview: `Your code to sign in to ${app.name} with this address`,
9
+ heading: "Confirm your new address",
10
+ children: [
11
+ /* @__PURE__ */ jsxs(Text, { children: [
12
+ "Use this code to sign in to ",
13
+ app.name,
14
+ " with this address from now on:"
15
+ ] }),
16
+ /* @__PURE__ */ jsx(EmailCode, { code }),
17
+ /* @__PURE__ */ jsxs(Text, { children: [
18
+ "It works for ",
19
+ lifetime,
20
+ ", once."
21
+ ] }),
22
+ /* @__PURE__ */ jsx(EmailNote, { children: "If you did not ask for this, ignore this email: nobody can move an account to your address without the code." })
23
+ ]
24
+ });
25
+ /** The email to the new address with the code that moves the account to it. */
26
+ const changeEmailCodeEmail = defineEmail({
27
+ name: "change-email-code",
28
+ subject: ({ app }) => `Confirm your new ${app.name} address`,
29
+ component: ChangeEmailCodeEmail
30
+ });
31
+ //#endregion
32
+ export { changeEmailCodeEmail };
@@ -4476,6 +4476,41 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
4476
4476
  handler: import("better-call").Middleware<import("better-call").MiddlewareOptions, (inputContext: import("better-call").MiddlewareInputContext<import("better-call").MiddlewareOptions>) => Promise<void>>;
4477
4477
  }[];
4478
4478
  };
4479
+ } | {
4480
+ id: "protobase-set-password";
4481
+ endpoints: {
4482
+ setFirstPassword: import("better-call").StrictEndpoint<"/account/set-password", {
4483
+ method: "POST";
4484
+ use: import("better-call").Middleware<import("better-call").MiddlewareOptions, (inputContext: import("better-call").MiddlewareInputContext<import("better-call").MiddlewareOptions>) => Promise<{
4485
+ session: {
4486
+ session: Record<string, any> & {
4487
+ id: string;
4488
+ createdAt: Date;
4489
+ updatedAt: Date;
4490
+ userId: string;
4491
+ expiresAt: Date;
4492
+ token: string;
4493
+ ipAddress?: string | null | undefined;
4494
+ userAgent?: string | null | undefined;
4495
+ };
4496
+ user: Record<string, any> & {
4497
+ id: string;
4498
+ createdAt: Date;
4499
+ updatedAt: Date;
4500
+ email: string;
4501
+ emailVerified: boolean;
4502
+ name: string;
4503
+ image?: string | null | undefined;
4504
+ };
4505
+ };
4506
+ }>>[];
4507
+ body: import("zod").ZodObject<{
4508
+ newPassword: import("zod").ZodString;
4509
+ }, import("zod/v4/core").$strip>;
4510
+ }, {
4511
+ status: boolean;
4512
+ }>;
4513
+ };
4479
4514
  } | {
4480
4515
  id: "protobase-gotrue-sessions";
4481
4516
  endpoints: {
@@ -8669,6 +8704,10 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
8669
8704
  window: number;
8670
8705
  max: number;
8671
8706
  };
8707
+ "/account/set-password": {
8708
+ window: number;
8709
+ max: number;
8710
+ };
8672
8711
  '/invitation/sign-up': {
8673
8712
  window: number;
8674
8713
  max: number;
@@ -25,6 +25,7 @@ import { resolveOrganizations } from "./organizations/options.js";
25
25
  import { passkeyHosts, readPasskeyDomain, resolvePasskeyDomain } from "./passkey-domain.js";
26
26
  import { passkeyPlugin } from "./passkey-plugin.js";
27
27
  import { passwordResetOptions } from "./password-reset.js";
28
+ import { setPasswordPath, setPasswordPlugin } from "./set-password.js";
28
29
  import { rolesPlugin } from "./roles/roles-plugin.js";
29
30
  import { platformSignInFor, platformSignInProvider, readPlatformSignIn, resolvePlatformSignIn } from "./platform-sign-in.js";
30
31
  import { appSignInPolicy } from "./app-sign-in-policy.js";
@@ -180,6 +181,7 @@ const betterAuthOptions = (options, { origins = [], passkeyDomain, roles, defini
180
181
  ...signIn ? [genericOAuth({ config: [platformSignInProvider(signIn)] })] : [],
181
182
  ...saml.length > 0 ? [samlPlugin(saml, options.baseURL), samlGuardPlugin(samlIds)] : [],
182
183
  signInPolicyPlugin(policy),
184
+ setPasswordPlugin(),
183
185
  goTrueSessionsPlugin({
184
186
  issuer: goTrueIssuer(options.baseURL),
185
187
  jwt: jwtOptions,
@@ -267,6 +269,10 @@ const betterAuthOptions = (options, { origins = [], passkeyDomain, roles, defini
267
269
  window: 60,
268
270
  max: 5
269
271
  },
272
+ [setPasswordPath]: {
273
+ window: 60,
274
+ max: 5
275
+ },
270
276
  "/invitation/sign-up": {
271
277
  window: 60,
272
278
  max: options.signInPerMinute ?? 5
@@ -0,0 +1,27 @@
1
+ import * as z$1 from "zod";
2
+ import { createAuthEndpoint, sessionMiddleware, setPassword } from "better-auth/api";
3
+ //#region src/better-auth/set-password.ts
4
+ const setPasswordPath = "/account/set-password";
5
+ /**
6
+ * `POST /account/set-password { newPassword }`: gives the signed-in account its first password, such as one made by
7
+ * signing up with emailed codes or a provider. It is Better Auth's server-only `setPassword`, with its length rules;
8
+ * an account that has a password already answers `400 PASSWORD_ALREADY_SET` and changes it with `/change-password`.
9
+ * Every session, the one asking included, and every MCP grant stays: nothing was known to anyone else before. The
10
+ * sign-in policy refuses it while passwords are off, and the staff plugin refuses it for a staff session.
11
+ */
12
+ const setPasswordPlugin = () => ({
13
+ id: "protobase-set-password",
14
+ endpoints: { setFirstPassword: createAuthEndpoint(setPasswordPath, {
15
+ method: "POST",
16
+ use: [sessionMiddleware],
17
+ body: z$1.object({ newPassword: z$1.string() })
18
+ }, async (ctx) => {
19
+ await setPassword({
20
+ ...ctx,
21
+ body: { newPassword: ctx.body.newPassword }
22
+ });
23
+ return ctx.json({ status: true });
24
+ }) }
25
+ });
26
+ //#endregion
27
+ export { setPasswordPath, setPasswordPlugin };
@@ -3,7 +3,8 @@ import { signInPolicyMethods } from "./sign-in-policy.js";
3
3
  const passwordPaths = [
4
4
  "/sign-in/email",
5
5
  "/request-password-reset",
6
- "/reset-password"
6
+ "/reset-password",
7
+ "/account/set-password"
7
8
  ];
8
9
  const emailCodePaths = ["/sign-in/email-otp"];
9
10
  const passkeyPaths = [
@@ -2,14 +2,16 @@ import { sendAuthEmail } from "./auth-mail/send-auth-email.js";
2
2
  const codeLifetimeMinutes = 5;
3
3
  const codeLifetime = "5 minutes";
4
4
  /**
5
- * Better Auth's email OTP plugin as sign-in only: never a sign-up, codes stored hashed, sent with `mail`. The
6
- * plugin's other uses (email verification, its own password reset, changing the address) are not offered.
5
+ * Better Auth's email OTP plugin for signing in and changing the address: never a sign-up, codes stored hashed, sent
6
+ * with `mail`. A new address gets its own email, with the code that moves the account to it. The plugin's other uses
7
+ * (email verification, its own password reset) are not offered.
7
8
  */
8
9
  const emailCodeOptions = (mail) => ({
9
10
  disableSignUp: true,
10
11
  expiresIn: 300,
11
12
  storeOTP: "hashed",
12
- sendVerificationOTP: ({ email, otp }, ctx) => sendAuthEmail(mail, "signInCodeEmail", email, {
13
+ changeEmail: { enabled: true },
14
+ sendVerificationOTP: ({ email, otp, type }, ctx) => sendAuthEmail(mail, type === "change-email" ? "changeEmailCodeEmail" : "signInCodeEmail", email, {
13
15
  code: otp,
14
16
  lifetime: codeLifetime
15
17
  }, ctx?.context.baseURL)
@@ -20,9 +22,7 @@ const unusedEmailCodePaths = [
20
22
  "/email-otp/verify-email",
21
23
  "/email-otp/request-password-reset",
22
24
  "/forget-password/email-otp",
23
- "/email-otp/reset-password",
24
- "/email-otp/request-email-change",
25
- "/email-otp/change-email"
25
+ "/email-otp/reset-password"
26
26
  ];
27
27
  /** The two-factor plugin's emailed code, sent with `mail`; stored hashed. */
28
28
  const twoFactorCodeOptions = (mail) => ({
@@ -39,7 +39,10 @@ const staffLabel = (claims) => typeof claims.email === "string" && claims.email
39
39
  const accountChanges = [
40
40
  "/change-password",
41
41
  "/set-password",
42
+ "/account/set-password",
42
43
  "/change-email",
44
+ "/email-otp/request-email-change",
45
+ "/email-otp/change-email",
43
46
  "/delete-user",
44
47
  "/link-social",
45
48
  "/unlink-account",
@@ -9,7 +9,7 @@ import { Hono } from "hono";
9
9
  * `GET {authBase}/status`: `needsAdmin` while the admin store has no user, so a login page can say how to create one;
10
10
  * `signInMethods`, the ways to sign in the sign-in policy leaves on (`password`, `emailCode`, `passkey`), so it offers
11
11
  * only those; `passwordReset` when reset mail can be sent and passwords are on, so it offers "Forgot password?" only
12
- * then; `signUp` when the app has open sign-up, so it offers "Create an account"; the ids of the `socialProviders` people can sign in with, so it offers "Continue with GitHub" when `github` is
12
+ * then; `changeEmail` when mail can be sent, so the account page offers to change the address with an emailed code; `signUp` when the app has open sign-up, so it offers "Create an account"; the ids of the `socialProviders` people can sign in with, so it offers "Continue with GitHub" when `github` is
13
13
  * one; `platformSignIn`, the id and name of the platform's provider when it is one of them; `samlProviders`, the id and name of each SAML provider, when the app has any; `staffSignIn`, the
14
14
  * operator provider's name, when staff can sign in as people; `passkeyDomains`, when people have passkeys for more than
15
15
  * one of the app's hosts, the RP IDs a passkey sign-in can ask for, the passkey domain first; with organizations, who may create them and the roles
@@ -37,6 +37,7 @@ const statusRoute = (auth, { anonymous = false, plugins = [], locale } = {}) =>
37
37
  needsAdmin: !await hasUsers(auth),
38
38
  signInMethods,
39
39
  passwordReset: auth.passwordReset && policy.password === "allowed",
40
+ ...auth.mail && { changeEmail: true },
40
41
  ...auth.signUp && { signUp: true },
41
42
  socialProviders,
42
43
  ...platformSignIn && { platformSignIn },
@@ -96,7 +96,7 @@ import { recordRoutes } from "./routes/record.js";
96
96
  import { createScanGuard } from "./scan-guard.js";
97
97
  import { failedJobs, failedJobsView } from "./jobs/failed-jobs.js";
98
98
  import { Hono } from "hono";
99
- import { checkPages } from "@protobase/layout";
99
+ import { accountPage, accountPageName, checkPages, settingsResources, withSettingsFields } from "@protobase/layout";
100
100
  //#region src/create-admin.ts
101
101
  const declaredRoles = (auth, roles) => {
102
102
  const names = [...auth?.roles ?? [], ...Object.keys(roles?.definitions ?? {})];
@@ -130,7 +130,9 @@ const createAdmin = ({ resources: appResources, views: appViews = [], pages = []
130
130
  const apiFunctions = Object.fromEntries(Object.entries(functions).map(([name, source]) => [name, toApiFunction(name, source)]));
131
131
  const viewModels = views.map((view) => view.toModel());
132
132
  const userMenuModel = userMenu?.toUserMenuModel();
133
- const pageModels = pages.map((entry) => entry.toModel());
133
+ const allPages = pages.map((entry) => entry.toModel());
134
+ const pageModels = allPages.filter((entry) => entry.name !== accountPageName);
135
+ const accountModel = allPages.find((entry) => entry.name === accountPageName) ?? accountPage.toModel();
134
136
  checkShell(registry, viewModels, userMenuModel, pageModels.map((entry) => entry.name));
135
137
  const bottomBarModels = bottomBars.map((bar) => bar.toBottomBarModel());
136
138
  checkBottomBars(registry, bottomBarModels, pageModels.map((entry) => entry.name), declaredRoles(auth, options.roles));
@@ -141,8 +143,9 @@ const createAdmin = ({ resources: appResources, views: appViews = [], pages = []
141
143
  if (options.mcp) checkMcpWrites(registry, declaredRoles(auth, options.roles), options.mcp.writes);
142
144
  checkPositionAfter(registry);
143
145
  checkParents(registry);
144
- checkPages(pageModels, {
145
- models: Object.fromEntries(registry.entries.map((entry) => [entry.name, entry.model])),
146
+ const resourceModels = Object.fromEntries(registry.entries.map((entry) => [entry.name, entry.model]));
147
+ checkPages([...pageModels, accountModel], {
148
+ models: resourceModels,
146
149
  actions: (resource) => new Set(viewModels.filter((view) => view.resource === resource).flatMap((view) => view.actions.map((action) => action.name))),
147
150
  createPages: new Set(viewModels.filter((view) => view.create).map((view) => view.resource))
148
151
  });
@@ -188,7 +191,8 @@ const createAdmin = ({ resources: appResources, views: appViews = [], pages = []
188
191
  db,
189
192
  registry,
190
193
  views: viewModels,
191
- pages: pageModels,
194
+ pages: pageModels.map((entry) => withSettingsFields(entry, resourceModels)),
195
+ account: withSettingsFields(accountModel, resourceModels),
192
196
  ...userMenuModel && { userMenu: userMenuModel },
193
197
  bottomBars: bottomBarModels,
194
198
  basePath,
@@ -207,6 +211,7 @@ const createAdmin = ({ resources: appResources, views: appViews = [], pages = []
207
211
  ...options.roles && { roles: options.roles },
208
212
  ...options.defaultAccess && { defaultAccess: options.defaultAccess }
209
213
  },
214
+ settingsResources: settingsResources([...pageModels, accountModel]),
210
215
  rowPermissions: (resource) => typeof options.rowPermissions === "object" ? !options.rowPermissions.except.includes(resource) : options.rowPermissions !== false,
211
216
  ...assistant && { assistant: assistant.kind === "external" ? assistant.url : `${systemPath}/assistant` },
212
217
  ...runtime && { runtime },
package/dist/deps.d.ts CHANGED
@@ -24,6 +24,8 @@ export type Deps = {
24
24
  /** The bottom bars, in config order. */
25
25
  bottomBars: BottomBarModel[];
26
26
  pages: PageModel[];
27
+ /** The signed-in person's account page, at `/-/account`: the app's page named `account`, or the standard one. */
28
+ account?: PageModel;
27
29
  basePath: string;
28
30
  /** Where the system endpoints live (`/meta`, `/openapi.json`, `/docs`): the parent of `basePath`, `/api` by default. */
29
31
  systemPath: string;
@@ -33,6 +35,8 @@ export type Deps = {
33
35
  /** Run on a create's or update's body before its transaction opens (see `WriteCheck`). */
34
36
  writeChecks: readonly WriteCheck[];
35
37
  accessOptions: ResolveOptions;
38
+ /** The resources the pages' `Settings` blocks show: every caller, an admin too, reads and writes only their own records. */
39
+ settingsResources: ReadonlySet<string>;
36
40
  /** Where reveals of sensitive fields are published. */
37
41
  audit: AuditQueue;
38
42
  /** Gets the errors of work that runs after a request's response is decided, such as a committed write's file cleanup. */
@@ -9,7 +9,7 @@ import { cardPhotoLimit } from "@protobase/schema/internal";
9
9
  import { checkFilter, createWhere } from "@protobase/schema";
10
10
  import { relatedRows } from "@protobase/query";
11
11
  //#region src/list-photos.ts
12
- const where = createWhere();
12
+ const where$1 = createWhere();
13
13
  const invalid = (parameter, message) => badRequest("invalid-parameter", `Invalid ${parameter}: ${message}`, { errors: [{
14
14
  parameter,
15
15
  message
@@ -64,7 +64,7 @@ const recordPhotos = async (deps, request, model, rows) => {
64
64
  if (!request.access || keys.length === 0) return { photos: {} };
65
65
  const { photos, access } = request;
66
66
  const sort = checkedSort(access.model, photos.sort);
67
- const filter = checkFilter(access.model, where.in(photos.field, keys));
67
+ const filter = checkFilter(access.model, where$1.in(photos.field, keys));
68
68
  if (!filter.ok) throw new Error(`Keys of "${model.name}" do not make a filter of "${photos.resource}": ${filter.errors[0].message}`);
69
69
  const scope = readScope(access);
70
70
  const warning = await deps.guard(access.entry, {
package/dist/meta.d.ts CHANGED
@@ -27,6 +27,8 @@ export type CallerMeta = {
27
27
  views: ViewModel[];
28
28
  /** The composed pages the caller may open, without the elements that name what they cannot see or do. */
29
29
  pages: PageModel[];
30
+ /** The caller's account page, at `/-/account`, when they are signed in, pruned as the pages are. */
31
+ account?: PageModel;
30
32
  permissions: Record<string, ResourcePermissions>;
31
33
  /** The user menu with the resources the caller cannot see left out. */
32
34
  userMenu?: UserMenuModel;
package/dist/meta.js CHANGED
@@ -57,6 +57,7 @@ const createMeta = (deps, appName, appIcon) => {
57
57
  actions: (resource) => new Set(views.find((view) => view.resource === resource)?.actions.map((action) => action.name) ?? [])
58
58
  };
59
59
  const pages = deps.pages.flatMap((page) => prunePage(page, session.user.roles, callerAccess) ?? []);
60
+ const account = deps.account && !session.anonymous ? prunePage(deps.account, session.user.roles, callerAccess) : void 0;
60
61
  const shown = (item) => item.kind === "link" || (item.kind === "resource" ? visible.some((access) => access.entry.name === item.resource) || prompted.has(item.resource) : pages.some((page) => page.name === item.page));
61
62
  const bar = bottomBarFor(deps.bottomBars, activeRole(session));
62
63
  const barItems = bar?.items.filter(shown) ?? [];
@@ -69,6 +70,7 @@ const createMeta = (deps, appName, appIcon) => {
69
70
  resources: visible.map(metaModel),
70
71
  views,
71
72
  pages,
73
+ ...account && { account },
72
74
  permissions: Object.fromEntries(visible.map(({ entry, resolved, full }) => [entry.name, {
73
75
  read: resolved.operations.read || resolved.operations.list,
74
76
  create: resolved.operations.create,
package/dist/package.js CHANGED
@@ -1,4 +1,4 @@
1
1
  //#region package.json
2
- var version = "0.46.2";
2
+ var version = "0.46.4";
3
3
  //#endregion
4
4
  export { version };
@@ -4,7 +4,7 @@ import { exposeRow } from "./rows.js";
4
4
  import { checkFilter, createWhere } from "@protobase/schema";
5
5
  import { listQuery } from "@protobase/query";
6
6
  //#region src/related-labels.ts
7
- const where = createWhere();
7
+ const where$1 = createWhere();
8
8
  const invalid = (entry, message) => badRequest("invalid-parameter", `Invalid labels entry "${entry}": ${message}`, { errors: [{
9
9
  parameter: "labels",
10
10
  message
@@ -55,7 +55,7 @@ const namesOf = async (trx, { field, access }, keys) => {
55
55
  }
56
56
  }
57
57
  };
58
- const filter = checkFilter(model, where.in(key, keys));
58
+ const filter = checkFilter(model, where$1.in(key, keys));
59
59
  if (!filter.ok) throw new Error(`Keys of "${access.entry.name}" do not make a filter: ${filter.errors[0].message}`);
60
60
  const named = (await listQuery(trx, model, {
61
61
  filter: filter.filter,
@@ -3,7 +3,7 @@ import { forbidden, notFound, unauthorized } from "./problem.js";
3
3
  import { checkedFilter } from "./filter-input.js";
4
4
  import { checkOwnerId } from "./owner-id.js";
5
5
  import { tenantScope } from "./tenant.js";
6
- import { checkFilter, resolveAccess, restrictModel } from "@protobase/schema";
6
+ import { checkFilter, resolveAccess, restrictModel, where } from "@protobase/schema";
7
7
  //#region src/request-access.ts
8
8
  const organizationOf = (session) => session.organization ? { organization: session.organization } : {};
9
9
  const withoutSensitive = (model) => ({
@@ -32,6 +32,38 @@ const throughForm = (resolved, form) => {
32
32
  }
33
33
  };
34
34
  };
35
+ const noAccess = {
36
+ operations: {
37
+ read: false,
38
+ list: false,
39
+ create: false,
40
+ update: false,
41
+ delete: false
42
+ },
43
+ recordChecks: [],
44
+ readableFields: [],
45
+ writableFields: {
46
+ create: [],
47
+ update: []
48
+ },
49
+ rowFilter: {}
50
+ };
51
+ const ownOnly = (resolved, entry, session) => {
52
+ if (session.anonymous) return noAccess;
53
+ const own = checkFilter(trusted(entry.model), where.eq(entry.model.owner, session.user.id));
54
+ if (!own.ok) throw new Error(`The owner filter of "${entry.name}" is invalid: ${own.errors[0].message}`);
55
+ const { rowFilter } = resolved;
56
+ const narrowed = (operation) => andFilters([rowFilter[operation], own.filter]);
57
+ return {
58
+ ...resolved,
59
+ rowFilter: {
60
+ read: narrowed("read"),
61
+ create: narrowed("create"),
62
+ update: narrowed("update"),
63
+ delete: narrowed("delete")
64
+ }
65
+ };
66
+ };
35
67
  /** `form` is the public form a visitor sends, the only way a caller without a token creates. */
36
68
  const requestAccess = async (deps, entry, session, form) => {
37
69
  const all = await resolveAccess(entry.source, {
@@ -41,7 +73,8 @@ const requestAccess = async (deps, entry, session, form) => {
41
73
  },
42
74
  ...organizationOf(session)
43
75
  }, deps.accessOptions);
44
- const resolved = form ? throughForm(all, form) : withoutAnonymousCreate(all, session);
76
+ const allowed = form ? throughForm(all, form) : withoutAnonymousCreate(all, session);
77
+ const resolved = deps.settingsResources.has(entry.name) ? ownOnly(allowed, entry, session) : allowed;
45
78
  checkOwnerId(entry, session.user, resolved);
46
79
  const readable = restrictModel(entry.model, resolved);
47
80
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@protobase/server",
3
- "version": "0.46.2",
3
+ "version": "0.46.4",
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": {
@@ -36,9 +36,9 @@
36
36
  "@better-auth/passkey": "^1.7.12",
37
37
  "@better-auth/sso": "^1.7.12",
38
38
  "@modelcontextprotocol/server": "^2.3.1",
39
- "@protobase/layout": "0.46.2",
40
- "@protobase/query": "0.46.2",
41
- "@protobase/schema": "0.46.2",
39
+ "@protobase/layout": "0.46.4",
40
+ "@protobase/query": "0.46.4",
41
+ "@protobase/schema": "0.46.4",
42
42
  "@react-email/render": "^2.1.0",
43
43
  "@scalar/hono-api-reference": "^0.12.10",
44
44
  "@sentry/core": "^11.7.0",