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.
- package/dist/cookies/index.mjs +1 -1
- package/dist/db/to-zod.d.mts +2 -2
- package/dist/db/to-zod.mjs +1 -1
- package/dist/package.mjs +1 -1
- package/dist/plugins/organization/adapter.mjs +2 -0
- package/dist/plugins/organization/routes/crud-access-control.d.mts +1 -1
- package/dist/plugins/organization/routes/crud-invites.mjs +30 -4
- package/dist/plugins/organization/types.d.mts +18 -11
- package/package.json +8 -8
package/dist/cookies/index.mjs
CHANGED
|
@@ -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)
|
|
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;
|
package/dist/db/to-zod.d.mts
CHANGED
|
@@ -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:
|
|
29
|
-
} ?
|
|
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;
|
package/dist/db/to-zod.mjs
CHANGED
|
@@ -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.
|
|
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
|
@@ -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.
|
|
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 ((
|
|
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 ((
|
|
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 ((
|
|
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 &&
|
|
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
|
|
169
|
-
* calls (accept, reject, get
|
|
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
|
-
*
|
|
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
|
-
* @
|
|
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.
|
|
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.
|
|
493
|
-
"@better-auth/drizzle-adapter": "1.6.
|
|
494
|
-
"@better-auth/kysely-adapter": "1.6.
|
|
495
|
-
"@better-auth/memory-adapter": "1.6.
|
|
496
|
-
"@better-auth/mongo-adapter": "1.6.
|
|
497
|
-
"@better-auth/prisma-adapter": "1.6.
|
|
498
|
-
"@better-auth/telemetry": "1.6.
|
|
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",
|