better-auth 1.8.0-beta.3 → 1.8.0-beta.4
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/api/dispatch.mjs +1 -1
- package/dist/client/parser.mjs +1 -11
- package/dist/client/vue/index.mjs +13 -7
- package/dist/context/secret-utils.mjs +3 -3
- package/dist/package.mjs +1 -1
- package/dist/plugins/admin/admin.d.mts +2 -1
- package/dist/plugins/admin/admin.mjs +4 -4
- package/dist/plugins/admin/client.d.mts +1 -0
- package/dist/plugins/admin/error-codes.d.mts +1 -0
- package/dist/plugins/admin/error-codes.mjs +2 -1
- package/dist/plugins/admin/routes.mjs +1 -0
- package/dist/plugins/generic-oauth/providers/hubspot.mjs +5 -2
- package/dist/plugins/username/index.mjs +5 -18
- package/package.json +8 -8
package/dist/api/dispatch.mjs
CHANGED
|
@@ -243,7 +243,7 @@ async function dispatchAuthEndpoint(endpoint, input) {
|
|
|
243
243
|
});
|
|
244
244
|
if (result instanceof Response) return result;
|
|
245
245
|
internalContext.context.returned = result.response;
|
|
246
|
-
internalContext.context
|
|
246
|
+
mergeResponseHeaders(internalContext.context, result.headers);
|
|
247
247
|
const after = await runAfterHooks(internalContext, afterHooks, endpoint, operationId);
|
|
248
248
|
if (after.response !== void 0) result.response = after.response;
|
|
249
249
|
result.headers = after.headers ?? result.headers;
|
package/dist/client/parser.mjs
CHANGED
|
@@ -1,10 +1,4 @@
|
|
|
1
1
|
//#region src/client/parser.ts
|
|
2
|
-
const PROTO_POLLUTION_PATTERNS = {
|
|
3
|
-
proto: /"(?:_|\\u0{2}5[Ff]){2}(?:p|\\u0{2}70)(?:r|\\u0{2}72)(?:o|\\u0{2}6[Ff])(?:t|\\u0{2}74)(?:o|\\u0{2}6[Ff])(?:_|\\u0{2}5[Ff]){2}"\s*:/,
|
|
4
|
-
constructor: /"(?:c|\\u0063)(?:o|\\u006[Ff])(?:n|\\u006[Ee])(?:s|\\u0073)(?:t|\\u0074)(?:r|\\u0072)(?:u|\\u0075)(?:c|\\u0063)(?:t|\\u0074)(?:o|\\u006[Ff])(?:r|\\u0072)"\s*:/,
|
|
5
|
-
protoShort: /"__proto__"\s*:/,
|
|
6
|
-
constructorShort: /"constructor"\s*:/
|
|
7
|
-
};
|
|
8
2
|
const JSON_SIGNATURE = /^\s*["[{]|^\s*-?\d{1,16}(\.\d{1,17})?([Ee][+-]?\d+)?\s*$/;
|
|
9
3
|
const SPECIAL_VALUES = {
|
|
10
4
|
true: true,
|
|
@@ -40,14 +34,10 @@ function betterJSONParse(value, options = {}) {
|
|
|
40
34
|
if (strict) throw new SyntaxError("[better-json] Invalid JSON");
|
|
41
35
|
return value;
|
|
42
36
|
}
|
|
43
|
-
if (Object.entries(PROTO_POLLUTION_PATTERNS).some(([key, pattern]) => {
|
|
44
|
-
const matches = pattern.test(trimmed);
|
|
45
|
-
if (matches && warnings) console.warn(`[better-json] Detected potential prototype pollution attempt using ${key} pattern`);
|
|
46
|
-
return matches;
|
|
47
|
-
}) && strict) throw new Error("[better-json] Potential prototype pollution attempt detected");
|
|
48
37
|
try {
|
|
49
38
|
const secureReviver = (key, value) => {
|
|
50
39
|
if (key === "__proto__" || key === "constructor" && value && typeof value === "object" && "prototype" in value) {
|
|
40
|
+
if (strict) throw new Error("[better-json] Potential prototype pollution attempt detected");
|
|
51
41
|
if (warnings) console.warn(`[better-json] Dropping "${key}" key to prevent prototype pollution`);
|
|
52
42
|
return;
|
|
53
43
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { trimTrailingSlashes } from "../../utils/url.mjs";
|
|
1
2
|
import { getClientConfig } from "../config.mjs";
|
|
2
3
|
import { createDynamicPathProxy } from "../proxy.mjs";
|
|
3
4
|
import { useStore } from "./vue-store.mjs";
|
|
@@ -16,18 +17,23 @@ function toHeadersInit(headers) {
|
|
|
16
17
|
}
|
|
17
18
|
function createAuthClient(options) {
|
|
18
19
|
const { baseURL, pluginPathMethods, pluginsActions, pluginsAtoms, hydrateSession, $sessionSignal, $fetch, $store, atomListeners } = getClientConfig(options, false);
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
const baseURLKey = options?.baseURL || "inferred";
|
|
21
|
+
const basePath = options?.basePath ?? "/api/auth";
|
|
22
|
+
const sessionCacheKey = `better-auth:session:${baseURLKey}:${basePath}`;
|
|
23
|
+
const sessionPath = trimTrailingSlashes(basePath.startsWith("/") ? basePath : `/${basePath}`);
|
|
24
|
+
/**
|
|
25
|
+
* Stays relative without an explicit `baseURL` so the SSR session fetch
|
|
26
|
+
* forwards the incoming cookies.
|
|
27
|
+
*
|
|
28
|
+
* @see https://nuxt.com/docs/4.x/getting-started/data-fetching
|
|
29
|
+
*/
|
|
30
|
+
const sessionURL = `${options?.baseURL ? baseURL : sessionPath}/get-session`;
|
|
25
31
|
const resolvedHooks = {};
|
|
26
32
|
for (const [key, value] of Object.entries(pluginsAtoms)) resolvedHooks[getAtomKey(key)] = () => useStore(value);
|
|
27
33
|
function useSession(useFetch) {
|
|
28
34
|
if (useFetch) {
|
|
29
35
|
const sessionSignal = useStore($sessionSignal);
|
|
30
|
-
return useFetch(
|
|
36
|
+
return useFetch(sessionURL, {
|
|
31
37
|
headers: toHeadersInit(options?.fetchOptions?.headers),
|
|
32
38
|
key: sessionCacheKey,
|
|
33
39
|
watch: [sessionSignal]
|
|
@@ -12,12 +12,12 @@ function estimateEntropy(str) {
|
|
|
12
12
|
}
|
|
13
13
|
function parseSecretsEnv(envValue) {
|
|
14
14
|
if (!envValue) return null;
|
|
15
|
-
return envValue.split(",").map((entry) => {
|
|
15
|
+
return envValue.split(",").map((entry, index) => {
|
|
16
16
|
entry = entry.trim();
|
|
17
17
|
const colonIdx = entry.indexOf(":");
|
|
18
|
-
if (colonIdx === -1) throw new BetterAuthError(`Invalid BETTER_AUTH_SECRETS entry
|
|
18
|
+
if (colonIdx === -1) throw new BetterAuthError(`Invalid BETTER_AUTH_SECRETS entry at position ${index + 1}. Expected format: "<version>:<secret>"`);
|
|
19
19
|
const version = parseInt(entry.slice(0, colonIdx), 10);
|
|
20
|
-
if (!Number.isInteger(version) || version < 0) throw new BetterAuthError(`Invalid version in BETTER_AUTH_SECRETS
|
|
20
|
+
if (!Number.isInteger(version) || version < 0) throw new BetterAuthError(`Invalid version in BETTER_AUTH_SECRETS entry at position ${index + 1}. Version must be a non-negative integer.`);
|
|
21
21
|
const value = entry.slice(colonIdx + 1).trim();
|
|
22
22
|
if (!value) throw new BetterAuthError(`Empty secret value for version ${version} in BETTER_AUTH_SECRETS.`);
|
|
23
23
|
return {
|
package/dist/package.mjs
CHANGED
|
@@ -12,7 +12,7 @@ declare module "@better-auth/core" {
|
|
|
12
12
|
export declare const admin: <O extends AdminOptions>(options?: O | undefined) => {
|
|
13
13
|
id: "admin";
|
|
14
14
|
version: string;
|
|
15
|
-
init(): {
|
|
15
|
+
init(authContext: import("@better-auth/core").AuthContext): {
|
|
16
16
|
options: {
|
|
17
17
|
databaseHooks: {
|
|
18
18
|
user: {
|
|
@@ -863,6 +863,7 @@ export declare const admin: <O extends AdminOptions>(options?: O | undefined) =>
|
|
|
863
863
|
INVALID_ROLE_TYPE: import("@better-auth/core/utils/error-codes").RawError<"INVALID_ROLE_TYPE">;
|
|
864
864
|
YOU_ARE_NOT_ALLOWED_TO_SET_USERS_EMAIL: import("@better-auth/core/utils/error-codes").RawError<"YOU_ARE_NOT_ALLOWED_TO_SET_USERS_EMAIL">;
|
|
865
865
|
PASSWORD_CANNOT_BE_UPDATED_VIA_UPDATE_USER: import("@better-auth/core/utils/error-codes").RawError<"PASSWORD_CANNOT_BE_UPDATED_VIA_UPDATE_USER">;
|
|
866
|
+
USER_ID_CANNOT_BE_UPDATED: import("@better-auth/core/utils/error-codes").RawError<"USER_ID_CANNOT_BE_UPDATED">;
|
|
866
867
|
};
|
|
867
868
|
schema: {
|
|
868
869
|
user: {
|
|
@@ -22,7 +22,7 @@ const admin = (options) => {
|
|
|
22
22
|
return {
|
|
23
23
|
id: "admin",
|
|
24
24
|
version: PACKAGE_VERSION,
|
|
25
|
-
init() {
|
|
25
|
+
init(authContext) {
|
|
26
26
|
return { options: { databaseHooks: {
|
|
27
27
|
user: { create: { async before(user) {
|
|
28
28
|
return { data: {
|
|
@@ -31,11 +31,11 @@ const admin = (options) => {
|
|
|
31
31
|
} };
|
|
32
32
|
} } },
|
|
33
33
|
session: { create: { async before(session, ctx) {
|
|
34
|
-
|
|
35
|
-
const user = await
|
|
34
|
+
const internalAdapter = ctx?.context.internalAdapter ?? authContext.internalAdapter;
|
|
35
|
+
const user = await internalAdapter.findUserById(session.userId);
|
|
36
36
|
if (user?.banned) {
|
|
37
37
|
if (user.banExpires && new Date(user.banExpires).getTime() < Date.now()) {
|
|
38
|
-
await
|
|
38
|
+
await internalAdapter.updateUser(session.userId, {
|
|
39
39
|
banned: false,
|
|
40
40
|
banReason: null,
|
|
41
41
|
banExpires: null
|
|
@@ -75,6 +75,7 @@ export declare const adminClient: <O extends AdminClientOptions>(options?: O | u
|
|
|
75
75
|
INVALID_ROLE_TYPE: import("@better-auth/core/utils/error-codes").RawError<"INVALID_ROLE_TYPE">;
|
|
76
76
|
YOU_ARE_NOT_ALLOWED_TO_SET_USERS_EMAIL: import("@better-auth/core/utils/error-codes").RawError<"YOU_ARE_NOT_ALLOWED_TO_SET_USERS_EMAIL">;
|
|
77
77
|
PASSWORD_CANNOT_BE_UPDATED_VIA_UPDATE_USER: import("@better-auth/core/utils/error-codes").RawError<"PASSWORD_CANNOT_BE_UPDATED_VIA_UPDATE_USER">;
|
|
78
|
+
USER_ID_CANNOT_BE_UPDATED: import("@better-auth/core/utils/error-codes").RawError<"USER_ID_CANNOT_BE_UPDATED">;
|
|
78
79
|
};
|
|
79
80
|
};
|
|
80
81
|
//#endregion
|
|
@@ -23,5 +23,6 @@ export declare const ADMIN_ERROR_CODES: {
|
|
|
23
23
|
INVALID_ROLE_TYPE: import("@better-auth/core/utils/error-codes").RawError<"INVALID_ROLE_TYPE">;
|
|
24
24
|
YOU_ARE_NOT_ALLOWED_TO_SET_USERS_EMAIL: import("@better-auth/core/utils/error-codes").RawError<"YOU_ARE_NOT_ALLOWED_TO_SET_USERS_EMAIL">;
|
|
25
25
|
PASSWORD_CANNOT_BE_UPDATED_VIA_UPDATE_USER: import("@better-auth/core/utils/error-codes").RawError<"PASSWORD_CANNOT_BE_UPDATED_VIA_UPDATE_USER">;
|
|
26
|
+
USER_ID_CANNOT_BE_UPDATED: import("@better-auth/core/utils/error-codes").RawError<"USER_ID_CANNOT_BE_UPDATED">;
|
|
26
27
|
};
|
|
27
28
|
//#endregion
|
|
@@ -23,7 +23,8 @@ const ADMIN_ERROR_CODES = defineErrorCodes({
|
|
|
23
23
|
YOU_CANNOT_IMPERSONATE_ADMINS: "You cannot impersonate admins",
|
|
24
24
|
INVALID_ROLE_TYPE: "Invalid role type",
|
|
25
25
|
YOU_ARE_NOT_ALLOWED_TO_SET_USERS_EMAIL: "You are not allowed to update users email",
|
|
26
|
-
PASSWORD_CANNOT_BE_UPDATED_VIA_UPDATE_USER: "Password cannot be updated through update-user. Use the set-user-password endpoint instead"
|
|
26
|
+
PASSWORD_CANNOT_BE_UPDATED_VIA_UPDATE_USER: "Password cannot be updated through update-user. Use the set-user-password endpoint instead",
|
|
27
|
+
USER_ID_CANNOT_BE_UPDATED: "User id cannot be updated"
|
|
27
28
|
});
|
|
28
29
|
//#endregion
|
|
29
30
|
export { ADMIN_ERROR_CODES };
|
|
@@ -257,6 +257,7 @@ const adminUpdateUser = (opts) => createAuthEndpoint("/admin/update-user", {
|
|
|
257
257
|
const updateData = ctx.body.data;
|
|
258
258
|
const hasDataKey = (key) => Object.prototype.hasOwnProperty.call(updateData, key);
|
|
259
259
|
if (hasDataKey("password")) throw APIError.from("BAD_REQUEST", ADMIN_ERROR_CODES.PASSWORD_CANNOT_BE_UPDATED_VIA_UPDATE_USER);
|
|
260
|
+
if (hasDataKey("id")) throw APIError.from("BAD_REQUEST", ADMIN_ERROR_CODES.USER_ID_CANNOT_BE_UPDATED);
|
|
260
261
|
if (Object.prototype.hasOwnProperty.call(ctx.body.data, "role")) {
|
|
261
262
|
if (!hasPermission({
|
|
262
263
|
userId: ctx.context.session.user.id,
|
|
@@ -25,8 +25,11 @@ import { betterFetch } from "@better-fetch/fetch";
|
|
|
25
25
|
function hubspot(options) {
|
|
26
26
|
const defaultScopes = ["oauth"];
|
|
27
27
|
const getUserInfo = async (tokens) => {
|
|
28
|
-
|
|
29
|
-
const { data: profile, error } = await betterFetch(
|
|
28
|
+
if (!tokens.accessToken) return null;
|
|
29
|
+
const { data: profile, error } = await betterFetch("https://api.hubapi.com/oauth/v1/access-tokens/:token", {
|
|
30
|
+
params: { token: tokens.accessToken },
|
|
31
|
+
headers: { "Content-Type": "application/json" }
|
|
32
|
+
});
|
|
30
33
|
if (error || !profile) return null;
|
|
31
34
|
const id = profile.user_id ?? profile.signed_access_token?.userId;
|
|
32
35
|
if (!id) return null;
|
|
@@ -155,24 +155,14 @@ const usernameImpl = (options, includeDisplayUsername) => {
|
|
|
155
155
|
ctx.context.logger.warn("Username or password not found");
|
|
156
156
|
throw APIError.from("UNAUTHORIZED", USERNAME_ERROR_CODES.INVALID_USERNAME_OR_PASSWORD);
|
|
157
157
|
}
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
const maxUsernameLength = options?.maxUsernameLength || 30;
|
|
161
|
-
if (username.length < minUsernameLength) {
|
|
162
|
-
ctx.context.logger.warn("Username too short");
|
|
163
|
-
throw APIError.from("UNPROCESSABLE_ENTITY", USERNAME_ERROR_CODES.USERNAME_TOO_SHORT);
|
|
164
|
-
}
|
|
165
|
-
if (username.length > maxUsernameLength) {
|
|
166
|
-
ctx.context.logger.warn("Username too long");
|
|
167
|
-
throw APIError.from("UNPROCESSABLE_ENTITY", USERNAME_ERROR_CODES.USERNAME_TOO_LONG);
|
|
168
|
-
}
|
|
169
|
-
if (!await (options?.usernameValidator || defaultUsernameValidator)(username)) throw APIError.from("UNPROCESSABLE_ENTITY", USERNAME_ERROR_CODES.INVALID_USERNAME);
|
|
158
|
+
const validationError = await validateUsernameValue(ctx.body.username);
|
|
159
|
+
if (validationError) throw APIError.from("UNPROCESSABLE_ENTITY", validationError);
|
|
170
160
|
assertPasswordNotTooLong(ctx, ctx.body.password);
|
|
171
161
|
const user = await ctx.context.adapter.findOne({
|
|
172
162
|
model: "user",
|
|
173
163
|
where: [{
|
|
174
164
|
field: "username",
|
|
175
|
-
value: normalizer(username)
|
|
165
|
+
value: normalizer(ctx.body.username)
|
|
176
166
|
}]
|
|
177
167
|
});
|
|
178
168
|
if (!user) {
|
|
@@ -227,11 +217,8 @@ const usernameImpl = (options, includeDisplayUsername) => {
|
|
|
227
217
|
}, async (ctx) => {
|
|
228
218
|
const username = ctx.body.username;
|
|
229
219
|
if (!username) throw APIError.from("UNPROCESSABLE_ENTITY", USERNAME_ERROR_CODES.INVALID_USERNAME);
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
if (username.length < minUsernameLength) throw APIError.from("UNPROCESSABLE_ENTITY", USERNAME_ERROR_CODES.USERNAME_TOO_SHORT);
|
|
233
|
-
if (username.length > maxUsernameLength) throw APIError.from("UNPROCESSABLE_ENTITY", USERNAME_ERROR_CODES.USERNAME_TOO_LONG);
|
|
234
|
-
if (!await (options?.usernameValidator || defaultUsernameValidator)(username)) throw APIError.from("UNPROCESSABLE_ENTITY", USERNAME_ERROR_CODES.INVALID_USERNAME);
|
|
220
|
+
const validationError = await validateUsernameValue(username);
|
|
221
|
+
if (validationError) throw APIError.from("UNPROCESSABLE_ENTITY", validationError);
|
|
235
222
|
if (await ctx.context.adapter.findOne({
|
|
236
223
|
model: "user",
|
|
237
224
|
where: [{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "better-auth",
|
|
3
|
-
"version": "1.8.0-beta.
|
|
3
|
+
"version": "1.8.0-beta.4",
|
|
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.8.0-beta.
|
|
507
|
-
"@better-auth/drizzle-adapter": "1.8.0-beta.
|
|
508
|
-
"@better-auth/kysely-adapter": "1.8.0-beta.
|
|
509
|
-
"@better-auth/memory-adapter": "1.8.0-beta.
|
|
510
|
-
"@better-auth/mongo-adapter": "1.8.0-beta.
|
|
511
|
-
"@better-auth/prisma-adapter": "1.8.0-beta.
|
|
512
|
-
"@better-auth/telemetry": "1.8.0-beta.
|
|
506
|
+
"@better-auth/core": "1.8.0-beta.4",
|
|
507
|
+
"@better-auth/drizzle-adapter": "1.8.0-beta.4",
|
|
508
|
+
"@better-auth/kysely-adapter": "1.8.0-beta.4",
|
|
509
|
+
"@better-auth/memory-adapter": "1.8.0-beta.4",
|
|
510
|
+
"@better-auth/mongo-adapter": "1.8.0-beta.4",
|
|
511
|
+
"@better-auth/prisma-adapter": "1.8.0-beta.4",
|
|
512
|
+
"@better-auth/telemetry": "1.8.0-beta.4"
|
|
513
513
|
},
|
|
514
514
|
"devDependencies": {
|
|
515
515
|
"@lynx-js/react": "^0.121.2",
|