@protobase/server 0.40.8 → 0.40.9

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.
@@ -2,7 +2,7 @@ import { checkedUrl } from "../checked-url.js";
2
2
  import { forbidden } from "../problem.js";
3
3
  import { jwtAuthenticator } from "../auth/jwt.js";
4
4
  import { noGlobalRole } from "./organizations/role-definitions.js";
5
- import "./auth-base-path.js";
5
+ import { authBasePath } from "./auth-base-path.js";
6
6
  import { createRemoteJWKSet, decodeJwt, errors } from "jose";
7
7
  //#region src/better-auth/app-tokens.ts
8
8
  const claimedIssuer = (request) => {
@@ -19,7 +19,7 @@ const claimedIssuer = (request) => {
19
19
  const trustedAuthenticator = (app, issuer) => {
20
20
  const forms = [issuer, `${issuer}/`];
21
21
  const verify = jwtAuthenticator({
22
- keys: app.keys ?? createRemoteJWKSet(new URL(`/api/auth/jwks`, issuer)),
22
+ keys: app.keys ?? createRemoteJWKSet(new URL(`${authBasePath}/jwks`, issuer)),
23
23
  issuer: forms,
24
24
  audience: forms,
25
25
  rolesClaim: "role",
@@ -0,0 +1 @@
1
+ import "@protobase/schema/internal";
@@ -1,5 +1,2 @@
1
- //#region src/better-auth/auth-base-path.ts
2
- /** Where Better Auth's endpoints are served, below the app's origin. */
3
- const authBasePath = "/api/auth";
4
- //#endregion
1
+ import { authBasePath } from "@protobase/schema/internal";
5
2
  export { authBasePath };
@@ -1,6 +1,7 @@
1
1
  import { StaffSignIn } from "./staff-log.js";
2
2
  import { RoleDefinitions, RolesInput } from "./organizations/role-definitions.js";
3
3
  import { ConnectedApp } from "./mcp/connected-apps.js";
4
+ import "./auth-base-path.js";
4
5
  import { Mailer } from "../mail/smtp-mailer.js";
5
6
  import { OperatorProvider } from "./operator-provider.js";
6
7
  import { OrganizationRole } from "./organizations/store.js";
@@ -8649,7 +8650,7 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
8649
8650
  };
8650
8651
  basePath: string;
8651
8652
  secret: string;
8652
- trustedOrigins: string[];
8653
+ trustedOrigins: string[] | ((request?: Request) => string[]);
8653
8654
  emailAndPassword: {
8654
8655
  onPasswordReset?: ((data: {
8655
8656
  user: import("better-auth").User;
@@ -8673,6 +8674,11 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
8673
8674
  maxPasswordLength: number;
8674
8675
  };
8675
8676
  }> & {
8677
+ /**
8678
+ * Answers a request from the app's iOS or Android build: Better Auth also trusts the Capacitor origin the request
8679
+ * names. Only `createAdmin` with a `mobile` section uses it; `handler` and `api` never trust a Capacitor origin.
8680
+ */
8681
+ handleMobile: (request: Request) => Promise<Response>;
8676
8682
  /** Issuer and audience of the tokens: `baseURL`, on every origin. */
8677
8683
  issuer: string;
8678
8684
  /** Every origin the app is served on, `baseURL`'s first. */
@@ -6,6 +6,7 @@ import { roleDefinitions } from "./organizations/role-definitions.js";
6
6
  import { signInPolicyPlugin } from "./policy-plugin.js";
7
7
  import { goTrueSessionsPlugin } from "../gotrue/sessions-plugin.js";
8
8
  import { authBasePath } from "./auth-base-path.js";
9
+ import { mobileRequests } from "./mobile-requests.js";
9
10
  import { passwordLength } from "./new-account-password.js";
10
11
  import { platformMailer } from "../mail/platform-mailer.js";
11
12
  import { readOperatorSettings, resolveOperator } from "./operator-provider.js";
@@ -60,7 +61,7 @@ const organizationPaths = ["/organization/set-active", "/organization/accept-inv
60
61
  * The options `createAuth` gives Better Auth, with the roles, default role, mailer, operator provider and platform
61
62
  * sign-in provider already resolved.
62
63
  */
63
- const betterAuthOptions = (options, { origins = [], passkeyDomain, roles, definitions, defaultRole, appName = defaultAppName, mailer, signUpRole, operator, platformSignIn, saml = [], organizations, mcp, context = noContext, signInPolicy = defaultSignInPolicy }) => {
64
+ const betterAuthOptions = (options, { origins = [], passkeyDomain, roles, definitions, defaultRole, appName = defaultAppName, mailer, signUpRole, operator, platformSignIn, saml = [], organizations, mcp, context = noContext, signInPolicy = defaultSignInPolicy, mobile }) => {
64
65
  const secure = options.baseURL.startsWith("https://");
65
66
  const signIn = platformSignInFor(platformSignIn, Object.keys(options.socialProviders ?? {}));
66
67
  const hosts = passkeyHosts([options.baseURL, ...origins]);
@@ -104,6 +105,11 @@ const betterAuthOptions = (options, { origins = [], passkeyDomain, roles, defini
104
105
  },
105
106
  disableSettingJwtHeader: true
106
107
  };
108
+ const trusted = [
109
+ options.baseURL,
110
+ ...origins,
111
+ ...options.trustedOrigins ?? []
112
+ ];
107
113
  const userFields = {
108
114
  ...userFieldsNamed(emailConfirmedField, users),
109
115
  ...organizations && userFieldsNamed(lastOrganizationField, users),
@@ -115,11 +121,7 @@ const betterAuthOptions = (options, { origins = [], passkeyDomain, roles, defini
115
121
  baseURL: multiHostBaseURL(options.baseURL, origins),
116
122
  basePath: authBasePath,
117
123
  secret: options.secret,
118
- trustedOrigins: [
119
- options.baseURL,
120
- ...origins,
121
- ...options.trustedOrigins ?? []
122
- ],
124
+ trustedOrigins: mobile ? (request) => [...trusted, ...mobile.trusted(request)] : trusted,
123
125
  emailAndPassword: {
124
126
  enabled: true,
125
127
  disableSignUp: true,
@@ -310,7 +312,9 @@ const createAuth = (options) => {
310
312
  const mcp = options.mcp && resolveMcp(options.baseURL, options.mcp);
311
313
  let instance;
312
314
  const context = () => instance?.$context ?? Promise.reject(/* @__PURE__ */ new Error("The auth instance is not made yet"));
315
+ const mobile = mobileRequests();
313
316
  const auth = betterAuth(betterAuthOptions(options, {
317
+ mobile,
314
318
  origins,
315
319
  passkeyDomain,
316
320
  roles,
@@ -329,6 +333,11 @@ const createAuth = (options) => {
329
333
  }));
330
334
  instance = auth;
331
335
  return Object.assign(auth, {
336
+ /**
337
+ * Answers a request from the app's iOS or Android build: Better Auth also trusts the Capacitor origin the request
338
+ * names. Only `createAdmin` with a `mobile` section uses it; `handler` and `api` never trust a Capacitor origin.
339
+ */
340
+ handleMobile: (request) => auth.handler(mobile.grant(request)),
332
341
  /** Issuer and audience of the tokens: `baseURL`, on every origin. */
333
342
  issuer: options.baseURL,
334
343
  /** Every origin the app is served on, `baseURL`'s first. */
@@ -0,0 +1,23 @@
1
+ import { capacitorOrigins } from "../mobile-config.js";
2
+ //#region src/better-auth/mobile-requests.ts
3
+ /**
4
+ * Which requests come from the app's iOS or Android build: `grant` marks a request, and `trusted` gives the Capacitor
5
+ * origin it names as an origin Better Auth trusts, for that very request only. Any other request, or an Origin that is
6
+ * not exactly a Capacitor origin, gets nothing.
7
+ */
8
+ const mobileRequests = () => {
9
+ const granted = /* @__PURE__ */ new WeakSet();
10
+ return {
11
+ grant: (request) => {
12
+ granted.add(request);
13
+ return request;
14
+ },
15
+ trusted: (request) => {
16
+ if (!request || !granted.has(request)) return [];
17
+ const origin = request.headers.get("origin");
18
+ return origin !== null && capacitorOrigins.includes(origin) ? [origin] : [];
19
+ }
20
+ };
21
+ };
22
+ //#endregion
23
+ export { mobileRequests };
@@ -46,7 +46,10 @@ export type CreateAdminInput = {
46
46
  subscriptions?: SubscriptionDefinition[];
47
47
  /** Webhook subscriptions made in the admin app, from `extends: [webhooks()]` (see `WebhooksOptions`). */
48
48
  webhooks?: WebhooksOptions;
49
- /** The app's iOS and Android build (see `MobileConfig`): the API answers its Capacitor origins through CORS. */
49
+ /**
50
+ * The app's iOS and Android build (see `MobileConfig`): the API answers its Capacitor origins through CORS, and sign-in
51
+ * at `/api/auth` trusts the Capacitor origin a request names, without CORS, as the app calls it through native HTTP.
52
+ */
50
53
  mobile?: MobileConfig;
51
54
  /** The app's colours, which its web manifest carries (see `AppIdentity`). */
52
55
  app?: AppIdentity;
@@ -275,7 +275,8 @@ const createAdmin = ({ resources: appResources, views: appViews = [], pages = []
275
275
  db
276
276
  }));
277
277
  if (auth.mcp) mcpDiscovery(app, auth);
278
- app.on(["GET", "POST"], `${authBasePath}/*`, async (c) => withoutSessionTokens(c.req.raw, await auth.handler(c.req.raw)));
278
+ const answer = mobile ? auth.handleMobile : auth.handler;
279
+ app.on(["GET", "POST"], `${authBasePath}/*`, async (c) => withoutSessionTokens(c.req.raw, await answer(c.req.raw)));
279
280
  app.route(goTruePath, goTrueRoutes(auth));
280
281
  if (auth.origins.length > 1) app.get(webauthnPath, (c) => c.json({ origins: auth.origins }));
281
282
  }
@@ -1,8 +1,6 @@
1
+ import { capacitorOrigin } from "@protobase/schema/internal";
1
2
  //#region src/mobile-config.ts
2
- /**
3
- * The origins Capacitor's WebView serves the app's files on: `capacitor://localhost` on iOS, `https://localhost` on
4
- * Android. They hold because `protobase mobile` never sets Capacitor's `server.hostname` or schemes.
5
- */
6
- const capacitorOrigins = ["capacitor://localhost", "https://localhost"];
3
+ /** The origins Capacitor's WebView serves the app's files on: `capacitor://localhost` on iOS, `https://localhost` on Android. */
4
+ const capacitorOrigins = Object.values(capacitorOrigin);
7
5
  //#endregion
8
6
  export { capacitorOrigins };
package/dist/package.js CHANGED
@@ -1,4 +1,4 @@
1
1
  //#region package.json
2
- var version = "0.40.8";
2
+ var version = "0.40.9";
3
3
  //#endregion
4
4
  export { version };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@protobase/server",
3
- "version": "0.40.8",
3
+ "version": "0.40.9",
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.40.8",
40
- "@protobase/query": "0.40.8",
41
- "@protobase/schema": "0.40.8",
39
+ "@protobase/layout": "0.40.9",
40
+ "@protobase/query": "0.40.9",
41
+ "@protobase/schema": "0.40.9",
42
42
  "@react-email/render": "^2.1.0",
43
43
  "@scalar/hono-api-reference": "^0.12.10",
44
44
  "@sentry/core": "^11.7.0",