better-auth 1.6.14 → 1.6.15
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.d.mts +34 -0
- package/dist/api/dispatch.mjs +272 -0
- package/dist/api/index.d.mts +2 -1
- package/dist/api/index.mjs +2 -1
- package/dist/api/routes/session.mjs +1 -1
- package/dist/api/to-auth-endpoints.mjs +14 -265
- package/dist/cookies/cookie-utils.mjs +2 -2
- package/dist/package.mjs +1 -1
- package/dist/plugins/admin/routes.mjs +3 -0
- package/package.json +8 -8
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { AuthContext } from "@better-auth/core";
|
|
2
|
+
import { Endpoint, EndpointContext, InputContext } from "better-call";
|
|
3
|
+
|
|
4
|
+
//#region src/api/dispatch.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Input accepted by {@link dispatchAuthEndpoint}. `context` must already be a
|
|
7
|
+
* resolved `AuthContext`; the caller owns `baseURL` resolution. A fresh
|
|
8
|
+
* dispatch carries no `session` (the shared context has none), while a resumed
|
|
9
|
+
* dispatch carries the in-flight request's `session` through.
|
|
10
|
+
*/
|
|
11
|
+
type DispatchContext = Partial<InputContext<string, any> & EndpointContext<string, any>> & {
|
|
12
|
+
context: AuthContext & {
|
|
13
|
+
returned?: unknown | undefined;
|
|
14
|
+
responseHeaders?: Headers | undefined;
|
|
15
|
+
};
|
|
16
|
+
operationId?: string | undefined;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Run a single endpoint through the configured `hooks.before` / `hooks.after`
|
|
20
|
+
* pipeline, normalizing the response, headers, and `APIError`s the same way a
|
|
21
|
+
* router or `auth.api.*` dispatch does.
|
|
22
|
+
*
|
|
23
|
+
* This is the canonical hook runner. The HTTP router and `auth.api.*` reach it
|
|
24
|
+
* through {@link toAuthEndpoints}. Plugins call it directly when they need to
|
|
25
|
+
* re-enter the pipeline on purpose, such as resuming `/oauth2/authorize` after
|
|
26
|
+
* a fresh sign-in. Calling an endpoint as a plain function deliberately skips
|
|
27
|
+
* hooks; `dispatchAuthEndpoint` is the supported way to opt back in.
|
|
28
|
+
*
|
|
29
|
+
* @param endpoint The endpoint to dispatch.
|
|
30
|
+
* @param input Input context whose `context` is an already-resolved `AuthContext`.
|
|
31
|
+
*/
|
|
32
|
+
declare function dispatchAuthEndpoint(endpoint: Endpoint, input: DispatchContext): Promise<unknown>;
|
|
33
|
+
//#endregion
|
|
34
|
+
export { DispatchContext, dispatchAuthEndpoint };
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { isAPIError } from "../utils/is-api-error.mjs";
|
|
2
|
+
import { isRequestLike } from "../utils/url.mjs";
|
|
3
|
+
import { runWithEndpointContext } from "@better-auth/core/context";
|
|
4
|
+
import { shouldPublishLog } from "@better-auth/core/env";
|
|
5
|
+
import { APIError } from "@better-auth/core/error";
|
|
6
|
+
import { createDefu } from "defu";
|
|
7
|
+
import { ATTR_CONTEXT, ATTR_HOOK_TYPE, ATTR_HTTP_ROUTE, ATTR_OPERATION_ID, withSpan } from "@better-auth/core/instrumentation";
|
|
8
|
+
import { kAPIErrorHeaderSymbol, toResponse } from "better-call";
|
|
9
|
+
//#region src/api/dispatch.ts
|
|
10
|
+
const defuReplaceArrays = createDefu((obj, key, value) => {
|
|
11
|
+
if (Array.isArray(obj[key]) && Array.isArray(value)) {
|
|
12
|
+
obj[key] = value;
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
const hooksSourceWeakMap = /* @__PURE__ */ new WeakMap();
|
|
17
|
+
/**
|
|
18
|
+
* Resolves the operation id used for spans, preferring an explicit
|
|
19
|
+
* `operationId`, then the OpenAPI one, then the caller's `fallback` (the
|
|
20
|
+
* `auth.api.*` map key), and finally the route path.
|
|
21
|
+
*/
|
|
22
|
+
function getOperationId(endpoint, fallback) {
|
|
23
|
+
const opts = endpoint.options;
|
|
24
|
+
return opts?.operationId ?? opts?.metadata?.openapi?.operationId ?? fallback ?? endpoint.path ?? "/:virtual";
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Merge a set of response headers onto the dispatch's accumulator, appending
|
|
28
|
+
* `set-cookie` (multiple cookies are legal) and replacing everything else.
|
|
29
|
+
*/
|
|
30
|
+
function mergeResponseHeaders(context, headers) {
|
|
31
|
+
if (!headers) return;
|
|
32
|
+
headers.forEach((value, key) => {
|
|
33
|
+
if (!context.responseHeaders) context.responseHeaders = new Headers({ [key]: value });
|
|
34
|
+
else if (key.toLowerCase() === "set-cookie") context.responseHeaders.append(key, value);
|
|
35
|
+
else context.responseHeaders.set(key, value);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Combine the two header sources an `APIError` can carry into one set:
|
|
40
|
+
* - `kAPIErrorHeaderSymbol`: `ctx.responseHeaders` accumulated via
|
|
41
|
+
* `c.setCookie` / `c.setHeader` before the throw.
|
|
42
|
+
* - `e.headers`: explicit headers on the error (e.g. `location` from
|
|
43
|
+
* `c.redirect`).
|
|
44
|
+
*
|
|
45
|
+
* `c.redirect()` reuses `ctx.responseHeaders` as `e.headers`, so when both
|
|
46
|
+
* point at the same object iterating each would duplicate every `set-cookie`;
|
|
47
|
+
* the identity check skips that copy. Explicit error headers override
|
|
48
|
+
* accumulated ones, while cookies from both accumulate.
|
|
49
|
+
*/
|
|
50
|
+
function mergeAPIErrorHeaders(error) {
|
|
51
|
+
const ctxHeaders = error[kAPIErrorHeaderSymbol];
|
|
52
|
+
const errHeaders = error.headers && error.headers !== ctxHeaders ? new Headers(error.headers) : null;
|
|
53
|
+
if (!ctxHeaders && !errHeaders) return null;
|
|
54
|
+
const headers = new Headers();
|
|
55
|
+
ctxHeaders?.forEach((value, key) => {
|
|
56
|
+
headers.append(key, value);
|
|
57
|
+
});
|
|
58
|
+
errHeaders?.forEach((value, key) => {
|
|
59
|
+
if (key.toLowerCase() === "set-cookie") headers.append(key, value);
|
|
60
|
+
else headers.set(key, value);
|
|
61
|
+
});
|
|
62
|
+
return headers;
|
|
63
|
+
}
|
|
64
|
+
async function runBeforeHooks(context, hooks, endpoint, operationId) {
|
|
65
|
+
let modifiedContext = {};
|
|
66
|
+
for (const hook of hooks) {
|
|
67
|
+
let matched = false;
|
|
68
|
+
try {
|
|
69
|
+
matched = hook.matcher(context);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
const hookSource = hooksSourceWeakMap.get(hook.handler) ?? "unknown";
|
|
72
|
+
context.context.logger.error(`An error occurred during ${hookSource} hook matcher execution:`, error);
|
|
73
|
+
throw new APIError("INTERNAL_SERVER_ERROR", { message: "An error occurred during hook matcher execution. Check the logs for more details." });
|
|
74
|
+
}
|
|
75
|
+
if (!matched) continue;
|
|
76
|
+
const hookSource = hooksSourceWeakMap.get(hook.handler) ?? "unknown";
|
|
77
|
+
const route = endpoint.path ?? "/:virtual";
|
|
78
|
+
const result = await withSpan(`hook before ${route} ${hookSource}`, {
|
|
79
|
+
[ATTR_HOOK_TYPE]: "before",
|
|
80
|
+
[ATTR_HTTP_ROUTE]: route,
|
|
81
|
+
[ATTR_CONTEXT]: hookSource,
|
|
82
|
+
[ATTR_OPERATION_ID]: operationId
|
|
83
|
+
}, () => hook.handler({
|
|
84
|
+
...context,
|
|
85
|
+
returnHeaders: true
|
|
86
|
+
})).catch((e) => {
|
|
87
|
+
if (isAPIError(e) && shouldPublishLog(context.context.logger.level, "debug")) e.stack = e.errorStack;
|
|
88
|
+
throw e;
|
|
89
|
+
});
|
|
90
|
+
mergeResponseHeaders(context.context, result?.headers);
|
|
91
|
+
const hookReturn = result?.response;
|
|
92
|
+
if (hookReturn && typeof hookReturn === "object") {
|
|
93
|
+
if ("context" in hookReturn && typeof hookReturn.context === "object") {
|
|
94
|
+
const { headers, ...rest } = hookReturn.context;
|
|
95
|
+
if (headers instanceof Headers) if (modifiedContext.headers) headers.forEach((value, key) => {
|
|
96
|
+
modifiedContext.headers?.set(key, value);
|
|
97
|
+
});
|
|
98
|
+
else modifiedContext.headers = headers;
|
|
99
|
+
modifiedContext = defuReplaceArrays(rest, modifiedContext);
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
return hookReturn;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return { context: modifiedContext };
|
|
106
|
+
}
|
|
107
|
+
async function runAfterHooks(context, hooks, endpoint, operationId) {
|
|
108
|
+
for (const hook of hooks) {
|
|
109
|
+
if (!hook.matcher(context)) continue;
|
|
110
|
+
const hookSource = hooksSourceWeakMap.get(hook.handler) ?? "unknown";
|
|
111
|
+
const route = endpoint.path ?? "/:virtual";
|
|
112
|
+
const result = await withSpan(`hook after ${route} ${hookSource}`, {
|
|
113
|
+
[ATTR_HOOK_TYPE]: "after",
|
|
114
|
+
[ATTR_HTTP_ROUTE]: route,
|
|
115
|
+
[ATTR_CONTEXT]: hookSource,
|
|
116
|
+
[ATTR_OPERATION_ID]: operationId
|
|
117
|
+
}, () => hook.handler(context)).catch((e) => {
|
|
118
|
+
if (isAPIError(e)) {
|
|
119
|
+
if (shouldPublishLog(context.context.logger.level, "debug")) e.stack = e.errorStack;
|
|
120
|
+
return {
|
|
121
|
+
response: e,
|
|
122
|
+
headers: mergeAPIErrorHeaders(e)
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
throw e;
|
|
126
|
+
});
|
|
127
|
+
mergeResponseHeaders(context.context, result.headers);
|
|
128
|
+
if (result.response !== void 0) context.context.returned = result.response;
|
|
129
|
+
}
|
|
130
|
+
return {
|
|
131
|
+
response: context.context.returned,
|
|
132
|
+
headers: context.context.responseHeaders
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function getHooks(authContext) {
|
|
136
|
+
const plugins = authContext.options.plugins || [];
|
|
137
|
+
const beforeHooks = [];
|
|
138
|
+
const afterHooks = [];
|
|
139
|
+
const beforeHookHandler = authContext.options.hooks?.before;
|
|
140
|
+
if (beforeHookHandler) {
|
|
141
|
+
hooksSourceWeakMap.set(beforeHookHandler, "user");
|
|
142
|
+
beforeHooks.push({
|
|
143
|
+
matcher: () => true,
|
|
144
|
+
handler: beforeHookHandler
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
const afterHookHandler = authContext.options.hooks?.after;
|
|
148
|
+
if (afterHookHandler) {
|
|
149
|
+
hooksSourceWeakMap.set(afterHookHandler, "user");
|
|
150
|
+
afterHooks.push({
|
|
151
|
+
matcher: () => true,
|
|
152
|
+
handler: afterHookHandler
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
const pluginBeforeHooks = plugins.flatMap((plugin) => (plugin.hooks?.before ?? []).map((h) => {
|
|
156
|
+
hooksSourceWeakMap.set(h.handler, `plugin:${plugin.id}`);
|
|
157
|
+
return h;
|
|
158
|
+
}));
|
|
159
|
+
const pluginAfterHooks = plugins.flatMap((plugin) => (plugin.hooks?.after ?? []).map((h) => {
|
|
160
|
+
hooksSourceWeakMap.set(h.handler, `plugin:${plugin.id}`);
|
|
161
|
+
return h;
|
|
162
|
+
}));
|
|
163
|
+
if (pluginBeforeHooks.length) beforeHooks.push(...pluginBeforeHooks);
|
|
164
|
+
if (pluginAfterHooks.length) afterHooks.push(...pluginAfterHooks);
|
|
165
|
+
return {
|
|
166
|
+
beforeHooks,
|
|
167
|
+
afterHooks
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Run a single endpoint through the configured `hooks.before` / `hooks.after`
|
|
172
|
+
* pipeline, normalizing the response, headers, and `APIError`s the same way a
|
|
173
|
+
* router or `auth.api.*` dispatch does.
|
|
174
|
+
*
|
|
175
|
+
* This is the canonical hook runner. The HTTP router and `auth.api.*` reach it
|
|
176
|
+
* through {@link toAuthEndpoints}. Plugins call it directly when they need to
|
|
177
|
+
* re-enter the pipeline on purpose, such as resuming `/oauth2/authorize` after
|
|
178
|
+
* a fresh sign-in. Calling an endpoint as a plain function deliberately skips
|
|
179
|
+
* hooks; `dispatchAuthEndpoint` is the supported way to opt back in.
|
|
180
|
+
*
|
|
181
|
+
* @param endpoint The endpoint to dispatch.
|
|
182
|
+
* @param input Input context whose `context` is an already-resolved `AuthContext`.
|
|
183
|
+
*/
|
|
184
|
+
async function dispatchAuthEndpoint(endpoint, input) {
|
|
185
|
+
const operationId = input.operationId ?? getOperationId(endpoint);
|
|
186
|
+
const route = endpoint.path ?? "/:virtual";
|
|
187
|
+
const endpointMethod = endpoint.options?.method;
|
|
188
|
+
const defaultMethod = Array.isArray(endpointMethod) ? endpointMethod[0] : endpointMethod;
|
|
189
|
+
const methodName = input.method ?? input.request?.method ?? defaultMethod ?? "?";
|
|
190
|
+
const shouldReturnResponse = input.asResponse ?? isRequestLike(input.request);
|
|
191
|
+
let internalContext = {
|
|
192
|
+
...input,
|
|
193
|
+
context: {
|
|
194
|
+
...input.context,
|
|
195
|
+
returned: void 0,
|
|
196
|
+
responseHeaders: void 0,
|
|
197
|
+
session: input.context.session ?? null
|
|
198
|
+
},
|
|
199
|
+
path: endpoint.path,
|
|
200
|
+
headers: input.headers ? new Headers(input.headers) : void 0
|
|
201
|
+
};
|
|
202
|
+
return withSpan(`${methodName} ${route}`, {
|
|
203
|
+
[ATTR_HTTP_ROUTE]: route,
|
|
204
|
+
[ATTR_OPERATION_ID]: operationId
|
|
205
|
+
}, async () => runWithEndpointContext(internalContext, async () => {
|
|
206
|
+
const { beforeHooks, afterHooks } = getHooks(internalContext.context);
|
|
207
|
+
const before = await runBeforeHooks(internalContext, beforeHooks, endpoint, operationId);
|
|
208
|
+
if ("context" in before && before.context && typeof before.context === "object") {
|
|
209
|
+
const { headers, ...rest } = before.context;
|
|
210
|
+
if (headers) {
|
|
211
|
+
if (!internalContext.headers) internalContext.headers = new Headers();
|
|
212
|
+
const requestHeaders = internalContext.headers;
|
|
213
|
+
headers.forEach((value, key) => {
|
|
214
|
+
requestHeaders.set(key, value);
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
internalContext = defuReplaceArrays(rest, internalContext);
|
|
218
|
+
} else if (before) {
|
|
219
|
+
const responseHeaders = internalContext.context.responseHeaders;
|
|
220
|
+
return shouldReturnResponse ? toResponse(before, { headers: responseHeaders }) : input.returnHeaders ? {
|
|
221
|
+
headers: responseHeaders,
|
|
222
|
+
response: before
|
|
223
|
+
} : before;
|
|
224
|
+
}
|
|
225
|
+
internalContext.asResponse = false;
|
|
226
|
+
internalContext.returnHeaders = true;
|
|
227
|
+
internalContext.returnStatus = true;
|
|
228
|
+
const result = await runWithEndpointContext(internalContext, () => withSpan(`handler ${route}`, {
|
|
229
|
+
[ATTR_HTTP_ROUTE]: route,
|
|
230
|
+
[ATTR_OPERATION_ID]: operationId
|
|
231
|
+
}, () => endpoint(internalContext))).catch((e) => {
|
|
232
|
+
if (isAPIError(e)) return {
|
|
233
|
+
response: e,
|
|
234
|
+
status: e.statusCode,
|
|
235
|
+
headers: mergeAPIErrorHeaders(e)
|
|
236
|
+
};
|
|
237
|
+
throw e;
|
|
238
|
+
});
|
|
239
|
+
if (result instanceof Response) return result;
|
|
240
|
+
internalContext.context.returned = result.response;
|
|
241
|
+
internalContext.context.responseHeaders = result.headers ?? void 0;
|
|
242
|
+
const after = await runAfterHooks(internalContext, afterHooks, endpoint, operationId);
|
|
243
|
+
if (after.response !== void 0) result.response = after.response;
|
|
244
|
+
result.headers = after.headers ?? result.headers;
|
|
245
|
+
if (isAPIError(result.response) && shouldPublishLog(internalContext.context.logger.level, "debug")) result.response.stack = result.response.errorStack;
|
|
246
|
+
if (isAPIError(result.response) && !shouldReturnResponse) {
|
|
247
|
+
if (result.headers) Object.defineProperty(result.response, kAPIErrorHeaderSymbol, {
|
|
248
|
+
enumerable: false,
|
|
249
|
+
configurable: true,
|
|
250
|
+
writable: false,
|
|
251
|
+
value: result.headers
|
|
252
|
+
});
|
|
253
|
+
throw result.response;
|
|
254
|
+
}
|
|
255
|
+
return shouldReturnResponse ? toResponse(result.response, {
|
|
256
|
+
headers: result.headers ?? void 0,
|
|
257
|
+
status: result.status
|
|
258
|
+
}) : input.returnHeaders ? input.returnStatus ? {
|
|
259
|
+
headers: result.headers,
|
|
260
|
+
response: result.response,
|
|
261
|
+
status: result.status
|
|
262
|
+
} : {
|
|
263
|
+
headers: result.headers,
|
|
264
|
+
response: result.response
|
|
265
|
+
} : input.returnStatus ? {
|
|
266
|
+
response: result.response,
|
|
267
|
+
status: result.status
|
|
268
|
+
} : result.response;
|
|
269
|
+
}));
|
|
270
|
+
}
|
|
271
|
+
//#endregion
|
|
272
|
+
export { dispatchAuthEndpoint, getOperationId };
|
package/dist/api/index.d.mts
CHANGED
|
@@ -2,6 +2,7 @@ import { OverrideMerge, Prettify as Prettify$1, UnionToIntersection } from "../t
|
|
|
2
2
|
import { AdditionalSessionFieldsInput, AdditionalUserFieldsInput } from "../types/models.mjs";
|
|
3
3
|
import { getIp } from "../utils/get-request-ip.mjs";
|
|
4
4
|
import { isAPIError } from "../utils/is-api-error.mjs";
|
|
5
|
+
import { DispatchContext, dispatchAuthEndpoint } from "./dispatch.mjs";
|
|
5
6
|
import { requireOrgRole, requireResourceOwnership } from "./middlewares/authorization.mjs";
|
|
6
7
|
import { formCsrfMiddleware, originCheck, originCheckMiddleware } from "./middlewares/origin-check.mjs";
|
|
7
8
|
import { accountInfo, getAccessToken, linkSocialAccount, listUserAccounts, refreshToken, unlinkAccount } from "./routes/account.mjs";
|
|
@@ -3960,4 +3961,4 @@ declare const router: <Option extends BetterAuthOptions>(ctx: AuthContext, optio
|
|
|
3960
3961
|
} extends infer T_2 ? { [K in keyof T_2 as K extends keyof T_1 ? never : K]: T_2[K] } : never) & T_1> : never : never : never;
|
|
3961
3962
|
};
|
|
3962
3963
|
//#endregion
|
|
3963
|
-
export { APIError, type AuthEndpoint, type AuthMiddleware, accountInfo, callbackOAuth, changeEmail, changePassword, checkEndpointConflicts, createAuthEndpoint, createAuthMiddleware, createEmailVerificationToken, deleteUser, deleteUserCallback, error, formCsrfMiddleware, freshSessionMiddleware, getAccessToken, getEndpoints, getIp, getOAuthState, getSession, getSessionFromCtx, getShouldSkipSessionRefresh, isAPIError, linkSocialAccount, listSessions, listUserAccounts, ok, optionsMiddleware, originCheck, originCheckMiddleware, refreshToken, requestOnlySessionMiddleware, requestPasswordReset, requestPasswordResetCallback, requireOrgRole, requireResourceOwnership, resetPassword, revokeOtherSessions, revokeSession, revokeSessions, router, sendVerificationEmail, sendVerificationEmailFn, sensitiveSessionMiddleware, sessionMiddleware, setPassword, setShouldSkipSessionRefresh, signInEmail, signInSocial, signOut, signUpEmail, unlinkAccount, updateSession, updateUser, verifyEmail, verifyPassword };
|
|
3964
|
+
export { APIError, type AuthEndpoint, type AuthMiddleware, type DispatchContext, accountInfo, callbackOAuth, changeEmail, changePassword, checkEndpointConflicts, createAuthEndpoint, createAuthMiddleware, createEmailVerificationToken, deleteUser, deleteUserCallback, dispatchAuthEndpoint, error, formCsrfMiddleware, freshSessionMiddleware, getAccessToken, getEndpoints, getIp, getOAuthState, getSession, getSessionFromCtx, getShouldSkipSessionRefresh, isAPIError, linkSocialAccount, listSessions, listUserAccounts, ok, optionsMiddleware, originCheck, originCheckMiddleware, refreshToken, requestOnlySessionMiddleware, requestPasswordReset, requestPasswordResetCallback, requireOrgRole, requireResourceOwnership, resetPassword, revokeOtherSessions, revokeSession, revokeSessions, router, sendVerificationEmail, sendVerificationEmailFn, sensitiveSessionMiddleware, sessionMiddleware, setPassword, setShouldSkipSessionRefresh, signInEmail, signInSocial, signOut, signUpEmail, unlinkAccount, updateSession, updateUser, verifyEmail, verifyPassword };
|
package/dist/api/index.mjs
CHANGED
|
@@ -17,6 +17,7 @@ import { signOut } from "./routes/sign-out.mjs";
|
|
|
17
17
|
import { signUpEmail } from "./routes/sign-up.mjs";
|
|
18
18
|
import { updateSession } from "./routes/update-session.mjs";
|
|
19
19
|
import { changeEmail, changePassword, deleteUser, deleteUserCallback, setPassword, updateUser } from "./routes/update-user.mjs";
|
|
20
|
+
import { dispatchAuthEndpoint } from "./dispatch.mjs";
|
|
20
21
|
import { toAuthEndpoints } from "./to-auth-endpoints.mjs";
|
|
21
22
|
import { logger } from "@better-auth/core/env";
|
|
22
23
|
import { APIError } from "@better-auth/core/error";
|
|
@@ -213,4 +214,4 @@ const router = (ctx, options) => {
|
|
|
213
214
|
});
|
|
214
215
|
};
|
|
215
216
|
//#endregion
|
|
216
|
-
export { APIError, accountInfo, callbackOAuth, changeEmail, changePassword, checkEndpointConflicts, createAuthEndpoint, createAuthMiddleware, createEmailVerificationToken, deleteUser, deleteUserCallback, error, formCsrfMiddleware, freshSessionMiddleware, getAccessToken, getEndpoints, getIp, getOAuthState, getSession, getSessionFromCtx, getShouldSkipSessionRefresh, isAPIError, linkSocialAccount, listSessions, listUserAccounts, ok, optionsMiddleware, originCheck, originCheckMiddleware, refreshToken, requestOnlySessionMiddleware, requestPasswordReset, requestPasswordResetCallback, requireOrgRole, requireResourceOwnership, resetPassword, revokeOtherSessions, revokeSession, revokeSessions, router, sendVerificationEmail, sendVerificationEmailFn, sensitiveSessionMiddleware, sessionMiddleware, setPassword, setShouldSkipSessionRefresh, signInEmail, signInSocial, signOut, signUpEmail, unlinkAccount, updateSession, updateUser, verifyEmail, verifyPassword };
|
|
217
|
+
export { APIError, accountInfo, callbackOAuth, changeEmail, changePassword, checkEndpointConflicts, createAuthEndpoint, createAuthMiddleware, createEmailVerificationToken, deleteUser, deleteUserCallback, dispatchAuthEndpoint, error, formCsrfMiddleware, freshSessionMiddleware, getAccessToken, getEndpoints, getIp, getOAuthState, getSession, getSessionFromCtx, getShouldSkipSessionRefresh, isAPIError, linkSocialAccount, listSessions, listUserAccounts, ok, optionsMiddleware, originCheck, originCheckMiddleware, refreshToken, requestOnlySessionMiddleware, requestPasswordReset, requestPasswordResetCallback, requireOrgRole, requireResourceOwnership, resetPassword, revokeOtherSessions, revokeSession, revokeSessions, router, sendVerificationEmail, sendVerificationEmailFn, sensitiveSessionMiddleware, sessionMiddleware, setPassword, setShouldSkipSessionRefresh, signInEmail, signInSocial, signOut, signUpEmail, unlinkAccount, updateSession, updateUser, verifyEmail, verifyPassword };
|
|
@@ -355,7 +355,7 @@ const freshSessionMiddleware = createAuthMiddleware(async (ctx) => {
|
|
|
355
355
|
const listSessions = () => createAuthEndpoint("/list-sessions", {
|
|
356
356
|
method: "GET",
|
|
357
357
|
operationId: "listUserSessions",
|
|
358
|
-
use: [
|
|
358
|
+
use: [freshSessionMiddleware],
|
|
359
359
|
requireHeaders: true,
|
|
360
360
|
metadata: { openapi: {
|
|
361
361
|
operationId: "listUserSessions",
|
|
@@ -1,25 +1,9 @@
|
|
|
1
|
-
import { isAPIError } from "../utils/is-api-error.mjs";
|
|
2
1
|
import { isDynamicBaseURLConfig, isRequestLike } from "../utils/url.mjs";
|
|
3
2
|
import { pickSource, resolveDynamicTrustedProxyHeaders, resolveRequestContext } from "../context/helpers.mjs";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import { dispatchAuthEndpoint, getOperationId } from "./dispatch.mjs";
|
|
4
|
+
import { hasRequestState, runWithRequestState } from "@better-auth/core/context";
|
|
6
5
|
import { APIError, BetterAuthError } from "@better-auth/core/error";
|
|
7
|
-
import { createDefu } from "defu";
|
|
8
|
-
import { ATTR_CONTEXT, ATTR_HOOK_TYPE, ATTR_HTTP_ROUTE, ATTR_OPERATION_ID, withSpan } from "@better-auth/core/instrumentation";
|
|
9
|
-
import { kAPIErrorHeaderSymbol, toResponse } from "better-call";
|
|
10
6
|
//#region src/api/to-auth-endpoints.ts
|
|
11
|
-
const defuReplaceArrays = createDefu((obj, key, value) => {
|
|
12
|
-
if (Array.isArray(obj[key]) && Array.isArray(value)) {
|
|
13
|
-
obj[key] = value;
|
|
14
|
-
return true;
|
|
15
|
-
}
|
|
16
|
-
});
|
|
17
|
-
const hooksSourceWeakMap = /* @__PURE__ */ new WeakMap();
|
|
18
|
-
function getOperationId(endpoint, key) {
|
|
19
|
-
if (!endpoint?.options) return key;
|
|
20
|
-
const opts = endpoint.options;
|
|
21
|
-
return opts.operationId ?? opts.metadata?.openapi?.operationId ?? key;
|
|
22
|
-
}
|
|
23
7
|
/**
|
|
24
8
|
* Resolves the per-call `AuthContext` for endpoints with a dynamic `baseURL`.
|
|
25
9
|
*
|
|
@@ -41,269 +25,34 @@ async function resolveDynamicContext(rawCtx, input) {
|
|
|
41
25
|
throw err;
|
|
42
26
|
}
|
|
43
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Wraps each raw endpoint so a router or `auth.api.*` call runs it through the
|
|
30
|
+
* configured hook pipeline. Per-call work that is specific to this entry point
|
|
31
|
+
* (dynamic `baseURL` resolution, request-state initialization) happens here;
|
|
32
|
+
* the hook pipeline itself lives in {@link dispatchAuthEndpoint}.
|
|
33
|
+
*/
|
|
44
34
|
function toAuthEndpoints(endpoints, ctx) {
|
|
45
35
|
const api = {};
|
|
46
36
|
for (const [key, endpoint] of Object.entries(endpoints)) {
|
|
47
37
|
api[key] = async (context) => {
|
|
48
38
|
const operationId = getOperationId(endpoint, key);
|
|
49
|
-
const endpointMethod = endpoint?.options?.method;
|
|
50
|
-
const defaultMethod = Array.isArray(endpointMethod) ? endpointMethod[0] : endpointMethod;
|
|
51
39
|
const run = async () => {
|
|
52
40
|
const rawContext = await ctx;
|
|
53
|
-
const methodName = context?.method ?? context?.request?.method ?? defaultMethod ?? "?";
|
|
54
|
-
const route = endpoint.path ?? "/:virtual";
|
|
55
41
|
const authContext = isDynamicBaseURLConfig(rawContext.options.baseURL) ? await resolveDynamicContext(rawContext, context) : rawContext;
|
|
56
|
-
|
|
42
|
+
return dispatchAuthEndpoint(endpoint, {
|
|
57
43
|
...context,
|
|
58
|
-
context:
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
session: null
|
|
63
|
-
},
|
|
64
|
-
path: endpoint.path,
|
|
65
|
-
headers: context?.headers ? new Headers(context?.headers) : void 0
|
|
66
|
-
};
|
|
67
|
-
const hasRequest = isRequestLike(context?.request);
|
|
68
|
-
const shouldReturnResponse = context?.asResponse ?? hasRequest;
|
|
69
|
-
return withSpan(`${methodName} ${route}`, {
|
|
70
|
-
[ATTR_HTTP_ROUTE]: route,
|
|
71
|
-
[ATTR_OPERATION_ID]: operationId
|
|
72
|
-
}, async () => runWithEndpointContext(internalContext, async () => {
|
|
73
|
-
const { beforeHooks, afterHooks } = getHooks(authContext);
|
|
74
|
-
const before = await runBeforeHooks(internalContext, beforeHooks, endpoint, operationId);
|
|
75
|
-
/**
|
|
76
|
-
* If `before.context` is returned, it should
|
|
77
|
-
* get merged with the original context
|
|
78
|
-
*/
|
|
79
|
-
if ("context" in before && before.context && typeof before.context === "object") {
|
|
80
|
-
const { headers, ...rest } = before.context;
|
|
81
|
-
/**
|
|
82
|
-
* Headers should be merged differently
|
|
83
|
-
* so the hook doesn't override the whole
|
|
84
|
-
* header
|
|
85
|
-
*/
|
|
86
|
-
if (headers) headers.forEach((value, key) => {
|
|
87
|
-
internalContext.headers.set(key, value);
|
|
88
|
-
});
|
|
89
|
-
internalContext = defuReplaceArrays(rest, internalContext);
|
|
90
|
-
} else if (before) return shouldReturnResponse ? toResponse(before, { headers: context?.headers }) : context?.returnHeaders ? {
|
|
91
|
-
headers: context?.headers,
|
|
92
|
-
response: before
|
|
93
|
-
} : before;
|
|
94
|
-
internalContext.asResponse = false;
|
|
95
|
-
internalContext.returnHeaders = true;
|
|
96
|
-
internalContext.returnStatus = true;
|
|
97
|
-
const result = await runWithEndpointContext(internalContext, () => withSpan(`handler ${route}`, {
|
|
98
|
-
[ATTR_HTTP_ROUTE]: route,
|
|
99
|
-
[ATTR_OPERATION_ID]: operationId
|
|
100
|
-
}, () => endpoint(internalContext))).catch((e) => {
|
|
101
|
-
if (isAPIError(e)) {
|
|
102
|
-
/**
|
|
103
|
-
* API Errors from response are caught
|
|
104
|
-
* and returned to hooks.
|
|
105
|
-
*
|
|
106
|
-
* Headers come from two sources that must both
|
|
107
|
-
* survive:
|
|
108
|
-
* - `kAPIErrorHeaderSymbol`: ctx.responseHeaders
|
|
109
|
-
* accumulated via c.setCookie / c.setHeader
|
|
110
|
-
* before the throw.
|
|
111
|
-
* - `e.headers`: explicit headers on the APIError
|
|
112
|
-
* (e.g. `location` from c.redirect).
|
|
113
|
-
*
|
|
114
|
-
* Start from the accumulated ctx headers, then
|
|
115
|
-
* apply e.headers on top — appending `set-cookie`
|
|
116
|
-
* and setting others — so explicit APIError
|
|
117
|
-
* headers override while cookies accumulate.
|
|
118
|
-
*/
|
|
119
|
-
const ctxHeaders = e[kAPIErrorHeaderSymbol];
|
|
120
|
-
/**
|
|
121
|
-
* `c.redirect()` (and similar APIError throws) reuse
|
|
122
|
-
* `ctx.responseHeaders` as `e.headers`, so when both sources
|
|
123
|
-
* reference the same Headers, iterating both duplicates every
|
|
124
|
-
* `set-cookie`. Skip the `errHeaders` copy in that case.
|
|
125
|
-
*/
|
|
126
|
-
const errHeaders = e.headers && e.headers !== ctxHeaders ? new Headers(e.headers) : null;
|
|
127
|
-
let headers = null;
|
|
128
|
-
if (ctxHeaders || errHeaders) {
|
|
129
|
-
headers = new Headers();
|
|
130
|
-
ctxHeaders?.forEach((value, key) => {
|
|
131
|
-
headers.append(key, value);
|
|
132
|
-
});
|
|
133
|
-
errHeaders?.forEach((value, key) => {
|
|
134
|
-
if (key.toLowerCase() === "set-cookie") headers.append(key, value);
|
|
135
|
-
else headers.set(key, value);
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
return {
|
|
139
|
-
response: e,
|
|
140
|
-
status: e.statusCode,
|
|
141
|
-
headers
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
throw e;
|
|
145
|
-
});
|
|
146
|
-
if (result && result instanceof Response) return result;
|
|
147
|
-
internalContext.context.returned = result.response;
|
|
148
|
-
internalContext.context.responseHeaders = result.headers;
|
|
149
|
-
const after = await runAfterHooks(internalContext, afterHooks, endpoint, operationId);
|
|
150
|
-
if (after.response) result.response = after.response;
|
|
151
|
-
if (isAPIError(result.response) && shouldPublishLog(authContext.logger.level, "debug")) result.response.stack = result.response.errorStack;
|
|
152
|
-
if (isAPIError(result.response) && !shouldReturnResponse) {
|
|
153
|
-
/**
|
|
154
|
-
* Non-response path: we re-throw the raw APIError
|
|
155
|
-
* to callers of `auth.api.*`. `result.headers`
|
|
156
|
-
* holds the merged ctx + explicit headers (see
|
|
157
|
-
* catch block above) — rewrite
|
|
158
|
-
* `kAPIErrorHeaderSymbol` with the merged set so
|
|
159
|
-
* downstream pipelines (e.g. better-call's
|
|
160
|
-
* response builder, or an outer hook catch) see
|
|
161
|
-
* the same headers we'd have written on the
|
|
162
|
-
* response.
|
|
163
|
-
*/
|
|
164
|
-
if (result.headers) Object.defineProperty(result.response, kAPIErrorHeaderSymbol, {
|
|
165
|
-
enumerable: false,
|
|
166
|
-
configurable: true,
|
|
167
|
-
writable: false,
|
|
168
|
-
value: result.headers
|
|
169
|
-
});
|
|
170
|
-
throw result.response;
|
|
171
|
-
}
|
|
172
|
-
return shouldReturnResponse ? toResponse(result.response, {
|
|
173
|
-
headers: result.headers,
|
|
174
|
-
status: result.status
|
|
175
|
-
}) : context?.returnHeaders ? context?.returnStatus ? {
|
|
176
|
-
headers: result.headers,
|
|
177
|
-
response: result.response,
|
|
178
|
-
status: result.status
|
|
179
|
-
} : {
|
|
180
|
-
headers: result.headers,
|
|
181
|
-
response: result.response
|
|
182
|
-
} : context?.returnStatus ? {
|
|
183
|
-
response: result.response,
|
|
184
|
-
status: result.status
|
|
185
|
-
} : result.response;
|
|
186
|
-
}));
|
|
44
|
+
context: authContext,
|
|
45
|
+
operationId,
|
|
46
|
+
asResponse: context?.asResponse ?? isRequestLike(context?.request)
|
|
47
|
+
});
|
|
187
48
|
};
|
|
188
49
|
if (await hasRequestState()) return run();
|
|
189
|
-
|
|
50
|
+
return runWithRequestState(/* @__PURE__ */ new WeakMap(), run);
|
|
190
51
|
};
|
|
191
52
|
api[key].path = endpoint.path;
|
|
192
53
|
api[key].options = endpoint.options;
|
|
193
54
|
}
|
|
194
55
|
return api;
|
|
195
56
|
}
|
|
196
|
-
async function runBeforeHooks(context, hooks, endpoint, operationId) {
|
|
197
|
-
let modifiedContext = {};
|
|
198
|
-
for (const hook of hooks) {
|
|
199
|
-
let matched = false;
|
|
200
|
-
try {
|
|
201
|
-
matched = hook.matcher(context);
|
|
202
|
-
} catch (error) {
|
|
203
|
-
const hookSource = hooksSourceWeakMap.get(hook.handler) ?? "unknown";
|
|
204
|
-
context.context.logger.error(`An error occurred during ${hookSource} hook matcher execution:`, error);
|
|
205
|
-
throw new APIError("INTERNAL_SERVER_ERROR", { message: `An error occurred during hook matcher execution. Check the logs for more details.` });
|
|
206
|
-
}
|
|
207
|
-
if (matched) {
|
|
208
|
-
const hookSource = hooksSourceWeakMap.get(hook.handler) ?? "unknown";
|
|
209
|
-
const route = endpoint.path ?? "/:virtual";
|
|
210
|
-
const result = await withSpan(`hook before ${route} ${hookSource}`, {
|
|
211
|
-
[ATTR_HOOK_TYPE]: "before",
|
|
212
|
-
[ATTR_HTTP_ROUTE]: route,
|
|
213
|
-
[ATTR_CONTEXT]: hookSource,
|
|
214
|
-
[ATTR_OPERATION_ID]: operationId
|
|
215
|
-
}, () => hook.handler({
|
|
216
|
-
...context,
|
|
217
|
-
returnHeaders: false
|
|
218
|
-
})).catch((e) => {
|
|
219
|
-
if (isAPIError(e) && shouldPublishLog(context.context.logger.level, "debug")) e.stack = e.errorStack;
|
|
220
|
-
throw e;
|
|
221
|
-
});
|
|
222
|
-
if (result && typeof result === "object") {
|
|
223
|
-
if ("context" in result && typeof result.context === "object") {
|
|
224
|
-
const { headers, ...rest } = result.context;
|
|
225
|
-
if (headers instanceof Headers) if (modifiedContext.headers) headers.forEach((value, key) => {
|
|
226
|
-
modifiedContext.headers?.set(key, value);
|
|
227
|
-
});
|
|
228
|
-
else modifiedContext.headers = headers;
|
|
229
|
-
modifiedContext = defuReplaceArrays(rest, modifiedContext);
|
|
230
|
-
continue;
|
|
231
|
-
}
|
|
232
|
-
return result;
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
return { context: modifiedContext };
|
|
237
|
-
}
|
|
238
|
-
async function runAfterHooks(context, hooks, endpoint, operationId) {
|
|
239
|
-
for (const hook of hooks) if (hook.matcher(context)) {
|
|
240
|
-
const hookSource = hooksSourceWeakMap.get(hook.handler) ?? "unknown";
|
|
241
|
-
const route = endpoint.path ?? "/:virtual";
|
|
242
|
-
const result = await withSpan(`hook after ${route} ${hookSource}`, {
|
|
243
|
-
[ATTR_HOOK_TYPE]: "after",
|
|
244
|
-
[ATTR_HTTP_ROUTE]: route,
|
|
245
|
-
[ATTR_CONTEXT]: hookSource,
|
|
246
|
-
[ATTR_OPERATION_ID]: operationId
|
|
247
|
-
}, () => hook.handler(context)).catch((e) => {
|
|
248
|
-
if (isAPIError(e)) {
|
|
249
|
-
const headers = e[kAPIErrorHeaderSymbol];
|
|
250
|
-
if (shouldPublishLog(context.context.logger.level, "debug")) e.stack = e.errorStack;
|
|
251
|
-
return {
|
|
252
|
-
response: e,
|
|
253
|
-
headers: headers ? headers : e.headers ? new Headers(e.headers) : null
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
|
-
throw e;
|
|
257
|
-
});
|
|
258
|
-
if (result.headers) result.headers.forEach((value, key) => {
|
|
259
|
-
if (!context.context.responseHeaders) context.context.responseHeaders = new Headers({ [key]: value });
|
|
260
|
-
else if (key.toLowerCase() === "set-cookie") context.context.responseHeaders.append(key, value);
|
|
261
|
-
else context.context.responseHeaders.set(key, value);
|
|
262
|
-
});
|
|
263
|
-
if (result.response) context.context.returned = result.response;
|
|
264
|
-
}
|
|
265
|
-
return {
|
|
266
|
-
response: context.context.returned,
|
|
267
|
-
headers: context.context.responseHeaders
|
|
268
|
-
};
|
|
269
|
-
}
|
|
270
|
-
function getHooks(authContext) {
|
|
271
|
-
const plugins = authContext.options.plugins || [];
|
|
272
|
-
const beforeHooks = [];
|
|
273
|
-
const afterHooks = [];
|
|
274
|
-
const beforeHookHandler = authContext.options.hooks?.before;
|
|
275
|
-
if (beforeHookHandler) {
|
|
276
|
-
hooksSourceWeakMap.set(beforeHookHandler, "user");
|
|
277
|
-
beforeHooks.push({
|
|
278
|
-
matcher: () => true,
|
|
279
|
-
handler: beforeHookHandler
|
|
280
|
-
});
|
|
281
|
-
}
|
|
282
|
-
const afterHookHandler = authContext.options.hooks?.after;
|
|
283
|
-
if (afterHookHandler) {
|
|
284
|
-
hooksSourceWeakMap.set(afterHookHandler, "user");
|
|
285
|
-
afterHooks.push({
|
|
286
|
-
matcher: () => true,
|
|
287
|
-
handler: afterHookHandler
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
const pluginBeforeHooks = plugins.flatMap((plugin) => (plugin.hooks?.before ?? []).map((h) => {
|
|
291
|
-
hooksSourceWeakMap.set(h.handler, `plugin:${plugin.id}`);
|
|
292
|
-
return h;
|
|
293
|
-
}));
|
|
294
|
-
const pluginAfterHooks = plugins.flatMap((plugin) => (plugin.hooks?.after ?? []).map((h) => {
|
|
295
|
-
hooksSourceWeakMap.set(h.handler, `plugin:${plugin.id}`);
|
|
296
|
-
return h;
|
|
297
|
-
}));
|
|
298
|
-
/**
|
|
299
|
-
* Add plugin added hooks at last
|
|
300
|
-
*/
|
|
301
|
-
if (pluginBeforeHooks.length) beforeHooks.push(...pluginBeforeHooks);
|
|
302
|
-
if (pluginAfterHooks.length) afterHooks.push(...pluginAfterHooks);
|
|
303
|
-
return {
|
|
304
|
-
beforeHooks,
|
|
305
|
-
afterHooks
|
|
306
|
-
};
|
|
307
|
-
}
|
|
308
57
|
//#endregion
|
|
309
58
|
export { toAuthEndpoints };
|
|
@@ -108,14 +108,14 @@ function toCookieOptions(attributes) {
|
|
|
108
108
|
*
|
|
109
109
|
* @see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6
|
|
110
110
|
*/
|
|
111
|
-
const cookieNameRegex = /^[\
|
|
111
|
+
const cookieNameRegex = /^[\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E\x5F\x60\x61-\x7A\x7C\x7E]+$/;
|
|
112
112
|
/**
|
|
113
113
|
* Cookie-value char set per RFC 6265 §4.1.1, plus space and comma.
|
|
114
114
|
*
|
|
115
115
|
* @see https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.1
|
|
116
116
|
* @see https://github.com/golang/go/issues/7243
|
|
117
117
|
*/
|
|
118
|
-
const cookieValueRegex = /^[
|
|
118
|
+
const cookieValueRegex = /^[\x20\x21\x23-\x3A\x3C-\x5B\x5D-\x7E]*$/;
|
|
119
119
|
/**
|
|
120
120
|
* Strip surrounding double-quotes per RFC 6265 §4.1.1 quoted-string form.
|
|
121
121
|
*
|
package/dist/package.mjs
CHANGED
|
@@ -72,6 +72,7 @@ const setRole = (opts) => createAuthEndpoint("/admin/set-role", {
|
|
|
72
72
|
const inputRoles = Array.isArray(ctx.body.role) ? ctx.body.role : [ctx.body.role];
|
|
73
73
|
for (const role of inputRoles) if (!roles[role]) throw APIError.from("BAD_REQUEST", ADMIN_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_SET_NON_EXISTENT_VALUE);
|
|
74
74
|
}
|
|
75
|
+
if (!await ctx.context.internalAdapter.findUserById(ctx.body.userId)) throw APIError.from("NOT_FOUND", BASE_ERROR_CODES.USER_NOT_FOUND);
|
|
75
76
|
const updatedUser = await ctx.context.internalAdapter.updateUser(ctx.body.userId, { role: parseRoles(ctx.body.role) });
|
|
76
77
|
return ctx.json({ user: parseUserOutput(ctx.context.options, updatedUser) });
|
|
77
78
|
});
|
|
@@ -234,6 +235,7 @@ const adminUpdateUser = (opts) => createAuthEndpoint("/admin/update-user", {
|
|
|
234
235
|
}
|
|
235
236
|
ctx.body.data.role = parseRoles(inputRoles);
|
|
236
237
|
}
|
|
238
|
+
if (!await ctx.context.internalAdapter.findUserById(ctx.body.userId)) throw APIError.from("NOT_FOUND", BASE_ERROR_CODES.USER_NOT_FOUND);
|
|
237
239
|
const updatedUser = await ctx.context.internalAdapter.updateUser(ctx.body.userId, ctx.body.data);
|
|
238
240
|
return ctx.json(parseUserOutput(ctx.context.options, updatedUser));
|
|
239
241
|
});
|
|
@@ -402,6 +404,7 @@ const unbanUser = (opts) => createAuthEndpoint("/admin/unban-user", {
|
|
|
402
404
|
options: opts,
|
|
403
405
|
permissions: { user: ["ban"] }
|
|
404
406
|
})) throw APIError.from("FORBIDDEN", ADMIN_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_BAN_USERS);
|
|
407
|
+
if (!await ctx.context.internalAdapter.findUserById(ctx.body.userId)) throw APIError.from("NOT_FOUND", BASE_ERROR_CODES.USER_NOT_FOUND);
|
|
405
408
|
const user = await ctx.context.internalAdapter.updateUser(ctx.body.userId, {
|
|
406
409
|
banned: false,
|
|
407
410
|
banExpires: null,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "better-auth",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.15",
|
|
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.15",
|
|
493
|
+
"@better-auth/drizzle-adapter": "1.6.15",
|
|
494
|
+
"@better-auth/kysely-adapter": "1.6.15",
|
|
495
|
+
"@better-auth/memory-adapter": "1.6.15",
|
|
496
|
+
"@better-auth/mongo-adapter": "1.6.15",
|
|
497
|
+
"@better-auth/prisma-adapter": "1.6.15",
|
|
498
|
+
"@better-auth/telemetry": "1.6.15"
|
|
499
499
|
},
|
|
500
500
|
"devDependencies": {
|
|
501
501
|
"@lynx-js/react": "^0.116.3",
|