chore: move session to api

This commit is contained in:
Bereket Engida
2024-09-24 04:08:55 +03:00
parent 9098a20696
commit fb2b8731f6
16 changed files with 84 additions and 67 deletions

View File

@@ -39,11 +39,10 @@ export const auth = betterAuth({
}, },
}, },
plugins: [ plugins: [
rateLimiter({ // rateLimiter({
enabled: true, // enabled: true,
// max: 1000,
max: 1000, // }),
}),
organization({ organization({
async sendInvitationEmail(data) { async sendInvitationEmail(data) {
const res = await resend.emails.send({ const res = await resend.emails.send({

View File

@@ -81,7 +81,7 @@
"@simplewebauthn/browser": "^10.0.0", "@simplewebauthn/browser": "^10.0.0",
"@simplewebauthn/server": "^10.0.1", "@simplewebauthn/server": "^10.0.1",
"arctic": "2.0.0-next.9", "arctic": "2.0.0-next.9",
"better-call": "0.2.3-beta.3", "better-call": "0.2.3-beta.8",
"better-sqlite3": "^11.1.2", "better-sqlite3": "^11.1.2",
"c12": "^1.11.2", "c12": "^1.11.2",
"chalk": "^5.3.0", "chalk": "^5.3.0",

View File

@@ -21,8 +21,7 @@ export const createAuthMiddleware = createMiddlewareCreator({
use: [ use: [
optionsMiddleware, optionsMiddleware,
/** /**
* This of for hooks. to tell ts there will a * Only use for post hooks
* return response object
*/ */
createMiddleware(async () => { createMiddleware(async () => {
return {} as { return {} as {

View File

@@ -105,25 +105,29 @@ export function getEndpoints<
let api: Record<string, any> = {}; let api: Record<string, any> = {};
for (const [key, value] of Object.entries(endpoints)) { for (const [key, value] of Object.entries(endpoints)) {
api[key] = async (context: any) => { api[key] = async (context: any) => {
for (const plugin of ctx.options.plugins || []) { // for (const plugin of ctx.options.plugins || []) {
if (plugin.hooks?.before) { // if (plugin.hooks?.before) {
for (const hook of plugin.hooks.before) { // for (const hook of plugin.hooks.before) {
const match = hook.matcher({ // const match = hook.matcher({
...context, // ...context,
...value, // ...value,
}); // });
if (match) { // if (match) {
const hookRes = await hook.handler(context); // const hookRes = await hook.handler(context);
if (hookRes && "context" in hookRes) { // if (hookRes && "context" in hookRes) {
context = { // context = {
...context, // ...context,
...hookRes.context, // ...hookRes.context,
...value, // ...value,
}; // };
} // }
} // }
} // }
} // }
// }
if (context.path === "/user/update") {
const res = await api[key].options.use[1](context);
console.log(res);
} }
/** /**
* TODO: move this to respond a json response * TODO: move this to respond a json response
@@ -187,7 +191,6 @@ export const router = <C extends AuthContext, Option extends BetterAuthOptions>(
}, },
...middlewares, ...middlewares,
], ],
onError(e) { onError(e) {
if (options.disableLog !== true) { if (options.disableLog !== true) {
if (e instanceof APIError) { if (e instanceof APIError) {
@@ -196,7 +199,7 @@ export const router = <C extends AuthContext, Option extends BetterAuthOptions>(
if (typeof e === "object" && e !== null && "message" in e) { if (typeof e === "object" && e !== null && "message" in e) {
const errorMessage = e.message as string; const errorMessage = e.message as string;
if (!errorMessage || typeof errorMessage !== "string") { if (!errorMessage || typeof errorMessage !== "string") {
logger.warn(e); logger.error(e);
return; return;
} }
if (errorMessage.includes("no such table")) { if (errorMessage.includes("no such table")) {
@@ -224,10 +227,10 @@ export const router = <C extends AuthContext, Option extends BetterAuthOptions>(
)} to create the tables. There are missing tables in your MySQL database.`, )} to create the tables. There are missing tables in your MySQL database.`,
); );
} else { } else {
logger.warn(e); logger.error(e);
} }
} else { } else {
logger.warn(e); logger.error(e);
} }
} }
} }

View File

@@ -1,2 +1 @@
export * from "./csrf"; export * from "./csrf";
export * from "./session";

View File

@@ -1,13 +0,0 @@
import { APIError } from "better-call";
import { createAuthMiddleware } from "../call";
import { getSessionFromCtx } from "../routes";
export const sessionMiddleware = createAuthMiddleware(async (ctx) => {
const session = await getSessionFromCtx(ctx);
if (!session?.session) {
throw new APIError("UNAUTHORIZED");
}
return {
session,
};
});

View File

@@ -1,8 +1,7 @@
import type { Context, InferUse } from "better-call"; import { APIError, type Context, type InferUse } from "better-call";
import { createAuthEndpoint } from "../call"; import { createAuthEndpoint, createAuthMiddleware } from "../call";
import { getDate } from "../../utils/date"; import { getDate } from "../../utils/date";
import { deleteSessionCookie, setSessionCookie } from "../../utils/cookies"; import { deleteSessionCookie, setSessionCookie } from "../../utils/cookies";
import { sessionMiddleware } from "../middlewares/session";
import type { Session, User } from "../../adapters/schema"; import type { Session, User } from "../../adapters/schema";
import { z } from "zod"; import { z } from "zod";
import { getIp } from "../../utils/get-request-ip"; import { getIp } from "../../utils/get-request-ip";
@@ -67,14 +66,18 @@ export const getSession = <Option extends BetterAuthOptions>() =>
const cachedSession = sessionCache.get(key); const cachedSession = sessionCache.get(key);
if (cachedSession) { if (cachedSession) {
if (cachedSession.expiresAt > Date.now()) { if (cachedSession.expiresAt > Date.now()) {
return ctx.json(cachedSession.data); return ctx.json(
cachedSession.data as unknown as {
session: Prettify<InferSession<Option>>;
user: Prettify<InferUser<Option>>;
},
);
} }
sessionCache.delete(key); sessionCache.delete(key);
} }
const session = const session =
await ctx.context.internalAdapter.findSession(sessionCookieToken); await ctx.context.internalAdapter.findSession(sessionCookieToken);
if (!session || session.session.expiresAt < new Date()) { if (!session || session.session.expiresAt < new Date()) {
deleteSessionCookie(ctx); deleteSessionCookie(ctx);
if (session) { if (session) {
@@ -95,10 +98,15 @@ export const getSession = <Option extends BetterAuthOptions>() =>
* We don't need to update the session if the user doesn't want to be remembered * We don't need to update the session if the user doesn't want to be remembered
*/ */
if (dontRememberMe) { if (dontRememberMe) {
return ctx.json(session); return ctx.json(
session as unknown as {
session: Prettify<InferSession<Option>>;
user: Prettify<InferUser<Option>>;
},
);
} }
const expiresIn = ctx.context.session.expiresIn; const expiresIn = ctx.context.sessionConfig.expiresIn;
const updateAge = ctx.context.session.updateAge; const updateAge = ctx.context.sessionConfig.updateAge;
/** /**
* Calculate last updated date to throttle write updates to database * Calculate last updated date to throttle write updates to database
* Formula: ({expiry date} - sessionMaxAge) + sessionUpdateAge * Formula: ({expiry date} - sessionMaxAge) + sessionUpdateAge
@@ -119,7 +127,7 @@ export const getSession = <Option extends BetterAuthOptions>() =>
await ctx.context.internalAdapter.updateSession( await ctx.context.internalAdapter.updateSession(
session.session.id, session.session.id,
{ {
expiresAt: getDate(ctx.context.session.expiresIn, true), expiresAt: getDate(ctx.context.sessionConfig.expiresIn, true),
}, },
); );
if (!updatedSession) { if (!updatedSession) {
@@ -167,6 +175,16 @@ export const getSessionFromCtx = async (ctx: Context<any, any>) => {
return session; return session;
}; };
export const sessionMiddleware = createAuthMiddleware(async (ctx) => {
const session = await getSessionFromCtx(ctx);
if (!session?.session) {
throw new APIError("UNAUTHORIZED");
}
return {
session,
};
});
/** /**
* user active sessions list * user active sessions list
*/ */
@@ -212,6 +230,13 @@ export const revokeSession = createAuthEndpoint(
}, },
async (ctx) => { async (ctx) => {
const id = ctx.body.id; const id = ctx.body.id;
const findSession = await ctx.context.internalAdapter.findSession(id);
if (!findSession) {
return ctx.json(null, { status: 400 });
}
if (findSession.session.userId !== ctx.context.session.user.id) {
return ctx.json(null, { status: 403 });
}
try { try {
await ctx.context.internalAdapter.deleteSession(id); await ctx.context.internalAdapter.deleteSession(id);
} catch (error) { } catch (error) {

View File

@@ -1,8 +1,9 @@
import { z } from "zod"; import { z } from "zod";
import { createAuthEndpoint } from "../call"; import { createAuthEndpoint, createAuthMiddleware } from "../call";
import { sessionMiddleware } from "../middlewares/session";
import { alphabet, generateRandomString } from "oslo/crypto"; import { alphabet, generateRandomString } from "oslo/crypto";
import { setSessionCookie } from "../../utils/cookies"; import { setSessionCookie } from "../../utils/cookies";
import { getSessionFromCtx, sessionMiddleware } from "./session";
import { APIError } from "better-call";
export const updateUser = createAuthEndpoint( export const updateUser = createAuthEndpoint(
"/user/update", "/user/update",
@@ -17,6 +18,9 @@ export const updateUser = createAuthEndpoint(
async (ctx) => { async (ctx) => {
const { name, image } = ctx.body; const { name, image } = ctx.body;
const session = ctx.context.session; const session = ctx.context.session;
if (!image && !name) {
return ctx.json(session.user);
}
const user = await ctx.context.internalAdapter.updateUserByEmail( const user = await ctx.context.internalAdapter.updateUserByEmail(
session.user.email, session.user.email,
{ {

View File

@@ -51,7 +51,7 @@ export const init = (options: BetterAuthOptions) => {
}, },
tables, tables,
baseURL: baseURL || "", baseURL: baseURL || "",
session: { sessionConfig: {
updateAge: options.session?.updateAge || 24 * 60 * 60, // 24 hours updateAge: options.session?.updateAge || 24 * 60 * 60, // 24 hours
expiresIn: options.session?.expiresIn || 60 * 60 * 24 * 7, // 7 days expiresIn: options.session?.expiresIn || 60 * 60 * 24 * 7, // 7 days
}, },
@@ -83,7 +83,7 @@ export type AuthContext = {
internalAdapter: ReturnType<typeof createInternalAdapter>; internalAdapter: ReturnType<typeof createInternalAdapter>;
createAuthCookie: ReturnType<typeof createCookieGetter>; createAuthCookie: ReturnType<typeof createCookieGetter>;
secret: string; secret: string;
session: { sessionConfig: {
updateAge: number; updateAge: number;
expiresIn: number; expiresIn: number;
}; };

View File

@@ -1,7 +1,7 @@
import { APIError, type Context, createEndpointCreator } from "better-call"; import { APIError, type Context, createEndpointCreator } from "better-call";
import type { Session, User } from "../../adapters/schema"; import type { Session, User } from "../../adapters/schema";
import { createAuthMiddleware, optionsMiddleware } from "../../api/call"; import { createAuthMiddleware, optionsMiddleware } from "../../api/call";
import { sessionMiddleware } from "../../api/middlewares/session"; import { sessionMiddleware } from "../../api";
import type { Role, defaultRoles } from "./access"; import type { Role, defaultRoles } from "./access";
import type { OrganizationOptions } from "./organization"; import type { OrganizationOptions } from "./organization";

View File

@@ -14,7 +14,7 @@ import { APIError } from "better-call";
import { alphabet, generateRandomString } from "oslo/crypto"; import { alphabet, generateRandomString } from "oslo/crypto";
import { z } from "zod"; import { z } from "zod";
import { createAuthEndpoint } from "../../api/call"; import { createAuthEndpoint } from "../../api/call";
import { sessionMiddleware } from "../../api/middlewares/session"; import { sessionMiddleware } from "../../api";
import { getSessionFromCtx } from "../../api/routes"; import { getSessionFromCtx } from "../../api/routes";
import type { BetterAuthPlugin } from "../../types/plugins"; import type { BetterAuthPlugin } from "../../types/plugins";
import { setSessionCookie } from "../../utils/cookies"; import { setSessionCookie } from "../../utils/cookies";

View File

@@ -1,7 +1,7 @@
import { alphabet, generateRandomString } from "oslo/crypto"; import { alphabet, generateRandomString } from "oslo/crypto";
import { z } from "zod"; import { z } from "zod";
import { createAuthEndpoint } from "../../../api/call"; import { createAuthEndpoint } from "../../../api/call";
import { sessionMiddleware } from "../../../api/middlewares/session"; import { sessionMiddleware } from "../../../api";
import { symmetricDecrypt, symmetricEncrypt } from "../../../crypto"; import { symmetricDecrypt, symmetricEncrypt } from "../../../crypto";
import { verifyTwoFactorMiddleware } from "../verify-middleware"; import { verifyTwoFactorMiddleware } from "../verify-middleware";
import type { TwoFactorProvider, UserWithTwoFactor } from "../types"; import type { TwoFactorProvider, UserWithTwoFactor } from "../types";

View File

@@ -1,7 +1,7 @@
import { alphabet, generateRandomString } from "oslo/crypto"; import { alphabet, generateRandomString } from "oslo/crypto";
import { z } from "zod"; import { z } from "zod";
import { createAuthEndpoint, createAuthMiddleware } from "../../api/call"; import { createAuthEndpoint, createAuthMiddleware } from "../../api/call";
import { sessionMiddleware } from "../../api/middlewares/session"; import { sessionMiddleware } from "../../api";
import { hs256, symmetricEncrypt } from "../../crypto"; import { hs256, symmetricEncrypt } from "../../crypto";
import type { BetterAuthPlugin } from "../../types/plugins"; import type { BetterAuthPlugin } from "../../types/plugins";
import { backupCode2fa, generateBackupCodes } from "./backup-codes"; import { backupCode2fa, generateBackupCodes } from "./backup-codes";

View File

@@ -3,7 +3,7 @@ import { TimeSpan } from "oslo";
import { TOTPController, createTOTPKeyURI } from "oslo/otp"; import { TOTPController, createTOTPKeyURI } from "oslo/otp";
import { z } from "zod"; import { z } from "zod";
import { createAuthEndpoint } from "../../../api/call"; import { createAuthEndpoint } from "../../../api/call";
import { sessionMiddleware } from "../../../api/middlewares/session"; import { sessionMiddleware } from "../../../api";
import { symmetricDecrypt } from "../../../crypto"; import { symmetricDecrypt } from "../../../crypto";
import type { BackupCodeOptions } from "../backup-codes"; import type { BackupCodeOptions } from "../backup-codes";
import { verifyTwoFactorMiddleware } from "../verify-middleware"; import { verifyTwoFactorMiddleware } from "../verify-middleware";

View File

@@ -25,6 +25,7 @@ export default defineConfig((env) => {
sourcemap: isBuild, sourcemap: isBuild,
format: ["esm"], format: ["esm"],
dts: true, dts: true,
splitting: false,
minify: isBuild, minify: isBuild,
minifyWhitespace: isBuild, minifyWhitespace: isBuild,
minifyIdentifiers: isBuild, minifyIdentifiers: isBuild,

8
pnpm-lock.yaml generated
View File

@@ -767,8 +767,8 @@ importers:
specifier: 2.0.0-next.9 specifier: 2.0.0-next.9
version: 2.0.0-next.9 version: 2.0.0-next.9
better-call: better-call:
specifier: 0.2.3-beta.3 specifier: 0.2.3-beta.8
version: 0.2.3-beta.3 version: 0.2.3-beta.8
better-sqlite3: better-sqlite3:
specifier: ^11.1.2 specifier: ^11.1.2
version: 11.3.0 version: 11.3.0
@@ -10102,8 +10102,8 @@ packages:
typescript: 5.6.2 typescript: 5.6.2
dev: false dev: false
/better-call@0.2.3-beta.3: /better-call@0.2.3-beta.8:
resolution: {integrity: sha512-1W0HB1aJ1adhbkVAi5gBQE/0uhdz+Y1Nrg3I0xZDFQx1R6vocBq7R+bVDrI5eNX+HmbzrXdY6O+Q/CfhhczJcg==} resolution: {integrity: sha512-cFD9x116voPbe9LSJFPIihY+2zDdhnEiVijQb8YWmXQXdC3PEZ7+lWyptJHBdtmLbao7xPt/JesH96/tm/o3wQ==}
dependencies: dependencies:
'@better-fetch/fetch': 1.1.8 '@better-fetch/fetch': 1.1.8
'@types/set-cookie-parser': 2.4.10 '@types/set-cookie-parser': 2.4.10