@better-auth/infra 0.2.7 → 0.2.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.
- package/dist/client.d.mts +4 -5
- package/dist/{dash-client-B6U89e1S.d.mts → dash-client-CZzJyRrV.d.mts} +1 -3
- package/dist/index.d.mts +590 -564
- package/dist/index.mjs +217 -81
- package/dist/native.d.mts +1 -1
- package/package.json +7 -7
package/dist/index.mjs
CHANGED
|
@@ -535,7 +535,7 @@ const initSessionEvents = (tracker) => {
|
|
|
535
535
|
eventData: {
|
|
536
536
|
userId: session.userId,
|
|
537
537
|
userName: user.name,
|
|
538
|
-
userEmail: user.email,
|
|
538
|
+
userEmail: user.email ?? "unknown",
|
|
539
539
|
sessionId: session.id,
|
|
540
540
|
triggeredBy: trigger.triggeredBy,
|
|
541
541
|
triggerContext: trigger.triggerContext
|
|
@@ -621,10 +621,10 @@ const initUserEvents = (tracker) => {
|
|
|
621
621
|
trackEvent({
|
|
622
622
|
eventKey: user.id,
|
|
623
623
|
eventType: EVENT_TYPES.USER_CREATED,
|
|
624
|
-
eventDisplayName: `${user.name || user.email} signed up`,
|
|
624
|
+
eventDisplayName: `${user.name || user.email || "unknown"} signed up`,
|
|
625
625
|
eventData: {
|
|
626
626
|
userId: user.id,
|
|
627
|
-
userEmail: user.email,
|
|
627
|
+
userEmail: user.email ?? "unknown",
|
|
628
628
|
userName: user.name,
|
|
629
629
|
triggeredBy: trigger.triggeredBy,
|
|
630
630
|
triggerContext: trigger.triggerContext
|
|
@@ -642,7 +642,7 @@ const initUserEvents = (tracker) => {
|
|
|
642
642
|
eventDisplayName: "User deleted",
|
|
643
643
|
eventData: {
|
|
644
644
|
userId: user.id,
|
|
645
|
-
userEmail: user.email,
|
|
645
|
+
userEmail: user.email ?? "unknown",
|
|
646
646
|
userName: user.name,
|
|
647
647
|
triggeredBy: trigger.triggeredBy,
|
|
648
648
|
triggerContext: trigger.triggerContext
|
|
@@ -660,7 +660,7 @@ const initUserEvents = (tracker) => {
|
|
|
660
660
|
eventDisplayName: "Profile updated",
|
|
661
661
|
eventData: {
|
|
662
662
|
userId: user.id,
|
|
663
|
-
userEmail: user.email,
|
|
663
|
+
userEmail: user.email ?? "unknown",
|
|
664
664
|
userName: user.name,
|
|
665
665
|
updatedFields: Object.keys(ctx?.body || {}),
|
|
666
666
|
triggeredBy: trigger.triggeredBy,
|
|
@@ -679,7 +679,7 @@ const initUserEvents = (tracker) => {
|
|
|
679
679
|
eventDisplayName: "Profile image updated",
|
|
680
680
|
eventData: {
|
|
681
681
|
userId: user.id,
|
|
682
|
-
userEmail: user.email,
|
|
682
|
+
userEmail: user.email ?? "unknown",
|
|
683
683
|
userName: user.name,
|
|
684
684
|
triggeredBy: trigger.triggeredBy,
|
|
685
685
|
triggerContext: trigger.triggerContext
|
|
@@ -699,7 +699,7 @@ const initUserEvents = (tracker) => {
|
|
|
699
699
|
eventDisplayName: `User banned${reasonSuffix}${expiresSuffix}`,
|
|
700
700
|
eventData: {
|
|
701
701
|
userId: user.id,
|
|
702
|
-
userEmail: user.email,
|
|
702
|
+
userEmail: user.email ?? "unknown",
|
|
703
703
|
userName: user.name,
|
|
704
704
|
banned: user.banned,
|
|
705
705
|
banReason: user.banReason,
|
|
@@ -720,7 +720,7 @@ const initUserEvents = (tracker) => {
|
|
|
720
720
|
eventDisplayName: "User unbanned",
|
|
721
721
|
eventData: {
|
|
722
722
|
userId: user.id,
|
|
723
|
-
userEmail: user.email,
|
|
723
|
+
userEmail: user.email ?? "unknown",
|
|
724
724
|
userName: user.name,
|
|
725
725
|
banned: user.banned,
|
|
726
726
|
triggeredBy: trigger.triggeredBy,
|
|
@@ -739,7 +739,7 @@ const initUserEvents = (tracker) => {
|
|
|
739
739
|
eventDisplayName: "Email verified",
|
|
740
740
|
eventData: {
|
|
741
741
|
userId: user.id,
|
|
742
|
-
userEmail: user.email,
|
|
742
|
+
userEmail: user.email ?? "unknown",
|
|
743
743
|
userName: user.name,
|
|
744
744
|
triggeredBy: trigger.triggeredBy,
|
|
745
745
|
triggerContext: trigger.triggerContext
|
|
@@ -1210,7 +1210,7 @@ function createSecurityClient(conn, $api, options, onSecurityEvent) {
|
|
|
1210
1210
|
return "";
|
|
1211
1211
|
}
|
|
1212
1212
|
},
|
|
1213
|
-
async checkImpossibleTravel(userId, currentLocation, visitorId) {
|
|
1213
|
+
async checkImpossibleTravel(userId, currentLocation, visitorId, ip = null) {
|
|
1214
1214
|
if (!options.impossibleTravel?.enabled || !currentLocation) return null;
|
|
1215
1215
|
try {
|
|
1216
1216
|
const data = await $api("/security/impossible-travel", {
|
|
@@ -1219,6 +1219,7 @@ function createSecurityClient(conn, $api, options, onSecurityEvent) {
|
|
|
1219
1219
|
userId,
|
|
1220
1220
|
visitorId,
|
|
1221
1221
|
location: currentLocation,
|
|
1222
|
+
ip,
|
|
1222
1223
|
config: options
|
|
1223
1224
|
}
|
|
1224
1225
|
});
|
|
@@ -1249,14 +1250,15 @@ function createSecurityClient(conn, $api, options, onSecurityEvent) {
|
|
|
1249
1250
|
/**
|
|
1250
1251
|
* Store user's last known location for impossible travel detection
|
|
1251
1252
|
*/
|
|
1252
|
-
async storeLastLocation(userId, location) {
|
|
1253
|
-
if (!location) return;
|
|
1253
|
+
async storeLastLocation(userId, location, ip = null) {
|
|
1254
|
+
if (!options.impossibleTravel?.enabled || !location) return;
|
|
1254
1255
|
try {
|
|
1255
1256
|
await $api("/security/store-last-login", {
|
|
1256
1257
|
method: "POST",
|
|
1257
1258
|
body: {
|
|
1258
1259
|
userId,
|
|
1259
|
-
location
|
|
1260
|
+
location,
|
|
1261
|
+
ip
|
|
1260
1262
|
}
|
|
1261
1263
|
});
|
|
1262
1264
|
} catch (error) {
|
|
@@ -1497,7 +1499,9 @@ const all = new Set([
|
|
|
1497
1499
|
"/send-verification-email",
|
|
1498
1500
|
"/change-email",
|
|
1499
1501
|
"/organization/invite-member",
|
|
1500
|
-
"/dash/organization/invite-member"
|
|
1502
|
+
"/dash/organization/invite-member",
|
|
1503
|
+
"/dash/create-user",
|
|
1504
|
+
"/dash/update-user"
|
|
1501
1505
|
]);
|
|
1502
1506
|
/**
|
|
1503
1507
|
* Path is one of `[
|
|
@@ -1516,7 +1520,9 @@ const all = new Set([
|
|
|
1516
1520
|
* '/send-verification-email',
|
|
1517
1521
|
* '/change-email',
|
|
1518
1522
|
* '/organization/invite-member',
|
|
1519
|
-
* '/dash/organization/invite-member'
|
|
1523
|
+
* '/dash/organization/invite-member',
|
|
1524
|
+
* '/dash/create-user',
|
|
1525
|
+
* '/dash/update-user'
|
|
1520
1526
|
* ]`.
|
|
1521
1527
|
* @param context Request context
|
|
1522
1528
|
* @param context.path Request path
|
|
@@ -2088,33 +2094,42 @@ const sentinel = (options) => {
|
|
|
2088
2094
|
emailValidation: opts.security?.emailValidation,
|
|
2089
2095
|
emailNormalization: opts.security?.emailNormalization,
|
|
2090
2096
|
databaseHooks: {
|
|
2091
|
-
user: {
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2097
|
+
user: {
|
|
2098
|
+
create: {
|
|
2099
|
+
async before(user, ctx) {
|
|
2100
|
+
if (!ctx) return;
|
|
2101
|
+
const visitorId = ctx.context.visitorId;
|
|
2102
|
+
if (visitorId && opts.security?.freeTrialAbuse?.enabled) {
|
|
2103
|
+
recordCheck(ctx, "free_trial_abuse");
|
|
2104
|
+
const abuseCheck = await securityService.checkFreeTrialAbuse(visitorId);
|
|
2105
|
+
if (abuseCheck.isAbuse && abuseCheck.action === "block") {
|
|
2106
|
+
setOutcome(ctx, "blocked", "free_trial_abuse", {
|
|
2107
|
+
accountCount: abuseCheck.accountCount,
|
|
2108
|
+
maxAccounts: abuseCheck.maxAccounts
|
|
2109
|
+
});
|
|
2110
|
+
emitEvaluation(ctx, trackEvent);
|
|
2111
|
+
throw new APIError("FORBIDDEN", { message: "Account creation is not allowed from this device." });
|
|
2112
|
+
}
|
|
2105
2113
|
}
|
|
2114
|
+
if (user.email && typeof user.email === "string" && isEmailNormalizationEnabled(opts.security)) return { data: {
|
|
2115
|
+
...user,
|
|
2116
|
+
email: normalizeEmail(user.email, ctx.context)
|
|
2117
|
+
} };
|
|
2118
|
+
},
|
|
2119
|
+
async after(user, ctx) {
|
|
2120
|
+
if (!ctx) return;
|
|
2121
|
+
const visitorId = ctx.context.visitorId;
|
|
2122
|
+
if (visitorId && opts.security?.freeTrialAbuse?.enabled) await ctx.context.runInBackgroundOrAwait(securityService.trackFreeTrialSignup(visitorId, user.id));
|
|
2106
2123
|
}
|
|
2124
|
+
},
|
|
2125
|
+
update: { async before(user, ctx) {
|
|
2126
|
+
if (!ctx) return;
|
|
2107
2127
|
if (user.email && typeof user.email === "string" && isEmailNormalizationEnabled(opts.security)) return { data: {
|
|
2108
2128
|
...user,
|
|
2109
2129
|
email: normalizeEmail(user.email, ctx.context)
|
|
2110
2130
|
} };
|
|
2111
|
-
}
|
|
2112
|
-
|
|
2113
|
-
if (!ctx) return;
|
|
2114
|
-
const visitorId = ctx.context.visitorId;
|
|
2115
|
-
if (visitorId && opts.security?.freeTrialAbuse?.enabled) await ctx.context.runInBackgroundOrAwait(securityService.trackFreeTrialSignup(visitorId, user.id));
|
|
2116
|
-
}
|
|
2117
|
-
} },
|
|
2131
|
+
} }
|
|
2132
|
+
},
|
|
2118
2133
|
session: { create: {
|
|
2119
2134
|
async before(session, ctx) {
|
|
2120
2135
|
if (!ctx) return;
|
|
@@ -2122,7 +2137,7 @@ const sentinel = (options) => {
|
|
|
2122
2137
|
const identification = ctx.context.identification;
|
|
2123
2138
|
if (session.userId && identification?.location && visitorId) {
|
|
2124
2139
|
recordCheck(ctx, "impossible_travel");
|
|
2125
|
-
const travelCheck = await securityService.checkImpossibleTravel(session.userId, identification.location, visitorId);
|
|
2140
|
+
const travelCheck = await securityService.checkImpossibleTravel(session.userId, identification.location, visitorId, identification.ip);
|
|
2126
2141
|
if (travelCheck?.isImpossible) {
|
|
2127
2142
|
if (travelCheck.action === "block") {
|
|
2128
2143
|
setOutcome(ctx, "blocked", "impossible_travel", {
|
|
@@ -2201,7 +2216,7 @@ const sentinel = (options) => {
|
|
|
2201
2216
|
}
|
|
2202
2217
|
}
|
|
2203
2218
|
}
|
|
2204
|
-
if (identification?.location) await ctx.context.runInBackgroundOrAwait(securityService.storeLastLocation(session.userId, identification.location));
|
|
2219
|
+
if (opts.security?.impossibleTravel?.enabled && identification?.location) await ctx.context.runInBackgroundOrAwait(securityService.storeLastLocation(session.userId, identification.location, identification.ip));
|
|
2205
2220
|
}
|
|
2206
2221
|
} }
|
|
2207
2222
|
}
|
|
@@ -2356,7 +2371,7 @@ const initInvitationEvents = (tracker) => {
|
|
|
2356
2371
|
inviteeTeamId: invitation.teamId,
|
|
2357
2372
|
inviterId: inviter.id,
|
|
2358
2373
|
inviterName: inviter.name,
|
|
2359
|
-
inviterEmail: inviter.email,
|
|
2374
|
+
inviterEmail: inviter.email ?? "unknown",
|
|
2360
2375
|
triggeredBy: trigger.triggeredBy,
|
|
2361
2376
|
triggerContext: trigger.triggerContext
|
|
2362
2377
|
}
|
|
@@ -2376,7 +2391,7 @@ const initInvitationEvents = (tracker) => {
|
|
|
2376
2391
|
inviteeRole: invitation.role,
|
|
2377
2392
|
inviteeTeamId: invitation.teamId,
|
|
2378
2393
|
acceptedById: acceptedBy.id,
|
|
2379
|
-
acceptedByEmail: acceptedBy.email,
|
|
2394
|
+
acceptedByEmail: acceptedBy.email ?? "unknown",
|
|
2380
2395
|
acceptedByName: acceptedBy.name,
|
|
2381
2396
|
memberId: member.id,
|
|
2382
2397
|
memberRole: member.role,
|
|
@@ -2399,7 +2414,7 @@ const initInvitationEvents = (tracker) => {
|
|
|
2399
2414
|
inviteeRole: invitation.role,
|
|
2400
2415
|
inviteeTeamId: invitation.teamId,
|
|
2401
2416
|
rejectedById: rejectedBy.id,
|
|
2402
|
-
rejectedByEmail: rejectedBy.email,
|
|
2417
|
+
rejectedByEmail: rejectedBy.email ?? "unknown",
|
|
2403
2418
|
rejectedByName: rejectedBy.name,
|
|
2404
2419
|
triggeredBy: trigger.triggeredBy,
|
|
2405
2420
|
triggerContext: trigger.triggerContext
|
|
@@ -2421,7 +2436,7 @@ const initInvitationEvents = (tracker) => {
|
|
|
2421
2436
|
inviteeTeamId: invitation.teamId,
|
|
2422
2437
|
cancelledById: cancelledBy.id,
|
|
2423
2438
|
cancelledByName: cancelledBy.name,
|
|
2424
|
-
cancelledByEmail: cancelledBy.email,
|
|
2439
|
+
cancelledByEmail: cancelledBy.email ?? "unknown",
|
|
2425
2440
|
triggeredBy: trigger.triggeredBy,
|
|
2426
2441
|
triggerContext: trigger.triggerContext
|
|
2427
2442
|
}
|
|
@@ -2451,7 +2466,7 @@ const initMemberEvents = (tracker) => {
|
|
|
2451
2466
|
memberName: user.name,
|
|
2452
2467
|
role: member.role,
|
|
2453
2468
|
memberId: member.id,
|
|
2454
|
-
memberEmail: user.email,
|
|
2469
|
+
memberEmail: user.email ?? "unknown",
|
|
2455
2470
|
triggeredBy: trigger.triggeredBy,
|
|
2456
2471
|
triggerContext: trigger.triggerContext
|
|
2457
2472
|
}
|
|
@@ -2470,7 +2485,7 @@ const initMemberEvents = (tracker) => {
|
|
|
2470
2485
|
memberName: user.name,
|
|
2471
2486
|
role: member.role,
|
|
2472
2487
|
memberId: member.id,
|
|
2473
|
-
memberEmail: user.email,
|
|
2488
|
+
memberEmail: user.email ?? "unknown",
|
|
2474
2489
|
triggeredBy: trigger.triggeredBy,
|
|
2475
2490
|
triggerContext: trigger.triggerContext
|
|
2476
2491
|
}
|
|
@@ -2490,7 +2505,7 @@ const initMemberEvents = (tracker) => {
|
|
|
2490
2505
|
newRole: member.role,
|
|
2491
2506
|
oldRole: previousRole,
|
|
2492
2507
|
memberId: member.id,
|
|
2493
|
-
memberEmail: user.email,
|
|
2508
|
+
memberEmail: user.email ?? "unknown",
|
|
2494
2509
|
triggeredBy: trigger.triggeredBy,
|
|
2495
2510
|
triggerContext: trigger.triggerContext
|
|
2496
2511
|
}
|
|
@@ -2779,7 +2794,7 @@ const jwtValidateMiddleware = (options) => {
|
|
|
2779
2794
|
};
|
|
2780
2795
|
//#endregion
|
|
2781
2796
|
//#region src/version.ts
|
|
2782
|
-
const PLUGIN_VERSION = "0.2.
|
|
2797
|
+
const PLUGIN_VERSION = "0.2.9";
|
|
2783
2798
|
//#endregion
|
|
2784
2799
|
//#region src/routes/auth/config.ts
|
|
2785
2800
|
const PLUGIN_OPTIONS_EXCLUDE_KEYS = { stripe: new Set(["stripeClient"]) };
|
|
@@ -3794,13 +3809,13 @@ const completeInvitation = (options) => {
|
|
|
3794
3809
|
* Used by the platform to verify before sending invitation
|
|
3795
3810
|
* This is different from /dash/organization/check-user-by-email which also checks membership
|
|
3796
3811
|
*/
|
|
3797
|
-
const checkUserExists = (
|
|
3812
|
+
const checkUserExists = (options) => {
|
|
3798
3813
|
return createAuthEndpoint("/dash/check-user-exists", {
|
|
3799
3814
|
method: "POST",
|
|
3815
|
+
use: [jwtMiddleware(options)],
|
|
3800
3816
|
body: z$1.object({ email: z$1.email() })
|
|
3801
3817
|
}, async (ctx) => {
|
|
3802
3818
|
const { email } = ctx.body;
|
|
3803
|
-
if (!ctx.request?.headers.get("Authorization")) throw new APIError("UNAUTHORIZED", { message: "Authorization required" });
|
|
3804
3819
|
const normalizedEmail = normalizeEmail(email, ctx.context);
|
|
3805
3820
|
const existingUser = await ctx.context.internalAdapter.findUserByEmail(normalizedEmail).then((user) => user?.user);
|
|
3806
3821
|
return {
|
|
@@ -3837,7 +3852,7 @@ const listOrganizationInvitations = (options) => {
|
|
|
3837
3852
|
operator: "in"
|
|
3838
3853
|
}]
|
|
3839
3854
|
}) : [];
|
|
3840
|
-
const userByEmail = new Map(users.map((u) => [normalizeEmail(u.email, ctx.context), u]));
|
|
3855
|
+
const userByEmail = new Map(users.filter((u) => u.email != null).map((u) => [normalizeEmail(u.email, ctx.context), u]));
|
|
3841
3856
|
return invitations.map((invitation) => {
|
|
3842
3857
|
const invitationEmail = normalizeEmail(invitation.email, ctx.context);
|
|
3843
3858
|
const user = userByEmail.get(invitationEmail);
|
|
@@ -3846,7 +3861,7 @@ const listOrganizationInvitations = (options) => {
|
|
|
3846
3861
|
user: user ? {
|
|
3847
3862
|
id: user.id,
|
|
3848
3863
|
name: user.name,
|
|
3849
|
-
email: user.email,
|
|
3864
|
+
email: user.email ?? void 0,
|
|
3850
3865
|
image: user.image || null
|
|
3851
3866
|
} : null
|
|
3852
3867
|
};
|
|
@@ -3932,7 +3947,7 @@ const checkUserByEmail = (options) => {
|
|
|
3932
3947
|
user: {
|
|
3933
3948
|
id: user.id,
|
|
3934
3949
|
name: user.name,
|
|
3935
|
-
email: user.email,
|
|
3950
|
+
email: user.email ?? void 0,
|
|
3936
3951
|
image: user.image ?? null
|
|
3937
3952
|
},
|
|
3938
3953
|
isAlreadyMember: !!existingMember
|
|
@@ -4088,18 +4103,18 @@ const listOrganizationMembers = (options) => {
|
|
|
4088
4103
|
for (const m of members) {
|
|
4089
4104
|
const joinedUser = Array.isArray(m.user) ? m.user[0] : m.user;
|
|
4090
4105
|
if (!joinedUser) continue;
|
|
4091
|
-
const invitation = invitationByEmail.get(joinedUser.email.toLowerCase());
|
|
4106
|
+
const invitation = joinedUser.email ? invitationByEmail.get(joinedUser.email.toLowerCase()) : void 0;
|
|
4092
4107
|
const inviter = invitation ? inviterById.get(invitation.inviterId) : void 0;
|
|
4093
4108
|
const user = {
|
|
4094
4109
|
id: joinedUser.id,
|
|
4095
|
-
email: joinedUser.email,
|
|
4110
|
+
email: joinedUser.email ?? void 0,
|
|
4096
4111
|
name: joinedUser.name,
|
|
4097
4112
|
image: joinedUser.image ?? null
|
|
4098
4113
|
};
|
|
4099
4114
|
const invitedBy = inviter ? {
|
|
4100
4115
|
id: inviter.id,
|
|
4101
4116
|
name: inviter.name,
|
|
4102
|
-
email: inviter.email,
|
|
4117
|
+
email: inviter.email ?? void 0,
|
|
4103
4118
|
image: inviter.image ?? null
|
|
4104
4119
|
} : null;
|
|
4105
4120
|
result.push({
|
|
@@ -4411,6 +4426,121 @@ async function withConcurrency(items, fn, options) {
|
|
|
4411
4426
|
return results;
|
|
4412
4427
|
}
|
|
4413
4428
|
//#endregion
|
|
4429
|
+
//#region src/routes/organizations/schemas.ts
|
|
4430
|
+
const DENIED_ORG_WRITE_KEYS = new Set([
|
|
4431
|
+
"id",
|
|
4432
|
+
"createdAt",
|
|
4433
|
+
"updatedAt"
|
|
4434
|
+
]);
|
|
4435
|
+
const CREATE_NON_PERSISTED_KEYS = new Set(["defaultTeamName"]);
|
|
4436
|
+
const CREATE_REQUEST_ONLY_KEYS = new Set(["userId", "skipDefaultTeam"]);
|
|
4437
|
+
const CORE_CREATE_KEYS = [
|
|
4438
|
+
"name",
|
|
4439
|
+
"slug",
|
|
4440
|
+
"logo"
|
|
4441
|
+
];
|
|
4442
|
+
const CORE_UPDATE_KEYS = [
|
|
4443
|
+
"name",
|
|
4444
|
+
"slug",
|
|
4445
|
+
"logo",
|
|
4446
|
+
"metadata"
|
|
4447
|
+
];
|
|
4448
|
+
function getOrganizationAdditionalFields(orgOptions) {
|
|
4449
|
+
return orgOptions?.schema?.organization?.additionalFields ?? {};
|
|
4450
|
+
}
|
|
4451
|
+
function getWritableOrganizationAdditionalFieldNames(orgOptions) {
|
|
4452
|
+
const additional = getOrganizationAdditionalFields(orgOptions);
|
|
4453
|
+
return Object.entries(additional).filter(([, field]) => {
|
|
4454
|
+
return field.input !== false;
|
|
4455
|
+
}).map(([name]) => name);
|
|
4456
|
+
}
|
|
4457
|
+
function getWritableOrganizationFieldNames(orgOptions, mode) {
|
|
4458
|
+
const keys = new Set(mode === "create" ? CORE_CREATE_KEYS : CORE_UPDATE_KEYS);
|
|
4459
|
+
for (const name of getWritableOrganizationAdditionalFieldNames(orgOptions)) keys.add(name);
|
|
4460
|
+
return keys;
|
|
4461
|
+
}
|
|
4462
|
+
/**
|
|
4463
|
+
* Returns only organization fields that may be persisted on create/update.
|
|
4464
|
+
* Strips unknown keys, denied keys, and create-only request fields.
|
|
4465
|
+
*/
|
|
4466
|
+
function pickWritableOrganizationFields(body, orgOptions, mode) {
|
|
4467
|
+
const allowed = getWritableOrganizationFieldNames(orgOptions, mode);
|
|
4468
|
+
const skip = new Set([
|
|
4469
|
+
...DENIED_ORG_WRITE_KEYS,
|
|
4470
|
+
...mode === "create" ? CREATE_NON_PERSISTED_KEYS : [],
|
|
4471
|
+
...mode === "create" ? CREATE_REQUEST_ONLY_KEYS : []
|
|
4472
|
+
]);
|
|
4473
|
+
const result = {};
|
|
4474
|
+
for (const [key, value] of Object.entries(body)) {
|
|
4475
|
+
if (skip.has(key) || !allowed.has(key) || value === void 0) continue;
|
|
4476
|
+
result[key] = value;
|
|
4477
|
+
}
|
|
4478
|
+
return result;
|
|
4479
|
+
}
|
|
4480
|
+
const BaseCreateOrgCoreBodySchema = z$1.object({
|
|
4481
|
+
name: z$1.string(),
|
|
4482
|
+
slug: z$1.string(),
|
|
4483
|
+
logo: z$1.string().optional(),
|
|
4484
|
+
defaultTeamName: z$1.string().optional()
|
|
4485
|
+
});
|
|
4486
|
+
const BaseUpdateOrgCoreBodySchema = z$1.object({
|
|
4487
|
+
logo: z$1.union([z$1.url(), z$1.literal("")]).optional(),
|
|
4488
|
+
name: z$1.string().optional(),
|
|
4489
|
+
slug: z$1.string().optional(),
|
|
4490
|
+
metadata: z$1.string().optional()
|
|
4491
|
+
});
|
|
4492
|
+
const CreateOrganizationBodySchema = BaseCreateOrgCoreBodySchema.catchall(z$1.unknown());
|
|
4493
|
+
const UpdateOrganizationBodySchema = BaseUpdateOrgCoreBodySchema.catchall(z$1.unknown());
|
|
4494
|
+
function createSchemaForDBField(field) {
|
|
4495
|
+
switch (field.type) {
|
|
4496
|
+
case "number": return field.required ? z$1.coerce.number() : z$1.coerce.number().optional();
|
|
4497
|
+
case "boolean": return field.required ? z$1.coerce.boolean() : z$1.coerce.boolean().optional();
|
|
4498
|
+
case "date": return field.required ? z$1.union([z$1.string().min(1), z$1.coerce.date()]) : z$1.union([z$1.string(), z$1.coerce.date()]).optional();
|
|
4499
|
+
default: return field.required ? z$1.string().min(1) : z$1.string().optional();
|
|
4500
|
+
}
|
|
4501
|
+
}
|
|
4502
|
+
/**
|
|
4503
|
+
* Validates required core + configured additional fields on create.
|
|
4504
|
+
* Call after pickWritableOrgFields if you need required additional field checks.
|
|
4505
|
+
*/
|
|
4506
|
+
function validateWritableCreateOrganizationFields(data, options) {
|
|
4507
|
+
const shape = {
|
|
4508
|
+
name: z$1.string().min(1),
|
|
4509
|
+
slug: z$1.string().min(1),
|
|
4510
|
+
logo: z$1.string().optional()
|
|
4511
|
+
};
|
|
4512
|
+
const additional = getOrganizationAdditionalFields(options);
|
|
4513
|
+
for (const [name, field] of Object.entries(additional)) {
|
|
4514
|
+
if (field.input === false) continue;
|
|
4515
|
+
shape[name] = createSchemaForDBField(field);
|
|
4516
|
+
}
|
|
4517
|
+
const result = z$1.object(shape).strict().safeParse(data);
|
|
4518
|
+
if (!result.success) throw result.error;
|
|
4519
|
+
}
|
|
4520
|
+
/**
|
|
4521
|
+
* Ensures at least one field is present on update and validates additional field types.
|
|
4522
|
+
*/
|
|
4523
|
+
function validateWritableOrganizationUpdateFields(data, orgOptions) {
|
|
4524
|
+
if (Object.keys(data).length === 0) throw new z$1.ZodError([{
|
|
4525
|
+
code: "custom",
|
|
4526
|
+
message: "No valid fields to update",
|
|
4527
|
+
path: []
|
|
4528
|
+
}]);
|
|
4529
|
+
const shape = {
|
|
4530
|
+
name: z$1.string().min(1).optional(),
|
|
4531
|
+
slug: z$1.string().min(1).optional(),
|
|
4532
|
+
logo: z$1.union([z$1.url(), z$1.literal("")]).optional(),
|
|
4533
|
+
metadata: z$1.string().optional()
|
|
4534
|
+
};
|
|
4535
|
+
const additional = getOrganizationAdditionalFields(orgOptions);
|
|
4536
|
+
for (const [name, field] of Object.entries(additional)) {
|
|
4537
|
+
if (field.input === false) continue;
|
|
4538
|
+
shape[name] = createSchemaForDBField(field);
|
|
4539
|
+
}
|
|
4540
|
+
const result = z$1.object(shape).partial().strict().safeParse(data);
|
|
4541
|
+
if (!result.success) throw result.error;
|
|
4542
|
+
}
|
|
4543
|
+
//#endregion
|
|
4414
4544
|
//#region src/routes/organizations/organization.ts
|
|
4415
4545
|
const listOrganizations = (options) => {
|
|
4416
4546
|
return createAuthEndpoint("/dash/list-organizations", {
|
|
@@ -4542,8 +4672,8 @@ const listOrganizations = (options) => {
|
|
|
4542
4672
|
const members = organization.member.map((m) => userMap.get(m.userId)).filter((u) => u !== void 0).map((u) => ({
|
|
4543
4673
|
id: u.id,
|
|
4544
4674
|
name: u.name,
|
|
4545
|
-
email: u.email,
|
|
4546
|
-
image: u.image
|
|
4675
|
+
email: u.email ?? void 0,
|
|
4676
|
+
image: u.image ?? null
|
|
4547
4677
|
}));
|
|
4548
4678
|
const { member: _members, ...org } = organization;
|
|
4549
4679
|
return {
|
|
@@ -4729,15 +4859,11 @@ const createOrganization = (options) => {
|
|
|
4729
4859
|
userId: z$1.string(),
|
|
4730
4860
|
skipDefaultTeam: z$1.boolean().optional().default(false)
|
|
4731
4861
|
}))],
|
|
4732
|
-
body:
|
|
4733
|
-
name: z$1.string(),
|
|
4734
|
-
slug: z$1.string(),
|
|
4735
|
-
logo: z$1.string().optional(),
|
|
4736
|
-
defaultTeamName: z$1.string().optional()
|
|
4737
|
-
})
|
|
4862
|
+
body: CreateOrganizationBodySchema
|
|
4738
4863
|
}, async (ctx) => {
|
|
4739
4864
|
const orgOptions = requireOrganizationPlugin(ctx).options || {};
|
|
4740
4865
|
const { userId } = ctx.context.payload;
|
|
4866
|
+
const defaultTeamName = ctx.body.defaultTeamName;
|
|
4741
4867
|
const user = await ctx.context.adapter.findOne({
|
|
4742
4868
|
model: "user",
|
|
4743
4869
|
where: [{
|
|
@@ -4753,10 +4879,13 @@ const createOrganization = (options) => {
|
|
|
4753
4879
|
value: ctx.body.slug
|
|
4754
4880
|
}]
|
|
4755
4881
|
}) > 0) throw ctx.error("BAD_REQUEST", { message: "Organization already exists" });
|
|
4756
|
-
let orgData =
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
}
|
|
4882
|
+
let orgData = pickWritableOrganizationFields(ctx.body, orgOptions, "create");
|
|
4883
|
+
try {
|
|
4884
|
+
validateWritableCreateOrganizationFields(orgData, orgOptions);
|
|
4885
|
+
} catch (error) {
|
|
4886
|
+
if (error instanceof z$1.ZodError) throw ctx.error("BAD_REQUEST", { message: error.issues[0]?.message ?? "Invalid organization data" });
|
|
4887
|
+
throw error;
|
|
4888
|
+
}
|
|
4760
4889
|
if (orgOptions.organizationCreation?.beforeCreate) {
|
|
4761
4890
|
const response = await orgOptions.organizationCreation.beforeCreate({
|
|
4762
4891
|
organization: {
|
|
@@ -4765,20 +4894,20 @@ const createOrganization = (options) => {
|
|
|
4765
4894
|
},
|
|
4766
4895
|
user
|
|
4767
4896
|
}, ctx.request);
|
|
4768
|
-
if (response && typeof response === "object" && "data" in response) orgData = {
|
|
4897
|
+
if (response && typeof response === "object" && "data" in response) orgData = pickWritableOrganizationFields({
|
|
4769
4898
|
...orgData,
|
|
4770
4899
|
...response.data
|
|
4771
|
-
};
|
|
4900
|
+
}, orgOptions, "create");
|
|
4772
4901
|
}
|
|
4773
4902
|
if (orgOptions?.organizationHooks?.beforeCreateOrganization) {
|
|
4774
4903
|
const response = await orgOptions?.organizationHooks.beforeCreateOrganization({
|
|
4775
4904
|
organization: orgData,
|
|
4776
4905
|
user
|
|
4777
4906
|
});
|
|
4778
|
-
if (response && typeof response === "object" && "data" in response) orgData = {
|
|
4907
|
+
if (response && typeof response === "object" && "data" in response) orgData = pickWritableOrganizationFields({
|
|
4779
4908
|
...orgData,
|
|
4780
4909
|
...response.data
|
|
4781
|
-
};
|
|
4910
|
+
}, orgOptions, "create");
|
|
4782
4911
|
}
|
|
4783
4912
|
const organization = await ctx.context.adapter.create({
|
|
4784
4913
|
model: "organization",
|
|
@@ -4827,7 +4956,7 @@ const createOrganization = (options) => {
|
|
|
4827
4956
|
if (orgOptions?.teams?.enabled && orgOptions.teams.defaultTeam?.enabled !== false && !ctx.context.payload.skipDefaultTeam) {
|
|
4828
4957
|
let teamData = {
|
|
4829
4958
|
organizationId: organization.id,
|
|
4830
|
-
name:
|
|
4959
|
+
name: defaultTeamName || `${organization.name}`,
|
|
4831
4960
|
createdAt: /* @__PURE__ */ new Date()
|
|
4832
4961
|
};
|
|
4833
4962
|
if (orgOptions?.organizationHooks?.beforeCreateTeam) {
|
|
@@ -4891,12 +5020,7 @@ const updateOrganization = (options) => {
|
|
|
4891
5020
|
return createAuthEndpoint("/dash/organization/update", {
|
|
4892
5021
|
method: "POST",
|
|
4893
5022
|
use: [jwtMiddleware(options, z$1.object({ organizationId: z$1.string() }))],
|
|
4894
|
-
body:
|
|
4895
|
-
logo: z$1.url().optional(),
|
|
4896
|
-
name: z$1.string().optional(),
|
|
4897
|
-
slug: z$1.string().optional(),
|
|
4898
|
-
metadata: z$1.string().optional()
|
|
4899
|
-
})
|
|
5023
|
+
body: UpdateOrganizationBodySchema
|
|
4900
5024
|
}, async (ctx) => {
|
|
4901
5025
|
const { organizationId } = ctx.context.payload;
|
|
4902
5026
|
const orgOptions = requireOrganizationPlugin(ctx).options || {};
|
|
@@ -4931,7 +5055,13 @@ const updateOrganization = (options) => {
|
|
|
4931
5055
|
const owner = owners[0];
|
|
4932
5056
|
const updatedByUser = Array.isArray(owner.user) ? owner.user[0] : owner.user;
|
|
4933
5057
|
if (!updatedByUser) throw ctx.error("NOT_FOUND", { message: "Owner user not found" });
|
|
4934
|
-
let updateData =
|
|
5058
|
+
let updateData = pickWritableOrganizationFields(ctx.body, orgOptions, "update");
|
|
5059
|
+
try {
|
|
5060
|
+
validateWritableOrganizationUpdateFields(updateData, orgOptions);
|
|
5061
|
+
} catch (error) {
|
|
5062
|
+
if (error instanceof z$1.ZodError) throw ctx.error("BAD_REQUEST", { message: error.issues[0]?.message ?? "Invalid organization data" });
|
|
5063
|
+
throw error;
|
|
5064
|
+
}
|
|
4935
5065
|
if (typeof updateData.metadata === "string") try {
|
|
4936
5066
|
updateData.metadata = updateData.metadata === "" ? void 0 : JSON.parse(updateData.metadata);
|
|
4937
5067
|
} catch (parseError) {
|
|
@@ -4944,10 +5074,10 @@ const updateOrganization = (options) => {
|
|
|
4944
5074
|
user: updatedByUser,
|
|
4945
5075
|
member: owner
|
|
4946
5076
|
});
|
|
4947
|
-
if (response && typeof response === "object" && "data" in response) updateData = {
|
|
5077
|
+
if (response && typeof response === "object" && "data" in response) updateData = pickWritableOrganizationFields({
|
|
4948
5078
|
...updateData,
|
|
4949
5079
|
...response.data
|
|
4950
|
-
};
|
|
5080
|
+
}, orgOptions, "update");
|
|
4951
5081
|
}
|
|
4952
5082
|
const organization = await ctx.context.adapter.update({
|
|
4953
5083
|
model: "organization",
|
|
@@ -5285,7 +5415,7 @@ const listTeamMembers = (options) => {
|
|
|
5285
5415
|
user: user ? {
|
|
5286
5416
|
id: user.id,
|
|
5287
5417
|
name: user.name,
|
|
5288
|
-
email: user.email,
|
|
5418
|
+
email: user.email ?? void 0,
|
|
5289
5419
|
image: user.image ?? null
|
|
5290
5420
|
} : null
|
|
5291
5421
|
};
|
|
@@ -7365,6 +7495,7 @@ const sendVerificationEmail = (options) => createAuthEndpoint("/dash/send-verifi
|
|
|
7365
7495
|
const user = await ctx.context.internalAdapter.findUserById(userId);
|
|
7366
7496
|
if (!user) throw ctx.error("NOT_FOUND", { message: "User not found" });
|
|
7367
7497
|
if (user.emailVerified) throw ctx.error("BAD_REQUEST", { message: "Email is already verified" });
|
|
7498
|
+
if (!user.email) throw ctx.error("BAD_REQUEST", { message: "User has no associated email address" });
|
|
7368
7499
|
if (!ctx.context.options.emailVerification?.sendVerificationEmail) throw ctx.error("BAD_REQUEST", { message: "Email verification is not enabled" });
|
|
7369
7500
|
await sendVerificationEmailFn({
|
|
7370
7501
|
...ctx,
|
|
@@ -7401,6 +7532,10 @@ const sendManyVerificationEmails = (options) => {
|
|
|
7401
7532
|
});
|
|
7402
7533
|
if (chunk.length - users.length > 0) for (const id of chunk.filter((id) => !users.some((u) => u.id === id))) skippedEmailUserIds.add(id);
|
|
7403
7534
|
for (const result of await Promise.allSettled(users.map(async (user) => {
|
|
7535
|
+
if (!user.email) return {
|
|
7536
|
+
success: false,
|
|
7537
|
+
id: user.id
|
|
7538
|
+
};
|
|
7404
7539
|
try {
|
|
7405
7540
|
await sendVerificationEmailFn({
|
|
7406
7541
|
...ctx,
|
|
@@ -7440,6 +7575,7 @@ const sendResetPasswordEmail = (options) => createAuthEndpoint("/dash/send-reset
|
|
|
7440
7575
|
const { userId } = ctx.context.payload;
|
|
7441
7576
|
const user = await ctx.context.internalAdapter.findUserById(userId);
|
|
7442
7577
|
if (!user) throw ctx.error("NOT_FOUND", { message: "User not found" });
|
|
7578
|
+
if (!user.email) throw ctx.error("BAD_REQUEST", { message: "User has no associated email address" });
|
|
7443
7579
|
ctx.body.redirectTo = ctx.body.callbackUrl;
|
|
7444
7580
|
ctx.body.email = user.email;
|
|
7445
7581
|
return await requestPasswordReset(ctx);
|
package/dist/native.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as DashGetAuditLogsInput, i as DashGetAllAuditLogsInput, n as DashAuditLogsResponse, o as dashClient, r as DashClientOptions, t as DashAuditLog } from "./dash-client-
|
|
1
|
+
import { a as DashGetAuditLogsInput, i as DashGetAllAuditLogsInput, n as DashAuditLogsResponse, o as dashClient, r as DashClientOptions, t as DashAuditLog } from "./dash-client-CZzJyRrV.mjs";
|
|
2
2
|
import { BetterAuthClientPlugin } from "better-auth";
|
|
3
3
|
|
|
4
4
|
//#region src/sentinel/native/client.d.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@better-auth/infra",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.9",
|
|
4
4
|
"description": "Dashboard and analytics plugin for Better Auth",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.mjs",
|
|
@@ -64,21 +64,21 @@
|
|
|
64
64
|
"@better-auth/scim": "catalog:",
|
|
65
65
|
"@better-auth/sso": "catalog:",
|
|
66
66
|
"@infra/mocks": "workspace:*",
|
|
67
|
-
"@types/bun": "
|
|
67
|
+
"@types/bun": "catalog:",
|
|
68
68
|
"@types/node": "catalog:",
|
|
69
69
|
"better-auth": "catalog:",
|
|
70
70
|
"expo-crypto": "^14.0.2",
|
|
71
71
|
"happy-dom": "^20.9.0",
|
|
72
|
-
"msw": "
|
|
73
|
-
"tsdown": "^0.
|
|
72
|
+
"msw": "catalog:",
|
|
73
|
+
"tsdown": "^0.22.0",
|
|
74
74
|
"typescript": "catalog:",
|
|
75
75
|
"zod": "catalog:"
|
|
76
76
|
},
|
|
77
77
|
"dependencies": {
|
|
78
|
-
"@better-fetch/fetch": "
|
|
79
|
-
"better-call": "
|
|
78
|
+
"@better-fetch/fetch": "catalog:",
|
|
79
|
+
"better-call": "catalog:",
|
|
80
80
|
"jose": "^6.1.0",
|
|
81
|
-
"libphonenumber-js": "^1.
|
|
81
|
+
"libphonenumber-js": "^1.13.1"
|
|
82
82
|
},
|
|
83
83
|
"peerDependencies": {
|
|
84
84
|
"better-auth": ">=1.4.0",
|