@protobase/server 0.44.4 → 0.44.5

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.
@@ -4445,7 +4445,7 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
4445
4445
  twoFactor: boolean;
4446
4446
  authenticatorApp: boolean;
4447
4447
  };
4448
- missing: ("passkey" | "twoFactor")[];
4448
+ missing: ("twoFactor" | "passkey")[];
4449
4449
  }>;
4450
4450
  };
4451
4451
  hooks: {
@@ -5318,7 +5318,7 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
5318
5318
  }[];
5319
5319
  total: number;
5320
5320
  }>;
5321
- acceptInvitation: import("better-call").StrictEndpoint<"/invitation/accept", {
5321
+ acceptInvitationOfLink: import("better-call").StrictEndpoint<"/invitation/accept", {
5322
5322
  method: "POST";
5323
5323
  body: import("zod").ZodObject<{
5324
5324
  token: import("zod").ZodString;
@@ -5356,6 +5356,44 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
5356
5356
  organizationId: string;
5357
5357
  };
5358
5358
  }>;
5359
+ acceptOwnInvitation: import("better-call").StrictEndpoint<"/invitation/accept-own", {
5360
+ method: "POST";
5361
+ body: import("zod").ZodObject<{
5362
+ invitationId: import("zod").ZodString;
5363
+ }, import("zod/v4/core").$strip>;
5364
+ use: import("better-call").Middleware<import("better-call").MiddlewareOptions, (inputContext: import("better-call").MiddlewareInputContext<import("better-call").MiddlewareOptions>) => Promise<{
5365
+ session: {
5366
+ session: Record<string, any> & {
5367
+ id: string;
5368
+ createdAt: Date;
5369
+ updatedAt: Date;
5370
+ userId: string;
5371
+ expiresAt: Date;
5372
+ token: string;
5373
+ ipAddress?: string | null | undefined;
5374
+ userAgent?: string | null | undefined;
5375
+ };
5376
+ user: Record<string, any> & {
5377
+ id: string;
5378
+ createdAt: Date;
5379
+ updatedAt: Date;
5380
+ email: string;
5381
+ emailVerified: boolean;
5382
+ name: string;
5383
+ image?: string | null | undefined;
5384
+ };
5385
+ };
5386
+ }>>[];
5387
+ }, {
5388
+ member: {
5389
+ appRoles: string[];
5390
+ id: string;
5391
+ createdAt: Date;
5392
+ role: string;
5393
+ userId: string;
5394
+ organizationId: string;
5395
+ };
5396
+ }>;
5359
5397
  };
5360
5398
  hooks: {
5361
5399
  after: {
@@ -5368,7 +5406,7 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
5368
5406
  } | {
5369
5407
  id: "protobase-users";
5370
5408
  endpoints: {
5371
- getInvitation: import("better-call").StrictEndpoint<"/invitation", {
5409
+ invitationOfLink: import("better-call").StrictEndpoint<"/invitation", {
5372
5410
  method: "GET";
5373
5411
  query: import("zod").ZodObject<{
5374
5412
  token: import("zod").ZodString;
@@ -5523,7 +5561,7 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
5523
5561
  roles: string[];
5524
5562
  expiresAt: string;
5525
5563
  }>;
5526
- listUserInvitations: import("better-call").StrictEndpoint<"/users/invitations", {
5564
+ listAppInvitations: import("better-call").StrictEndpoint<"/users/invitations", {
5527
5565
  method: "GET";
5528
5566
  use: import("better-call").Middleware<{
5529
5567
  use: import("better-call").Middleware<import("better-call").MiddlewareOptions, (inputContext: import("better-call").MiddlewareInputContext<import("better-call").MiddlewareOptions>) => Promise<{
@@ -26,6 +26,7 @@ import { passwordResetOptions } from "./password-reset.js";
26
26
  import { rolesPlugin } from "./roles/roles-plugin.js";
27
27
  import { platformSignInFor, platformSignInProvider, readPlatformSignIn, resolvePlatformSignIn } from "./platform-sign-in.js";
28
28
  import { appSignInPolicy } from "./app-sign-in-policy.js";
29
+ import { assertNoEndpointKeyClashes } from "./endpoint-key-clashes.js";
29
30
  import { emailInDomains, resolveSamlProviders, samlDisabledPaths, samlGuardPlugin, samlPlugin } from "./saml-sign-in.js";
30
31
  import { emailCodeOptions, twoFactorCodeOptions, unusedEmailCodePaths } from "./sign-in-mail.js";
31
32
  import { signUpPaths, signUpPlugin } from "./sign-up/sign-up-plugin.js";
@@ -313,7 +314,7 @@ const createAuth = (options) => {
313
314
  let instance;
314
315
  const context = () => instance?.$context ?? Promise.reject(/* @__PURE__ */ new Error("The auth instance is not made yet"));
315
316
  const mobile = mobileRequests();
316
- const auth = betterAuth(betterAuthOptions(options, {
317
+ const authOptions = betterAuthOptions(options, {
317
318
  mobile,
318
319
  origins,
319
320
  passkeyDomain,
@@ -330,7 +331,9 @@ const createAuth = (options) => {
330
331
  ...organizations && { organizations },
331
332
  ...mcp && { mcp },
332
333
  ...(organizations || mcp) && { context }
333
- }));
334
+ });
335
+ assertNoEndpointKeyClashes(authOptions.plugins);
336
+ const auth = betterAuth(authOptions);
334
337
  instance = auth;
335
338
  return Object.assign(auth, {
336
339
  /**
@@ -0,0 +1,23 @@
1
+ //#region src/better-auth/endpoint-key-clashes.ts
2
+ /**
3
+ * Each endpoint key more than one plugin registers, with those plugins' ids. Better Auth merges the plugins' endpoints
4
+ * by key into one object, so a later plugin's endpoint silently replaces an earlier one's of the same key, and the
5
+ * earlier one's path answers 404.
6
+ */
7
+ const endpointKeyClashes = (plugins) => {
8
+ const owners = /* @__PURE__ */ new Map();
9
+ for (const plugin of plugins) for (const key of Object.keys(plugin.endpoints ?? {})) owners.set(key, [...owners.get(key) ?? [], plugin.id]);
10
+ return [...owners].filter(([, ids]) => ids.length > 1).map(([key, ids]) => ({
11
+ key,
12
+ plugins: ids
13
+ }));
14
+ };
15
+ /** Fails when two plugins register an endpoint of the same key, as one would hide the other. */
16
+ const assertNoEndpointKeyClashes = (plugins) => {
17
+ const clashes = endpointKeyClashes(plugins);
18
+ if (clashes.length === 0) return;
19
+ const list = clashes.map(({ key, plugins: ids }) => `${key} (${ids.join(", ")})`).join("; ");
20
+ throw new Error(`Auth plugins register the same endpoint keys, so one hides the other: ${list}`);
21
+ };
22
+ //#endregion
23
+ export { assertNoEndpointKeyClashes, endpointKeyClashes };
@@ -19,7 +19,7 @@ const invitationEndpoints = ({ mail, defaultPolicy, organizations }) => ({
19
19
  * `GET /invitation?token=`: who invites, the address and roles, until when, and whether the address has an account;
20
20
  * for an invitation to the app also the app's name, and for an organization's the organization and the role there.
21
21
  */
22
- getInvitation: createAuthEndpoint("/invitation", {
22
+ invitationOfLink: createAuthEndpoint("/invitation", {
23
23
  method: "GET",
24
24
  query: tokenQuery
25
25
  }, async (ctx) => {
@@ -3,23 +3,33 @@ import { findMember, membersOf } from "./store.js";
3
3
  import { invalidInvitation, invitationIdOf, verifyInvitationToken } from "../invitations/invitation-token.js";
4
4
  import { APIError } from "better-auth/api";
5
5
  //#region src/better-auth/organizations/accept-invitation.ts
6
- /** The pending invitation an emailed link's token names, or a 400 `INVITATION_INVALID`. */
7
- const invitationOfToken = async (context, token) => {
8
- const id = invitationIdOf(token);
9
- const row = id && await context.adapter.findOne({
10
- model: "invitation",
11
- where: [{
12
- field: "id",
13
- value: id
14
- }]
15
- });
16
- if (!row || !await verifyInvitationToken(context.secret, token, row)) throw new APIError("BAD_REQUEST", invalidInvitation);
6
+ const findInvitation = (context, id) => context.adapter.findOne({
7
+ model: "invitation",
8
+ where: [{
9
+ field: "id",
10
+ value: id
11
+ }]
12
+ });
13
+ const pending = (row) => {
17
14
  if (row.status !== "pending" || new Date(row.expiresAt).getTime() < Date.now()) throw new APIError("BAD_REQUEST", invalidInvitation);
18
15
  return {
19
16
  ...row,
20
17
  appRoles: parseAppRoles(row.appRoles)
21
18
  };
22
19
  };
20
+ /** The pending invitation an emailed link's token names, or a 400 `INVITATION_INVALID`. */
21
+ const invitationOfToken = async (context, token) => {
22
+ const id = invitationIdOf(token);
23
+ const row = id && await findInvitation(context, id);
24
+ if (!row || !await verifyInvitationToken(context.secret, token, row)) throw new APIError("BAD_REQUEST", invalidInvitation);
25
+ return pending(row);
26
+ };
27
+ /** The pending invitation of `id`, or a 400 `INVITATION_INVALID`; only its own address may accept it (`acceptInvitation`). */
28
+ const invitationOfId = async (context, id) => {
29
+ const row = await findInvitation(context, id);
30
+ if (!row) throw new APIError("BAD_REQUEST", invalidInvitation);
31
+ return pending(row);
32
+ };
23
33
  /**
24
34
  * Makes `user` a member as the invitation says, with its organization role and app roles, in one transaction with the
25
35
  * invitation's acceptance, so a member never exists without the app roles they were invited with. Better Auth's own
@@ -73,4 +83,4 @@ const acceptInvitation = async (context, invitation, user, membershipLimit) => {
73
83
  });
74
84
  };
75
85
  //#endregion
76
- export { acceptInvitation, invitationOfToken };
86
+ export { acceptInvitation, invitationOfId, invitationOfToken };
@@ -1,23 +1,31 @@
1
- import { acceptInvitation, invitationOfToken } from "./accept-invitation.js";
1
+ import { acceptInvitation, invitationOfId, invitationOfToken } from "./accept-invitation.js";
2
2
  import * as z$1 from "zod";
3
3
  import { createAuthEndpoint, sessionMiddleware } from "better-auth/api";
4
4
  //#region src/better-auth/organizations/invitation-endpoints.ts
5
+ const accept = async (context, invitation, membershipLimit) => {
6
+ const { user, session } = context.session;
7
+ const member = await acceptInvitation(context, invitation, user, membershipLimit);
8
+ await context.internalAdapter.updateSession(session.token, { activeOrganizationId: invitation.organizationId });
9
+ return { member };
10
+ };
5
11
  /**
6
- * Accepting an organization's emailed invitation signed in. What a link is for, and making an account from it, are the
7
- * shared invitation endpoints (`invitations/invitation-endpoints.ts`), which serve invitations to the app as well.
12
+ * Accepting an organization's invitation signed in, from its emailed link or from the person's own invitations (Better
13
+ * Auth's `GET /organization/list-user-invitations`). What a link is for, and making an account from it, are the shared
14
+ * invitation endpoints (`invitations/invitation-endpoints.ts`), which serve invitations to the app as well.
8
15
  */
9
- const invitationEndpoints = ({ resolved }) => ({
10
- /** `POST /invitation/accept` with `{ token }`, signed in as the invited address: becomes a member, and works there. */
11
- acceptInvitation: createAuthEndpoint("/invitation/accept", {
12
- method: "POST",
13
- body: z$1.object({ token: z$1.string().min(1) }),
14
- use: [sessionMiddleware]
15
- }, async (ctx) => {
16
- const invitation = await invitationOfToken(ctx.context, ctx.body.token);
17
- const { user, session } = ctx.context.session;
18
- const member = await acceptInvitation(ctx.context, invitation, user, resolved.membershipLimit);
19
- await ctx.context.internalAdapter.updateSession(session.token, { activeOrganizationId: invitation.organizationId });
20
- return ctx.json({ member });
21
- }) });
16
+ const invitationEndpoints = ({ resolved }) => ({
17
+ /** `POST /invitation/accept` with `{ token }`, signed in as the invited address: becomes a member, and works there. */
18
+ acceptInvitationOfLink: createAuthEndpoint("/invitation/accept", {
19
+ method: "POST",
20
+ body: z$1.object({ token: z$1.string().min(1) }),
21
+ use: [sessionMiddleware]
22
+ }, async (ctx) => ctx.json(await accept(ctx.context, await invitationOfToken(ctx.context, ctx.body.token), resolved.membershipLimit))),
23
+ /** `POST /invitation/accept-own` with `{ invitationId }`, signed in as the invited address: the same, from their list. */
24
+ acceptOwnInvitation: createAuthEndpoint("/invitation/accept-own", {
25
+ method: "POST",
26
+ body: z$1.object({ invitationId: z$1.string().min(1) }),
27
+ use: [sessionMiddleware]
28
+ }, async (ctx) => ctx.json(await accept(ctx.context, await invitationOfId(ctx.context, ctx.body.invitationId), resolved.membershipLimit)))
29
+ });
22
30
  //#endregion
23
31
  export { invitationEndpoints };
@@ -56,7 +56,7 @@ const userEndpoints = ({ definitions, mail, invitationUrl, invitationSeconds })
56
56
  return ctx.json(answerOf(invitation));
57
57
  }),
58
58
  /** `GET /users/invitations`: the pending invitations to the app, oldest first. */
59
- listUserInvitations: createAuthEndpoint("/users/invitations", {
59
+ listAppInvitations: createAuthEndpoint("/users/invitations", {
60
60
  method: "GET",
61
61
  use: [admins]
62
62
  }, async (ctx) => ctx.json({ invitations: (await listAppInvitations(ctx.context)).map(answerOf) })),
package/dist/package.js CHANGED
@@ -1,4 +1,4 @@
1
1
  //#region package.json
2
- var version = "0.44.4";
2
+ var version = "0.44.5";
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.44.4",
3
+ "version": "0.44.5",
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.44.4",
40
- "@protobase/query": "0.44.4",
41
- "@protobase/schema": "0.44.4",
39
+ "@protobase/layout": "0.44.5",
40
+ "@protobase/query": "0.44.5",
41
+ "@protobase/schema": "0.44.5",
42
42
  "@react-email/render": "^2.1.0",
43
43
  "@scalar/hono-api-reference": "^0.12.10",
44
44
  "@sentry/core": "^11.7.0",