better-auth 1.6.13 → 1.6.14

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.
@@ -199,7 +199,7 @@ const getSessionCookie = (request, config) => {
199
199
  if (!cookies) return null;
200
200
  const { cookieName = "session_token", cookiePrefix = "better-auth" } = config || {};
201
201
  const parsedCookie = parseCookies(cookies);
202
- const getCookie = (name) => parsedCookie.get(name) || parsedCookie.get(`__Secure-${name}`);
202
+ const getCookie = (name) => parsedCookie.get(`__Secure-${name}`) ?? parsedCookie.get(name);
203
203
  const sessionToken = getCookie(`${cookiePrefix}.${cookieName}`) || getCookie(`${cookiePrefix}-${cookieName}`);
204
204
  if (sessionToken) return sessionToken;
205
205
  return null;
@@ -25,8 +25,8 @@ type GetType<F extends DBFieldAttribute> = F extends {
25
25
  type: "date";
26
26
  } ? z.ZodDate : z.ZodAny;
27
27
  type GetRequired<F extends DBFieldAttribute, Schema extends z.core.SomeType> = F extends {
28
- required: true;
29
- } ? Schema : z.ZodOptional<Schema>;
28
+ required: false;
29
+ } ? z.ZodOptional<z.ZodNullable<Schema>> : Schema;
30
30
  type GetInput<isClientSide extends boolean, Field extends DBFieldAttribute, Schema extends z.core.SomeType> = Field extends {
31
31
  input: false;
32
32
  } ? isClientSide extends true ? never : Schema : Schema;
@@ -10,7 +10,7 @@ function toZodSchema({ fields, isClientSide }) {
10
10
  else if (field.type === "string[]" || field.type === "number[]") schema = z.array(field.type === "string[]" ? z.string() : z.number());
11
11
  else if (Array.isArray(field.type)) schema = z.any();
12
12
  else schema = z[field.type]();
13
- if (field?.required === false) schema = schema.optional();
13
+ if (field?.required === false) schema = schema.nullish();
14
14
  if (!isClientSide && field?.returned === false) return acc;
15
15
  return {
16
16
  ...acc,
package/dist/package.mjs CHANGED
@@ -1,4 +1,4 @@
1
1
  //#region package.json
2
- var version = "1.6.13";
2
+ var version = "1.6.14";
3
3
  //#endregion
4
4
  export { version };
@@ -550,9 +550,11 @@ const getOrgAdapter = (context, options) => {
550
550
  createInvitation: async ({ invitation, user }) => {
551
551
  const adapter = await getCurrentAdapter(baseAdapter);
552
552
  const expiresAt = getDate(options?.invitationExpiresIn || 3600 * 48, "sec");
553
+ const invitationId = context.generateId({ model: "invitation" });
553
554
  return await adapter.create({
554
555
  model: "invitation",
555
556
  data: {
557
+ ...invitationId !== false ? { id: invitationId } : {},
556
558
  status: "pending",
557
559
  expiresAt,
558
560
  createdAt: /* @__PURE__ */ new Date(),
@@ -14,7 +14,7 @@ declare const createOrgRole: <O extends OrganizationOptions>(options: O) => bett
14
14
  role: z.ZodString;
15
15
  permission: z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>;
16
16
  additionalFields: z.ZodOptional<z.ZodObject<{
17
- [x: string]: z.ZodOptional<z.ZodAny>;
17
+ [x: string]: z.ZodAny;
18
18
  }, z.core.$strip>>;
19
19
  }, z.core.$strip>;
20
20
  metadata: {
@@ -19,6 +19,20 @@ const baseInvitationSchema = z.object({
19
19
  resend: z.boolean().meta({ description: "Resend the invitation email, if the user is already invited. Eg: true" }).optional(),
20
20
  teamId: z.union([z.string().meta({ description: "The team ID to invite the user to" }).optional(), z.array(z.string()).meta({ description: "The team IDs to invite the user to" }).optional()])
21
21
  });
22
+ const getAdvancedGenerateId = (advancedOptions) => {
23
+ if (typeof advancedOptions !== "object" || advancedOptions === null) return;
24
+ const generateId = advancedOptions.generateId;
25
+ if (typeof generateId !== "function") return;
26
+ return generateId;
27
+ };
28
+ const hasBuiltInOpaqueInvitationIdGeneration = ({ advancedGenerateId, databaseGenerateId }) => advancedGenerateId === void 0 && (databaseGenerateId === void 0 || databaseGenerateId === "uuid");
29
+ const shouldRequireVerifiedEmailForInvitationIdAction = ({ organizationOptions, advancedGenerateId, databaseGenerateId }) => {
30
+ if (organizationOptions.requireEmailVerificationOnInvitation !== void 0) return organizationOptions.requireEmailVerificationOnInvitation;
31
+ return !hasBuiltInOpaqueInvitationIdGeneration({
32
+ advancedGenerateId,
33
+ databaseGenerateId
34
+ });
35
+ };
22
36
  const createInvitation = (option) => {
23
37
  const additionalFieldsSchema = toZodSchema({
24
38
  fields: option?.schema?.invitation?.additionalFields || {},
@@ -247,7 +261,11 @@ const acceptInvitation = (options) => createAuthEndpoint("/organization/accept-i
247
261
  const invitation = await adapter.findInvitationById(ctx.body.invitationId);
248
262
  if (!invitation || invitation.expiresAt < /* @__PURE__ */ new Date() || invitation.status !== "pending") throw APIError.from("BAD_REQUEST", ORGANIZATION_ERROR_CODES.INVITATION_NOT_FOUND);
249
263
  if (invitation.email.toLowerCase() !== session.user.email.toLowerCase()) throw APIError.from("FORBIDDEN", ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_THE_RECIPIENT_OF_THE_INVITATION);
250
- if ((ctx.context.orgOptions.requireEmailVerificationOnInvitation ?? true) && !session.user.emailVerified) throw APIError.from("FORBIDDEN", ORGANIZATION_ERROR_CODES.EMAIL_VERIFICATION_REQUIRED_BEFORE_ACCEPTING_OR_REJECTING_INVITATION);
264
+ if (shouldRequireVerifiedEmailForInvitationIdAction({
265
+ organizationOptions: ctx.context.orgOptions,
266
+ advancedGenerateId: getAdvancedGenerateId(ctx.context.options.advanced),
267
+ databaseGenerateId: ctx.context.options.advanced?.database?.generateId
268
+ }) && !session.user.emailVerified) throw APIError.from("FORBIDDEN", ORGANIZATION_ERROR_CODES.EMAIL_VERIFICATION_REQUIRED_BEFORE_ACCEPTING_OR_REJECTING_INVITATION);
251
269
  const membershipLimit = ctx.context.orgOptions?.membershipLimit || 100;
252
270
  const membersCount = await adapter.countMembers({ organizationId: invitation.organizationId });
253
271
  const organization = await adapter.findOrganizationById(invitation.organizationId);
@@ -336,7 +354,11 @@ const rejectInvitation = (options) => createAuthEndpoint("/organization/reject-i
336
354
  code: "INVITATION_NOT_FOUND"
337
355
  });
338
356
  if (invitation.email.toLowerCase() !== session.user.email.toLowerCase()) throw APIError.from("FORBIDDEN", ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_THE_RECIPIENT_OF_THE_INVITATION);
339
- if ((ctx.context.orgOptions.requireEmailVerificationOnInvitation ?? true) && !session.user.emailVerified) throw APIError.from("FORBIDDEN", ORGANIZATION_ERROR_CODES.EMAIL_VERIFICATION_REQUIRED_BEFORE_ACCEPTING_OR_REJECTING_INVITATION);
357
+ if (shouldRequireVerifiedEmailForInvitationIdAction({
358
+ organizationOptions: ctx.context.orgOptions,
359
+ advancedGenerateId: getAdvancedGenerateId(ctx.context.options.advanced),
360
+ databaseGenerateId: ctx.context.options.advanced?.database?.generateId
361
+ }) && !session.user.emailVerified) throw APIError.from("FORBIDDEN", ORGANIZATION_ERROR_CODES.EMAIL_VERIFICATION_REQUIRED_BEFORE_ACCEPTING_OR_REJECTING_INVITATION);
340
362
  const organization = await adapter.findOrganizationById(invitation.organizationId);
341
363
  if (!organization) throw APIError.from("BAD_REQUEST", ORGANIZATION_ERROR_CODES.ORGANIZATION_NOT_FOUND);
342
364
  if (options?.organizationHooks?.beforeRejectInvitation) await options?.organizationHooks.beforeRejectInvitation({
@@ -455,7 +477,11 @@ const getInvitation = (options) => createAuthEndpoint("/organization/get-invitat
455
477
  const invitation = await adapter.findInvitationById(ctx.query.id);
456
478
  if (!invitation || invitation.status !== "pending" || invitation.expiresAt < /* @__PURE__ */ new Date()) throw APIError.fromStatus("BAD_REQUEST", { message: "Invitation not found!" });
457
479
  if (invitation.email.toLowerCase() !== session.user.email.toLowerCase()) throw APIError.from("FORBIDDEN", ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_THE_RECIPIENT_OF_THE_INVITATION);
458
- if ((ctx.context.orgOptions.requireEmailVerificationOnInvitation ?? true) && !session.user.emailVerified) throw APIError.from("FORBIDDEN", ORGANIZATION_ERROR_CODES.EMAIL_VERIFICATION_REQUIRED_FOR_INVITATION);
480
+ if (shouldRequireVerifiedEmailForInvitationIdAction({
481
+ organizationOptions: ctx.context.orgOptions,
482
+ advancedGenerateId: getAdvancedGenerateId(ctx.context.options.advanced),
483
+ databaseGenerateId: ctx.context.options.advanced?.database?.generateId
484
+ }) && !session.user.emailVerified) throw APIError.from("FORBIDDEN", ORGANIZATION_ERROR_CODES.EMAIL_VERIFICATION_REQUIRED_FOR_INVITATION);
459
485
  const organization = await adapter.findOrganizationById(invitation.organizationId);
460
486
  if (!organization) throw APIError.from("BAD_REQUEST", ORGANIZATION_ERROR_CODES.ORGANIZATION_NOT_FOUND);
461
487
  const member = await adapter.findMemberByOrgId({
@@ -541,7 +567,7 @@ const listUserInvitations = (options) => createAuthEndpoint("/organization/list-
541
567
  }, async (ctx) => {
542
568
  const session = await getSessionFromCtx(ctx);
543
569
  if (ctx.request && ctx.query?.email) throw APIError.fromStatus("BAD_REQUEST", { message: "User email cannot be passed for client side API calls." });
544
- if (session && (ctx.context.orgOptions.requireEmailVerificationOnInvitation ?? true) && !session.user.emailVerified) throw APIError.from("FORBIDDEN", ORGANIZATION_ERROR_CODES.EMAIL_VERIFICATION_REQUIRED_FOR_INVITATION);
570
+ if (session && !session.user.emailVerified) throw APIError.from("FORBIDDEN", ORGANIZATION_ERROR_CODES.EMAIL_VERIFICATION_REQUIRED_FOR_INVITATION);
545
571
  const userEmail = session?.user.email || ctx.query?.email;
546
572
  if (!userEmail) throw APIError.fromStatus("BAD_REQUEST", { message: "Missing session headers, or email query parameter." });
547
573
  const pendingInvitations = (await getOrgAdapter(ctx.context, options).listUserInvitations(userEmail)).filter((inv) => inv.status === "pending");
@@ -165,19 +165,26 @@ interface OrganizationOptions {
165
165
  */
166
166
  cancelPendingInvitationsOnReInvite?: boolean | undefined;
167
167
  /**
168
- * Require email verification on session-authenticated recipient invitation
169
- * calls (accept, reject, get, list). Defaults to `true` so unverified
170
- * accounts registered against a victim's email cannot accept, read, or
171
- * enumerate invitations targeted at that email. Server-side
172
- * `listUserInvitations` calls without a session (caller passes
173
- * `ctx.query.email`) continue to bypass the gate because the caller is
174
- * trusted. Set to `false` for backward compatibility on apps that do not
175
- * require email verification; understand the takeover risk before doing so.
168
+ * Require email verification before session-authenticated recipient
169
+ * invitation calls that carry an invitation ID (accept, reject, get).
176
170
  *
177
- * @default true
171
+ * When unset, Better Auth preserves the normal emailed-invitation flow for
172
+ * built-in opaque invitation IDs, including the default generator and
173
+ * `advanced.database.generateId: "uuid"`. It requires verification for
174
+ * externally controlled or predictable invitation IDs, such as
175
+ * `advanced.database.generateId: "serial"` / `false` or custom ID
176
+ * generation.
177
+ *
178
+ * Set this option to `true` when invitation IDs may be visible outside the
179
+ * invited user's mailbox, when organization invitation lists are exposed to
180
+ * members, or when verified email should be the ownership proof for by-ID
181
+ * invitation actions. Client-side `listUserInvitations` calls always require
182
+ * a verified session email because they enumerate invitation IDs from
183
+ * `session.user.email`. Server-side `listUserInvitations` calls without a
184
+ * session (caller passes `ctx.query.email`) continue to bypass the gate
185
+ * because the caller is trusted.
178
186
  *
179
- * @deprecated The option will be removed on the next minor; the gate will
180
- * become unconditional. Plan to verify emails before invitation acceptance.
187
+ * @default undefined
181
188
  */
182
189
  requireEmailVerificationOnInvitation?: boolean | undefined;
183
190
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "better-auth",
3
- "version": "1.6.13",
3
+ "version": "1.6.14",
4
4
  "description": "The most comprehensive authentication framework for TypeScript.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -489,13 +489,13 @@
489
489
  "kysely": "^0.28.17 || ^0.29.0",
490
490
  "nanostores": "^1.1.1",
491
491
  "zod": "^4.3.6",
492
- "@better-auth/core": "1.6.13",
493
- "@better-auth/drizzle-adapter": "1.6.13",
494
- "@better-auth/kysely-adapter": "1.6.13",
495
- "@better-auth/memory-adapter": "1.6.13",
496
- "@better-auth/mongo-adapter": "1.6.13",
497
- "@better-auth/prisma-adapter": "1.6.13",
498
- "@better-auth/telemetry": "1.6.13"
492
+ "@better-auth/core": "1.6.14",
493
+ "@better-auth/drizzle-adapter": "1.6.14",
494
+ "@better-auth/kysely-adapter": "1.6.14",
495
+ "@better-auth/memory-adapter": "1.6.14",
496
+ "@better-auth/mongo-adapter": "1.6.14",
497
+ "@better-auth/prisma-adapter": "1.6.14",
498
+ "@better-auth/telemetry": "1.6.14"
499
499
  },
500
500
  "devDependencies": {
501
501
  "@lynx-js/react": "^0.116.3",