better-auth 1.7.17 → 1.7.19
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/proxy.mjs +4 -11
- package/dist/db/internal-adapter.mjs +52 -40
- package/dist/db/schema.mjs +17 -8
- package/dist/package.mjs +1 -1
- package/dist/plugins/admin/routes.mjs +13 -11
- package/dist/plugins/device-authorization/index.d.mts +1 -0
- package/dist/plugins/device-authorization/routes.mjs +3 -1
- package/dist/plugins/open-api/generator.mjs +2 -1
- package/dist/plugins/organization/adapter.mjs +4 -0
- package/dist/plugins/organization/routes/crud-invites.mjs +1 -1
- package/dist/plugins/siwe/index.mjs +40 -23
- package/dist/plugins/username/index.mjs +3 -2
- package/dist/state.mjs +1 -2
- package/dist/test-utils/test-instance.mjs +43 -33
- package/package.json +8 -8
package/dist/client/proxy.mjs
CHANGED
|
@@ -1,14 +1,7 @@
|
|
|
1
1
|
import { isAtom } from "../utils/is-atom.mjs";
|
|
2
2
|
import { toKebabCase } from "@better-auth/core/utils/string";
|
|
3
|
+
import { isPayloadMethod } from "@better-fetch/fetch";
|
|
3
4
|
//#region src/client/proxy.ts
|
|
4
|
-
function getMethod(path, knownPathMethods, args) {
|
|
5
|
-
const method = knownPathMethods[path];
|
|
6
|
-
const { fetchOptions, query: _query, ...body } = args || {};
|
|
7
|
-
if (method) return method;
|
|
8
|
-
if (fetchOptions?.method) return fetchOptions.method;
|
|
9
|
-
if (body && Object.keys(body).length > 0) return "POST";
|
|
10
|
-
return "GET";
|
|
11
|
-
}
|
|
12
5
|
function createDynamicPathProxy(routes, client, knownPathMethods, atoms, atomListeners) {
|
|
13
6
|
function createProxy(path = []) {
|
|
14
7
|
return new Proxy(function() {}, {
|
|
@@ -35,13 +28,13 @@ function createDynamicPathProxy(routes, client, knownPathMethods, atoms, atomLis
|
|
|
35
28
|
...fetchOptions,
|
|
36
29
|
...argFetchOptions
|
|
37
30
|
};
|
|
38
|
-
const method =
|
|
31
|
+
const method = knownPathMethods[routePath] || options.method || (Object.keys(body).length > 0 ? "POST" : "GET");
|
|
39
32
|
return await client(routePath, {
|
|
40
33
|
...options,
|
|
41
|
-
body: method
|
|
34
|
+
body: isPayloadMethod(method) ? {
|
|
42
35
|
...body,
|
|
43
36
|
...options?.body || {}
|
|
44
|
-
},
|
|
37
|
+
} : void 0,
|
|
45
38
|
query: query || options?.query,
|
|
46
39
|
method,
|
|
47
40
|
async onSuccess(context) {
|
|
@@ -38,7 +38,7 @@ const createInternalAdapter = (adapter, ctx) => {
|
|
|
38
38
|
* or `deleteUserSessions` sweeping a user's accumulated preserved rows) would
|
|
39
39
|
* re-match it and re-fire the hooks, re-dispatching back-channel logout.
|
|
40
40
|
*/
|
|
41
|
-
const endPreservedSessions = (where) => {
|
|
41
|
+
const endPreservedSessions = (where, afterEnd) => {
|
|
42
42
|
const liveSessions = [...where, {
|
|
43
43
|
field: "expiresAt",
|
|
44
44
|
value: /* @__PURE__ */ new Date(),
|
|
@@ -51,6 +51,7 @@ const createInternalAdapter = (adapter, ctx) => {
|
|
|
51
51
|
where: liveSessions,
|
|
52
52
|
update: { expiresAt: /* @__PURE__ */ new Date() }
|
|
53
53
|
});
|
|
54
|
+
await afterEnd?.();
|
|
54
55
|
},
|
|
55
56
|
executeMainFn: false
|
|
56
57
|
});
|
|
@@ -78,6 +79,16 @@ const createInternalAdapter = (adapter, ctx) => {
|
|
|
78
79
|
const activeSessions = await secondaryStorage.get(`active-sessions-${userId}`);
|
|
79
80
|
return activeSessions ? safeJSONParse(activeSessions) || [] : [];
|
|
80
81
|
}
|
|
82
|
+
async function setActiveSessionReferences(userId, sessionReferences, now) {
|
|
83
|
+
if (!secondaryStorage) return;
|
|
84
|
+
const sorted = [...sessionReferences].sort((a, b) => a.expiresAt - b.expiresAt);
|
|
85
|
+
const ttl = getTTLSeconds(sorted.at(-1)?.expiresAt ?? 0, now);
|
|
86
|
+
if (ttl > 0) {
|
|
87
|
+
await secondaryStorage.set(`active-sessions-${userId}`, JSON.stringify(sorted), ttl);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
await secondaryStorage.delete(`active-sessions-${userId}`);
|
|
91
|
+
}
|
|
81
92
|
const revokedBeforeKey = (userId) => `revoked-sessions-before-${userId}`;
|
|
82
93
|
const revokedBeforeTTL = Math.max(sessionExpiration, 86400, Number(options.plugins?.find((plugin) => plugin.id === "admin")?.options?.impersonationSessionDuration) || 0);
|
|
83
94
|
async function isRevokedSession(session, revokedBeforeByUser) {
|
|
@@ -91,22 +102,15 @@ const createInternalAdapter = (adapter, ctx) => {
|
|
|
91
102
|
const previousRevokedBefore = Number(await secondaryStorage.get(revokedBeforeKey(userId)));
|
|
92
103
|
await secondaryStorage.set(revokedBeforeKey(userId), String(Math.max(revokedAt, previousRevokedBefore || 0)), revokedBeforeTTL);
|
|
93
104
|
const deletedTokens = new Set(sessionReferences.map((session) => session.token));
|
|
94
|
-
for (const
|
|
95
|
-
const activeSessionsKey = `active-sessions-${userId}`;
|
|
105
|
+
for (const token of deletedTokens) await secondaryStorage.delete(token);
|
|
96
106
|
const currentSessionReferences = await getActiveSessionReferences(userId);
|
|
97
107
|
const now = Date.now();
|
|
98
|
-
|
|
99
|
-
remainingSessionReferences.sort((a, b) => a.expiresAt - b.expiresAt);
|
|
100
|
-
const furthestExpiration = remainingSessionReferences.at(-1)?.expiresAt;
|
|
101
|
-
if (furthestExpiration) {
|
|
102
|
-
await secondaryStorage.set(activeSessionsKey, JSON.stringify(remainingSessionReferences), getTTLSeconds(furthestExpiration, now));
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
await secondaryStorage.delete(activeSessionsKey);
|
|
108
|
+
await setActiveSessionReferences(userId, currentSessionReferences.filter((session) => session.expiresAt > now && !deletedTokens.has(session.token)), now);
|
|
106
109
|
}
|
|
107
|
-
async function queueCachedUserSessionDeletion(userId, sessionReferences
|
|
110
|
+
async function queueCachedUserSessionDeletion(userId, sessionReferences) {
|
|
108
111
|
if (!secondaryStorage) return;
|
|
109
112
|
const references = sessionReferences ?? await getActiveSessionReferences(userId);
|
|
113
|
+
const revokedAt = Date.now();
|
|
110
114
|
await queueAfterTransactionHook(() => deleteCachedUserSessions(userId, references, revokedAt), { onError(error) {
|
|
111
115
|
logger.error("Failed to delete committed user sessions from secondary storage", error);
|
|
112
116
|
} });
|
|
@@ -308,12 +312,10 @@ const createInternalAdapter = (adapter, ctx) => {
|
|
|
308
312
|
list = safeJSONParse(currentList) || [];
|
|
309
313
|
list = list.filter((session) => session.expiresAt > now && session.token !== data.token);
|
|
310
314
|
}
|
|
311
|
-
|
|
315
|
+
await setActiveSessionReferences(userId, [...list, {
|
|
312
316
|
token: data.token,
|
|
313
317
|
expiresAt: data.expiresAt.getTime()
|
|
314
|
-
}]
|
|
315
|
-
const furthestSessionTTL = getTTLSeconds(sorted.at(-1)?.expiresAt ?? data.expiresAt.getTime(), now);
|
|
316
|
-
if (furthestSessionTTL > 0) await secondaryStorage.set(`active-sessions-${userId}`, JSON.stringify(sorted), furthestSessionTTL);
|
|
318
|
+
}], now);
|
|
317
319
|
const user = await (await getCurrentAdapter(adapter)).findOne({
|
|
318
320
|
model: "user",
|
|
319
321
|
where: [{
|
|
@@ -465,13 +467,11 @@ const createInternalAdapter = (adapter, ctx) => {
|
|
|
465
467
|
}), sessionTTL);
|
|
466
468
|
const listKey = `active-sessions-${updatedSession.userId}`;
|
|
467
469
|
const listRaw = await secondaryStorage.get(listKey);
|
|
468
|
-
const
|
|
470
|
+
const filtered = (listRaw ? safeJSONParse(listRaw) || [] : []).filter((s) => s.token !== sessionToken && s.expiresAt > now).concat([{
|
|
469
471
|
token: sessionToken,
|
|
470
472
|
expiresAt: expiresMs
|
|
471
|
-
}])
|
|
472
|
-
|
|
473
|
-
if (furthestSessionExp && furthestSessionExp > now) await secondaryStorage.set(listKey, JSON.stringify(sorted), getTTLSeconds(furthestSessionExp, now));
|
|
474
|
-
else await secondaryStorage.delete(listKey);
|
|
473
|
+
}]);
|
|
474
|
+
await setActiveSessionReferences(updatedSession.userId, filtered, now);
|
|
475
475
|
}
|
|
476
476
|
return updatedSession;
|
|
477
477
|
},
|
|
@@ -492,10 +492,7 @@ const createInternalAdapter = (adapter, ctx) => {
|
|
|
492
492
|
if (currentList) {
|
|
493
493
|
const list = safeJSONParse(currentList) || [];
|
|
494
494
|
const now = Date.now();
|
|
495
|
-
|
|
496
|
-
const furthestSessionExp = filtered.sort((a, b) => a.expiresAt - b.expiresAt).at(-1)?.expiresAt;
|
|
497
|
-
if (filtered.length > 0 && furthestSessionExp && furthestSessionExp > Date.now()) await secondaryStorage.set(`active-sessions-${userId}`, JSON.stringify(filtered), getTTLSeconds(furthestSessionExp, now));
|
|
498
|
-
else await secondaryStorage.delete(`active-sessions-${userId}`);
|
|
495
|
+
await setActiveSessionReferences(userId, list.filter((session) => session.expiresAt > now && session.token !== token), now);
|
|
499
496
|
} else logger.error("Active sessions list not found in secondary storage");
|
|
500
497
|
}
|
|
501
498
|
await secondaryStorage.delete(token);
|
|
@@ -531,25 +528,40 @@ const createInternalAdapter = (adapter, ctx) => {
|
|
|
531
528
|
}], "account", void 0);
|
|
532
529
|
},
|
|
533
530
|
deleteUserSessions: async (userId) => {
|
|
534
|
-
const revokedAt = Date.now();
|
|
535
531
|
const sessionReferences = await getActiveSessionReferences(userId);
|
|
536
|
-
if (secondaryStorage) {
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
532
|
+
if (!secondaryStorage) {
|
|
533
|
+
await deleteManyWithHooks([{
|
|
534
|
+
field: "userId",
|
|
535
|
+
value: userId
|
|
536
|
+
}], "session", void 0);
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
if (!options.session?.storeSessionInDatabase) {
|
|
540
|
+
await queueCachedUserSessionDeletion(userId, sessionReferences);
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
const revokeCachedSessions = async () => queueCachedUserSessionDeletion(userId, [...sessionReferences, ...await getActiveSessionReferences(userId)]);
|
|
544
|
+
if (ctx.options.session?.preserveSessionInDatabase) {
|
|
545
|
+
await endPreservedSessions([{
|
|
546
|
+
field: "userId",
|
|
547
|
+
value: userId
|
|
548
|
+
}], revokeCachedSessions);
|
|
549
|
+
return;
|
|
548
550
|
}
|
|
549
|
-
|
|
551
|
+
await deleteManyWithHooks([{
|
|
550
552
|
field: "userId",
|
|
551
553
|
value: userId
|
|
552
|
-
}], "session",
|
|
554
|
+
}], "session", {
|
|
555
|
+
fn: async (where) => {
|
|
556
|
+
const deleted = await (await getCurrentAdapter(adapter)).deleteMany({
|
|
557
|
+
model: "session",
|
|
558
|
+
where
|
|
559
|
+
});
|
|
560
|
+
await revokeCachedSessions();
|
|
561
|
+
return deleted;
|
|
562
|
+
},
|
|
563
|
+
executeMainFn: false
|
|
564
|
+
});
|
|
553
565
|
},
|
|
554
566
|
deleteSessions: async (sessionTokens) => {
|
|
555
567
|
if (secondaryStorage) {
|
package/dist/db/schema.mjs
CHANGED
|
@@ -149,16 +149,25 @@ function getSessionDefaultFields(options) {
|
|
|
149
149
|
}
|
|
150
150
|
function mergeSchema(schema, newSchema) {
|
|
151
151
|
if (!newSchema) return schema;
|
|
152
|
+
const merged = { ...schema };
|
|
152
153
|
for (const table in newSchema) {
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
154
|
+
const base = schema[table];
|
|
155
|
+
const override = newSchema[table];
|
|
156
|
+
if (!base || !override) continue;
|
|
157
|
+
const fields = Object.fromEntries(Object.entries(base.fields).map(([key, field]) => {
|
|
158
|
+
const fieldName = override.fields?.[key];
|
|
159
|
+
return [key, fieldName ? {
|
|
160
|
+
...field,
|
|
161
|
+
fieldName
|
|
162
|
+
} : field];
|
|
163
|
+
}));
|
|
164
|
+
merged[table] = {
|
|
165
|
+
...base,
|
|
166
|
+
...override.modelName ? { modelName: override.modelName } : {},
|
|
167
|
+
fields
|
|
168
|
+
};
|
|
160
169
|
}
|
|
161
|
-
return
|
|
170
|
+
return merged;
|
|
162
171
|
}
|
|
163
172
|
//#endregion
|
|
164
173
|
export { buildSyntheticUserOutput, getSessionDefaultFields, mergeSchema, parseAccountInput, parseAccountOutput, parseAdditionalUserInput, parseAdditionalUserInputFromProviderProfile, parseInputData, parseSessionInput, parseSessionOutput, parseUserInput, parseUserOutput };
|
package/dist/package.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import { getAuthoritativeSessionFromCtx, getSessionFromCtx } from "../../api/rou
|
|
|
5
5
|
import { assertPasswordNotTooLong, assertPasswordNotTooShort } from "../../utils/password.mjs";
|
|
6
6
|
import { ADMIN_ERROR_CODES } from "./error-codes.mjs";
|
|
7
7
|
import { hasPermission } from "./has-permission.mjs";
|
|
8
|
+
import { runWithTransaction } from "@better-auth/core/context";
|
|
8
9
|
import { APIError, BASE_ERROR_CODES } from "@better-auth/core/error";
|
|
9
10
|
import { whereOperators } from "@better-auth/core/db/adapter";
|
|
10
11
|
import { createAuthEndpoint, createAuthMiddleware } from "@better-auth/core/api";
|
|
@@ -193,22 +194,23 @@ const createUser = (opts) => createAuthEndpoint("/admin/create-user", {
|
|
|
193
194
|
if (!z.email().safeParse(email).success) throw APIError.from("BAD_REQUEST", BASE_ERROR_CODES.INVALID_EMAIL);
|
|
194
195
|
if (ctx.body.password) assertPasswordNotTooLong(ctx, ctx.body.password);
|
|
195
196
|
if (await ctx.context.internalAdapter.findUserByEmail(email)) throw APIError.from("BAD_REQUEST", ADMIN_ERROR_CODES.USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL);
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
await ctx.context.internalAdapter.linkAccount({
|
|
197
|
+
const hashedPassword = ctx.body.password ? await ctx.context.password.hash(ctx.body.password) : void 0;
|
|
198
|
+
const user = await runWithTransaction(ctx.context.adapter, async () => {
|
|
199
|
+
const user = await ctx.context.internalAdapter.createUser({
|
|
200
|
+
...userData,
|
|
201
|
+
email,
|
|
202
|
+
name: ctx.body.name,
|
|
203
|
+
role: requestedRole !== void 0 ? parseRoles(requestedRole) : opts?.defaultRole ?? "user"
|
|
204
|
+
}, { method: "admin" });
|
|
205
|
+
if (!user) throw APIError.from("INTERNAL_SERVER_ERROR", ADMIN_ERROR_CODES.FAILED_TO_CREATE_USER);
|
|
206
|
+
if (hashedPassword !== void 0) await ctx.context.internalAdapter.linkAccount({
|
|
206
207
|
providerId: "credential",
|
|
207
208
|
accountId: user.id,
|
|
208
209
|
password: hashedPassword,
|
|
209
210
|
userId: user.id
|
|
210
211
|
});
|
|
211
|
-
|
|
212
|
+
return user;
|
|
213
|
+
});
|
|
212
214
|
return ctx.json({ user: parseUserOutput(ctx.context.options, user) });
|
|
213
215
|
});
|
|
214
216
|
const adminUpdateUserBodySchema = z.object({
|
|
@@ -302,6 +302,7 @@ export declare const deviceAuthorization: <Grant extends DeviceAuthorizationGran
|
|
|
302
302
|
error: z.ZodObject<{
|
|
303
303
|
error: z.ZodEnum<{
|
|
304
304
|
invalid_request: "invalid_request";
|
|
305
|
+
invalid_client: "invalid_client";
|
|
305
306
|
authorization_pending: "authorization_pending";
|
|
306
307
|
slow_down: "slow_down";
|
|
307
308
|
expired_token: "expired_token";
|
|
@@ -278,6 +278,7 @@ const deviceTokenErrorSchema = z.object({
|
|
|
278
278
|
"expired_token",
|
|
279
279
|
"access_denied",
|
|
280
280
|
"invalid_request",
|
|
281
|
+
"invalid_client",
|
|
281
282
|
"invalid_grant"
|
|
282
283
|
]).meta({ description: "Error code" }),
|
|
283
284
|
error_description: z.string().meta({ description: "Detailed error description" })
|
|
@@ -413,6 +414,7 @@ Follow [rfc8628#section-3.4](https://datatracker.ietf.org/doc/html/rfc8628#secti
|
|
|
413
414
|
"expired_token",
|
|
414
415
|
"access_denied",
|
|
415
416
|
"invalid_request",
|
|
417
|
+
"invalid_client",
|
|
416
418
|
"invalid_grant"
|
|
417
419
|
]
|
|
418
420
|
},
|
|
@@ -427,7 +429,7 @@ Follow [rfc8628#section-3.4](https://datatracker.ietf.org/doc/html/rfc8628#secti
|
|
|
427
429
|
const { device_code, client_id } = ctx.body;
|
|
428
430
|
if (opts.validateClient) {
|
|
429
431
|
if (!await opts.validateClient(client_id)) throw new APIError("BAD_REQUEST", {
|
|
430
|
-
error: "
|
|
432
|
+
error: "invalid_client",
|
|
431
433
|
error_description: "Invalid client ID"
|
|
432
434
|
});
|
|
433
435
|
}
|
|
@@ -74,7 +74,8 @@ function getZodPipeSchema(zodType) {
|
|
|
74
74
|
function getParameters(options) {
|
|
75
75
|
const parameters = [];
|
|
76
76
|
if (options.metadata?.openapi?.parameters) parameters.push(...options.metadata.openapi.parameters);
|
|
77
|
-
|
|
77
|
+
const query = options.query instanceof z.core.$ZodOptional ? options.query._zod.def.innerType : options.query;
|
|
78
|
+
if (!options.metadata?.openapi?.parameters && query instanceof z.core.$ZodObject) Object.entries(query._zod.def.shape).forEach(([key, value]) => {
|
|
78
79
|
if (value instanceof z.ZodType) {
|
|
79
80
|
const parameterSchema = toOpenApiSchema(value);
|
|
80
81
|
parameters.push({
|
|
@@ -214,6 +214,10 @@ const getOrgAdapter = (context, options) => {
|
|
|
214
214
|
...data.filter.operator ? { operator: data.filter.operator } : {}
|
|
215
215
|
}] : []]
|
|
216
216
|
})]);
|
|
217
|
+
if (members[0].length === 0) return {
|
|
218
|
+
members: [],
|
|
219
|
+
total: members[1]
|
|
220
|
+
};
|
|
217
221
|
const users = await adapter.findMany({
|
|
218
222
|
model: "user",
|
|
219
223
|
where: [{
|
|
@@ -505,7 +505,7 @@ const getInvitation = (options) => createAuthEndpoint("/organization/get-invitat
|
|
|
505
505
|
if (!session) throw APIError.fromStatus("UNAUTHORIZED", { message: "Not authenticated" });
|
|
506
506
|
const adapter = getOrgAdapter(ctx.context, options);
|
|
507
507
|
const invitation = await adapter.findInvitationById(ctx.query.id);
|
|
508
|
-
if (!invitation || invitation.status !== "pending" || invitation.expiresAt < /* @__PURE__ */ new Date()) throw APIError.
|
|
508
|
+
if (!invitation || invitation.status !== "pending" || invitation.expiresAt < /* @__PURE__ */ new Date()) throw APIError.from("BAD_REQUEST", ORGANIZATION_ERROR_CODES.INVITATION_NOT_FOUND);
|
|
509
509
|
if (invitation.email.toLowerCase() !== session.user.email.toLowerCase()) throw APIError.from("FORBIDDEN", ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_THE_RECIPIENT_OF_THE_INVITATION);
|
|
510
510
|
if (shouldRequireVerifiedEmailForInvitationIdAction({
|
|
511
511
|
organizationOptions: ctx.context.orgOptions,
|
|
@@ -6,6 +6,7 @@ import { PACKAGE_VERSION } from "../../version.mjs";
|
|
|
6
6
|
import { toChecksumAddress } from "../../utils/hashing.mjs";
|
|
7
7
|
import { normalizeSiweDomain, parseSiweMessage } from "./parse-message.mjs";
|
|
8
8
|
import { schema } from "./schema.mjs";
|
|
9
|
+
import { getCurrentAdapter, runWithTransaction } from "@better-auth/core/context";
|
|
9
10
|
import { createAuthEndpoint } from "@better-auth/core/api";
|
|
10
11
|
import * as z from "zod";
|
|
11
12
|
import { createPlaceholderEmail } from "@better-auth/core/utils/email";
|
|
@@ -186,38 +187,54 @@ const siwe = (options) => {
|
|
|
186
187
|
}
|
|
187
188
|
}
|
|
188
189
|
const { name, avatar } = await options.ensLookup?.({ walletAddress }) ?? {};
|
|
189
|
-
const createSIWEUser = (email) => ctx.context.
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
190
|
+
const createSIWEUser = (email) => runWithTransaction(ctx.context.adapter, async () => {
|
|
191
|
+
const createdUser = await ctx.context.internalAdapter.createUser({
|
|
192
|
+
name: name ?? walletAddress,
|
|
193
|
+
email,
|
|
194
|
+
image: avatar ?? ""
|
|
195
|
+
}, { method: "siwe" });
|
|
196
|
+
await (await getCurrentAdapter(ctx.context.adapter)).create({
|
|
197
|
+
model: "walletAddress",
|
|
198
|
+
data: {
|
|
199
|
+
userId: createdUser.id,
|
|
200
|
+
address: walletAddress,
|
|
201
|
+
chainId,
|
|
202
|
+
isPrimary: true,
|
|
203
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
await ctx.context.internalAdapter.createAccount({
|
|
207
|
+
userId: createdUser.id,
|
|
208
|
+
providerId: "siwe",
|
|
209
|
+
accountId: `${walletAddress}:${chainId}`,
|
|
210
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
211
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
212
|
+
});
|
|
213
|
+
return createdUser;
|
|
214
|
+
});
|
|
194
215
|
try {
|
|
195
216
|
user = await createSIWEUser(userEmail);
|
|
196
217
|
} catch (error) {
|
|
197
218
|
if (userEmail !== normalizedEmail || !normalizedEmail) throw error;
|
|
198
|
-
|
|
219
|
+
const claimedUser = await ctx.context.internalAdapter.findUserByEmail(normalizedEmail);
|
|
220
|
+
const registeredWallet = await ctx.context.adapter.findOne({
|
|
221
|
+
model: "walletAddress",
|
|
222
|
+
where: [{
|
|
223
|
+
field: "address",
|
|
224
|
+
operator: "eq",
|
|
225
|
+
value: walletAddress
|
|
226
|
+
}, {
|
|
227
|
+
field: "chainId",
|
|
228
|
+
operator: "eq",
|
|
229
|
+
value: chainId
|
|
230
|
+
}]
|
|
231
|
+
});
|
|
232
|
+
if (!claimedUser || registeredWallet) throw error;
|
|
199
233
|
userEmail = walletEmail;
|
|
200
234
|
user = await createSIWEUser(userEmail);
|
|
201
235
|
} finally {
|
|
202
236
|
if (emailClaimIdentifier) await ctx.context.internalAdapter.consumeVerificationValue(emailClaimIdentifier).catch(() => {});
|
|
203
237
|
}
|
|
204
|
-
await ctx.context.adapter.create({
|
|
205
|
-
model: "walletAddress",
|
|
206
|
-
data: {
|
|
207
|
-
userId: user.id,
|
|
208
|
-
address: walletAddress,
|
|
209
|
-
chainId,
|
|
210
|
-
isPrimary: true,
|
|
211
|
-
createdAt: /* @__PURE__ */ new Date()
|
|
212
|
-
}
|
|
213
|
-
});
|
|
214
|
-
await ctx.context.internalAdapter.createAccount({
|
|
215
|
-
userId: user.id,
|
|
216
|
-
providerId: "siwe",
|
|
217
|
-
accountId: `${walletAddress}:${chainId}`,
|
|
218
|
-
createdAt: /* @__PURE__ */ new Date(),
|
|
219
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
220
|
-
});
|
|
221
238
|
} else if (!existingWalletAddress) {
|
|
222
239
|
await ctx.context.adapter.create({
|
|
223
240
|
model: "walletAddress",
|
|
@@ -6,6 +6,7 @@ import { assertPasswordNotTooLong } from "../../utils/password.mjs";
|
|
|
6
6
|
import { PACKAGE_VERSION } from "../../version.mjs";
|
|
7
7
|
import { USERNAME_ERROR_CODES } from "./error-codes.mjs";
|
|
8
8
|
import { getSchema } from "./schema.mjs";
|
|
9
|
+
import { getCurrentAdapter } from "@better-auth/core/context";
|
|
9
10
|
import { APIError, BASE_ERROR_CODES } from "@better-auth/core/error";
|
|
10
11
|
import { createAuthEndpoint, createAuthMiddleware } from "@better-auth/core/api";
|
|
11
12
|
import * as z from "zod";
|
|
@@ -71,7 +72,7 @@ const usernameImpl = (options, includeDisplayUsername) => {
|
|
|
71
72
|
const currentPath = context?.path;
|
|
72
73
|
const skipValidation = currentPath && pathsWithHttpHookValidation.includes(currentPath);
|
|
73
74
|
if (username) {
|
|
74
|
-
if (!skipValidation) await validateUsername(username, displayUsername, ctx.adapter);
|
|
75
|
+
if (!skipValidation) await validateUsername(username, displayUsername, await getCurrentAdapter(ctx.adapter));
|
|
75
76
|
return { data: {
|
|
76
77
|
...user,
|
|
77
78
|
username: normalizer(username),
|
|
@@ -91,7 +92,7 @@ const usernameImpl = (options, includeDisplayUsername) => {
|
|
|
91
92
|
if (username) {
|
|
92
93
|
if (!skipValidation) {
|
|
93
94
|
const currentUserId = context?.context?.session?.user?.id || ("id" in user ? user.id : null);
|
|
94
|
-
await validateUsername(username, displayUsername, ctx.adapter, currentUserId);
|
|
95
|
+
await validateUsername(username, displayUsername, await getCurrentAdapter(ctx.adapter), currentUserId);
|
|
95
96
|
}
|
|
96
97
|
return { data: {
|
|
97
98
|
...user,
|
package/dist/state.mjs
CHANGED
|
@@ -77,8 +77,7 @@ async function generateGenericState(c, stateData, settings) {
|
|
|
77
77
|
}
|
|
78
78
|
const stateCookie = c.context.createAuthCookie(settings?.cookieName ?? "state", { maxAge: 300 });
|
|
79
79
|
await c.setSignedCookie(stateCookie.name, state, c.context.secret, stateCookie.attributes);
|
|
80
|
-
const expiresAt =
|
|
81
|
-
expiresAt.setMinutes(expiresAt.getMinutes() + 10);
|
|
80
|
+
const expiresAt = new Date(Date.now() + 6e5);
|
|
82
81
|
if (!await c.context.internalAdapter.createVerificationValue({
|
|
83
82
|
value: JSON.stringify({
|
|
84
83
|
...stateData,
|
|
@@ -32,6 +32,8 @@ const testPassword = {
|
|
|
32
32
|
async function getTestInstance(options, config) {
|
|
33
33
|
const testWith = config?.testWith || "sqlite";
|
|
34
34
|
const postgresSchema = testWith === "postgres" ? `ba_test_${randomUUID().replaceAll("-", "_")}` : void 0;
|
|
35
|
+
const mysqlServerURL = "mysql://root:root_password@127.0.0.1:3306";
|
|
36
|
+
const mysqlDatabase = testWith === "mysql" ? `ba_test_${randomUUID().replaceAll("-", "_")}` : void 0;
|
|
35
37
|
const quotePostgresIdentifier = (identifier) => `"${identifier.replaceAll("\"", "\"\"")}"`;
|
|
36
38
|
async function getPostgres() {
|
|
37
39
|
const { Kysely, PostgresDialect } = await import("kysely");
|
|
@@ -49,8 +51,14 @@ async function getTestInstance(options, config) {
|
|
|
49
51
|
}
|
|
50
52
|
async function getMysql() {
|
|
51
53
|
const { Kysely, MysqlDialect } = await import("kysely");
|
|
52
|
-
const { createPool } = await import("mysql2
|
|
53
|
-
|
|
54
|
+
const { createConnection, createPool } = await import("mysql2");
|
|
55
|
+
const admin = createConnection(mysqlServerURL).promise();
|
|
56
|
+
try {
|
|
57
|
+
await admin.query(`CREATE DATABASE \`${mysqlDatabase}\``);
|
|
58
|
+
} finally {
|
|
59
|
+
await admin.end();
|
|
60
|
+
}
|
|
61
|
+
return new Kysely({ dialect: new MysqlDialect({ pool: createPool(`${mysqlServerURL}/${mysqlDatabase}`) }) });
|
|
54
62
|
}
|
|
55
63
|
async function mongodbClient() {
|
|
56
64
|
const { MongoClient } = await import("mongodb");
|
|
@@ -64,6 +72,9 @@ async function getTestInstance(options, config) {
|
|
|
64
72
|
};
|
|
65
73
|
return await dbClient("mongodb://127.0.0.1:27017", "better-auth");
|
|
66
74
|
}
|
|
75
|
+
const postgres = testWith === "postgres" ? await getPostgres() : void 0;
|
|
76
|
+
const mysql = testWith === "mysql" ? await getMysql() : void 0;
|
|
77
|
+
const sqlite = testWith === "sqlite" ? await getSqlite() : void 0;
|
|
67
78
|
const opts = {
|
|
68
79
|
socialProviders: {
|
|
69
80
|
github: {
|
|
@@ -76,20 +87,45 @@ async function getTestInstance(options, config) {
|
|
|
76
87
|
}
|
|
77
88
|
},
|
|
78
89
|
secret: "better-auth-secret-that-is-long-enough-for-validation-test",
|
|
79
|
-
database:
|
|
80
|
-
db:
|
|
90
|
+
database: postgres ? {
|
|
91
|
+
db: postgres,
|
|
81
92
|
type: "postgres",
|
|
82
93
|
transaction: config?.transaction
|
|
83
|
-
} : testWith === "mongodb" ? await Promise.all([mongodbClient(), await import("../adapters/mongodb-adapter/index.mjs")]).then(([db, { mongodbAdapter }]) => mongodbAdapter(db)) :
|
|
84
|
-
db:
|
|
94
|
+
} : testWith === "mongodb" ? await Promise.all([mongodbClient(), await import("../adapters/mongodb-adapter/index.mjs")]).then(([db, { mongodbAdapter }]) => mongodbAdapter(db)) : mysql ? {
|
|
95
|
+
db: mysql,
|
|
85
96
|
type: "mysql",
|
|
86
97
|
transaction: config?.transaction
|
|
87
|
-
} :
|
|
98
|
+
} : sqlite,
|
|
88
99
|
emailAndPassword: { enabled: true },
|
|
89
100
|
rateLimit: { enabled: false },
|
|
90
101
|
advanced: { cookies: {} },
|
|
91
102
|
logger: { level: "debug" }
|
|
92
103
|
};
|
|
104
|
+
const cleanup = async () => {
|
|
105
|
+
if (testWith === "mongodb") {
|
|
106
|
+
await (await mongodbClient()).dropDatabase();
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (postgres) {
|
|
110
|
+
try {
|
|
111
|
+
if (postgresSchema) await sql.raw(`DROP SCHEMA IF EXISTS ${quotePostgresIdentifier(postgresSchema)} CASCADE`).execute(postgres);
|
|
112
|
+
else await sql`DROP SCHEMA public CASCADE; CREATE SCHEMA public;`.execute(postgres);
|
|
113
|
+
} finally {
|
|
114
|
+
await postgres.destroy();
|
|
115
|
+
}
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (mysql) {
|
|
119
|
+
try {
|
|
120
|
+
await sql.raw(`DROP DATABASE IF EXISTS \`${mysqlDatabase}\``).execute(mysql);
|
|
121
|
+
} finally {
|
|
122
|
+
await mysql.destroy();
|
|
123
|
+
}
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (sqlite) sqlite.close();
|
|
127
|
+
};
|
|
128
|
+
cleanupSet.add(cleanup);
|
|
93
129
|
const authOptions = {
|
|
94
130
|
baseURL: "http://localhost:" + (config?.port || 3e3),
|
|
95
131
|
...opts,
|
|
@@ -126,32 +162,6 @@ async function getTestInstance(options, config) {
|
|
|
126
162
|
});
|
|
127
163
|
}
|
|
128
164
|
await createTestUser();
|
|
129
|
-
const cleanup = async () => {
|
|
130
|
-
if (testWith === "mongodb") {
|
|
131
|
-
await (await mongodbClient()).dropDatabase();
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
if (testWith === "postgres") {
|
|
135
|
-
const postgres = await getPostgres();
|
|
136
|
-
if (postgresSchema) await sql.raw(`DROP SCHEMA IF EXISTS ${quotePostgresIdentifier(postgresSchema)} CASCADE`).execute(postgres);
|
|
137
|
-
else await sql`DROP SCHEMA public CASCADE; CREATE SCHEMA public;`.execute(postgres);
|
|
138
|
-
await postgres.destroy();
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
if (testWith === "mysql") {
|
|
142
|
-
const mysql = await getMysql();
|
|
143
|
-
await sql`SET FOREIGN_KEY_CHECKS = 0;`.execute(mysql);
|
|
144
|
-
const tables = await mysql.introspection.getTables();
|
|
145
|
-
for (const table of tables) await mysql.deleteFrom(table.name).execute();
|
|
146
|
-
await sql`SET FOREIGN_KEY_CHECKS = 1;`.execute(mysql);
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
if (testWith === "sqlite") {
|
|
150
|
-
(await getSqlite()).close();
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
};
|
|
154
|
-
cleanupSet.add(cleanup);
|
|
155
165
|
const customFetchImpl = async (url, init) => {
|
|
156
166
|
const headers = init?.headers || {};
|
|
157
167
|
const storageHeaders = currentUserContextStorage.getStore()?.headers;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "better-auth",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.19",
|
|
4
4
|
"description": "The most comprehensive authentication framework for TypeScript.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -503,13 +503,13 @@
|
|
|
503
503
|
"kysely": "^0.28.17 || ^0.29.0",
|
|
504
504
|
"nanostores": "^1.3.0",
|
|
505
505
|
"zod": "^4.5.4",
|
|
506
|
-
"@better-auth/core": "1.7.
|
|
507
|
-
"@better-auth/drizzle-adapter": "1.7.
|
|
508
|
-
"@better-auth/kysely-adapter": "1.7.
|
|
509
|
-
"@better-auth/memory-adapter": "1.7.
|
|
510
|
-
"@better-auth/mongo-adapter": "1.7.
|
|
511
|
-
"@better-auth/prisma-adapter": "1.7.
|
|
512
|
-
"@better-auth/telemetry": "1.7.
|
|
506
|
+
"@better-auth/core": "1.7.19",
|
|
507
|
+
"@better-auth/drizzle-adapter": "1.7.19",
|
|
508
|
+
"@better-auth/kysely-adapter": "1.7.19",
|
|
509
|
+
"@better-auth/memory-adapter": "1.7.19",
|
|
510
|
+
"@better-auth/mongo-adapter": "1.7.19",
|
|
511
|
+
"@better-auth/prisma-adapter": "1.7.19",
|
|
512
|
+
"@better-auth/telemetry": "1.7.19"
|
|
513
513
|
},
|
|
514
514
|
"devDependencies": {
|
|
515
515
|
"@lynx-js/react": "^0.121.2",
|