better-auth
Version:
The most comprehensive authentication framework for TypeScript.
179 lines (177 loc) • 6.46 kB
JavaScript
import { hasRequestState, runWithEndpointContext, runWithRequestState } from "@better-auth/core/context";
import { shouldPublishLog } from "@better-auth/core/env";
import { APIError, toResponse } from "better-call";
import { createDefu } from "defu";
//#region src/api/to-auth-endpoints.ts
const defuReplaceArrays = createDefu((obj, key, value) => {
if (Array.isArray(obj[key]) && Array.isArray(value)) {
obj[key] = value;
return true;
}
});
function toAuthEndpoints(endpoints, ctx) {
const api = {};
for (const [key, endpoint] of Object.entries(endpoints)) {
api[key] = async (context) => {
const run = async () => {
const authContext = await ctx;
let internalContext = {
...context,
context: {
...authContext,
returned: void 0,
responseHeaders: void 0,
session: null
},
path: endpoint.path,
headers: context?.headers ? new Headers(context?.headers) : void 0
};
return runWithEndpointContext(internalContext, async () => {
const { beforeHooks, afterHooks } = getHooks(authContext);
const before = await runBeforeHooks(internalContext, beforeHooks);
/**
* If `before.context` is returned, it should
* get merged with the original context
*/
if ("context" in before && before.context && typeof before.context === "object") {
const { headers, ...rest } = before.context;
/**
* Headers should be merged differently
* so the hook doesn't override the whole
* header
*/
if (headers) headers.forEach((value, key$1) => {
internalContext.headers.set(key$1, value);
});
internalContext = defuReplaceArrays(rest, internalContext);
} else if (before) return context?.asResponse ? toResponse(before, { headers: context?.headers }) : context?.returnHeaders ? {
headers: context?.headers,
response: before
} : before;
internalContext.asResponse = false;
internalContext.returnHeaders = true;
internalContext.returnStatus = true;
const result = await runWithEndpointContext(internalContext, () => endpoint(internalContext)).catch((e) => {
if (e instanceof APIError)
/**
* API Errors from response are caught
* and returned to hooks
*/
return {
response: e,
status: e.statusCode,
headers: e.headers ? new Headers(e.headers) : null
};
throw e;
});
if (result && result instanceof Response) return result;
internalContext.context.returned = result.response;
internalContext.context.responseHeaders = result.headers;
const after = await runAfterHooks(internalContext, afterHooks);
if (after.response) result.response = after.response;
if (result.response instanceof APIError && shouldPublishLog(authContext.logger.level, "debug")) result.response.stack = result.response.errorStack;
if (result.response instanceof APIError && !context?.asResponse) throw result.response;
return context?.asResponse ? toResponse(result.response, {
headers: result.headers,
status: result.status
}) : context?.returnHeaders ? context?.returnStatus ? {
headers: result.headers,
response: result.response,
status: result.status
} : {
headers: result.headers,
response: result.response
} : context?.returnStatus ? {
response: result.response,
status: result.status
} : result.response;
});
};
if (await hasRequestState()) return run();
else return runWithRequestState(/* @__PURE__ */ new WeakMap(), run);
};
api[key].path = endpoint.path;
api[key].options = endpoint.options;
}
return api;
}
async function runBeforeHooks(context, hooks) {
let modifiedContext = {};
for (const hook of hooks) if (hook.matcher(context)) {
const result = await hook.handler({
...context,
returnHeaders: false
}).catch((e) => {
if (e instanceof APIError && shouldPublishLog(context.context.logger.level, "debug")) e.stack = e.errorStack;
throw e;
});
if (result && typeof result === "object") {
if ("context" in result && typeof result.context === "object") {
const { headers, ...rest } = result.context;
if (headers instanceof Headers) if (modifiedContext.headers) headers.forEach((value, key) => {
modifiedContext.headers?.set(key, value);
});
else modifiedContext.headers = headers;
modifiedContext = defuReplaceArrays(rest, modifiedContext);
continue;
}
return result;
}
}
return { context: modifiedContext };
}
async function runAfterHooks(context, hooks) {
for (const hook of hooks) if (hook.matcher(context)) {
const result = await hook.handler(context).catch((e) => {
if (e instanceof APIError) {
if (shouldPublishLog(context.context.logger.level, "debug")) e.stack = e.errorStack;
return {
response: e,
headers: e.headers ? new Headers(e.headers) : null
};
}
throw e;
});
if (result.headers) result.headers.forEach((value, key) => {
if (!context.context.responseHeaders) context.context.responseHeaders = new Headers({ [key]: value });
else if (key.toLowerCase() === "set-cookie") context.context.responseHeaders.append(key, value);
else context.context.responseHeaders.set(key, value);
});
if (result.response) context.context.returned = result.response;
}
return {
response: context.context.returned,
headers: context.context.responseHeaders
};
}
function getHooks(authContext) {
const plugins = authContext.options.plugins || [];
const beforeHooks = [];
const afterHooks = [];
if (authContext.options.hooks?.before) beforeHooks.push({
matcher: () => true,
handler: authContext.options.hooks.before
});
if (authContext.options.hooks?.after) afterHooks.push({
matcher: () => true,
handler: authContext.options.hooks.after
});
const pluginBeforeHooks = plugins.map((plugin) => {
if (plugin.hooks?.before) return plugin.hooks.before;
}).filter((plugin) => plugin !== void 0).flat();
const pluginAfterHooks = plugins.map((plugin) => {
if (plugin.hooks?.after) return plugin.hooks.after;
}).filter((plugin) => plugin !== void 0).flat();
/**
* Add plugin added hooks at last
*/
if (pluginBeforeHooks.length) beforeHooks.push(...pluginBeforeHooks);
if (pluginAfterHooks.length) afterHooks.push(...pluginAfterHooks);
return {
beforeHooks,
afterHooks
};
}
//#endregion
export { toAuthEndpoints };
//# sourceMappingURL=to-auth-endpoints.mjs.map