From 99d6cababa36f58d4a190928567399659d8cfc41 Mon Sep 17 00:00:00 2001 From: Milo Schwartz Date: Sun, 13 Oct 2024 17:13:47 -0400 Subject: [PATCH] remove lucia --- package.json | 4 +- scripts/hydrate.ts | 1 - server/auth/2fa.ts | 4 +- server/auth/actions.ts | 5 +- server/auth/index.ts | 149 +++++++++++++----- server/auth/verifySession.ts | 6 +- server/db/schema.ts | 39 ++--- server/middlewares/verifySession.ts | 2 +- server/middlewares/verifyUser.ts | 2 +- server/routers/auth/changePassword.ts | 8 +- server/routers/auth/disable2fa.ts | 6 +- server/routers/auth/getUserOrgs.ts | 4 +- server/routers/auth/login.ts | 19 ++- server/routers/auth/logout.ts | 15 +- .../auth/requestEmailVerificationCode.ts | 2 +- server/routers/auth/requestPasswordReset.ts | 9 +- server/routers/auth/requestTotpSecret.ts | 2 +- server/routers/auth/resetPassword.ts | 10 +- server/routers/auth/signup.ts | 21 +-- server/routers/auth/verifyEmail.ts | 6 +- server/routers/auth/verifyOrgAccess.ts | 2 +- server/routers/auth/verifyResourceAccess.ts | 4 +- server/routers/auth/verifyRoleAccess.ts | 4 +- server/routers/auth/verifySiteAccess.ts | 4 +- server/routers/auth/verifySuperuser.ts | 8 +- server/routers/auth/verifyTargetAccess.ts | 4 +- server/routers/auth/verifyTotp.ts | 6 +- server/routers/auth/verifyUserAccess.ts | 6 +- server/routers/badger/verifyUser.ts | 5 +- server/routers/resource/createResource.ts | 2 +- server/routers/resource/listResources.ts | 4 +- server/routers/site/createSite.ts | 12 +- server/routers/site/listSites.ts | 4 +- server/routers/user/addUserAction.ts | 4 +- server/routers/user/addUserOrg.ts | 4 +- server/routers/user/getUser.ts | 4 +- server/routers/user/listUsers.ts | 6 +- server/types/Auth.ts | 2 +- src/app/page.tsx | 2 - 39 files changed, 234 insertions(+), 167 deletions(-) diff --git a/package.json b/package.json index 9e2a2087..cb61711b 100644 --- a/package.json +++ b/package.json @@ -16,8 +16,9 @@ "dependencies": { "@esbuild-plugins/tsconfig-paths": "0.1.2", "@hookform/resolvers": "3.9.0", - "@lucia-auth/adapter-drizzle": "1.1.0", "@node-rs/argon2": "1.8.3", + "@oslojs/crypto": "1.0.1", + "@oslojs/encoding": "1.1.0", "@radix-ui/react-icons": "1.3.0", "@radix-ui/react-label": "2.1.0", "@radix-ui/react-slot": "1.1.0", @@ -40,7 +41,6 @@ "http-errors": "2.0.0", "input-otp": "1.2.4", "js-yaml": "4.1.0", - "lucia": "3.2.0", "lucide-react": "0.447.0", "moment": "2.30.1", "next": "14.2.13", diff --git a/scripts/hydrate.ts b/scripts/hydrate.ts index defdf73e..68b7e026 100644 --- a/scripts/hydrate.ts +++ b/scripts/hydrate.ts @@ -1,6 +1,5 @@ import { orgs, - users, sites, resources, exitNodes, diff --git a/server/auth/2fa.ts b/server/auth/2fa.ts index 2483656b..cb215ddd 100644 --- a/server/auth/2fa.ts +++ b/server/auth/2fa.ts @@ -45,14 +45,14 @@ export async function verifyBackUpCode( parallelism: 1, }); if (validCode) { - validId = hashedCode.id; + validId = hashedCode.codeId; } } if (validId) { await db .delete(twoFactorBackupCodes) - .where(eq(twoFactorBackupCodes.id, validId)); + .where(eq(twoFactorBackupCodes.codeId, validId)); } return validId ? true : false; diff --git a/server/auth/actions.ts b/server/auth/actions.ts index fbb17f7e..56fbb38f 100644 --- a/server/auth/actions.ts +++ b/server/auth/actions.ts @@ -55,8 +55,7 @@ export enum ActionsEnum { } export async function checkUserActionPermission(actionId: string, req: Request): Promise { - const userId = req.user?.id; - + const userId = req.user?.userId; if (!userId) { throw createHttpError(HttpCode.UNAUTHORIZED, 'User not authenticated'); } @@ -116,4 +115,4 @@ export async function checkUserActionPermission(actionId: string, req: Request): console.error('Error checking user action permission:', error); throw createHttpError(HttpCode.INTERNAL_SERVER_ERROR, 'Error checking action permission'); } -} \ No newline at end of file +} diff --git a/server/auth/index.ts b/server/auth/index.ts index b6886f82..7692f9f5 100644 --- a/server/auth/index.ts +++ b/server/auth/index.ts @@ -1,51 +1,122 @@ -export * from "./unauthorizedResponse"; export * from "./verifySession"; +export * from "./unauthorizedResponse"; -import { Lucia, TimeSpan } from "lucia"; -import { DrizzleSQLiteAdapter } from "@lucia-auth/adapter-drizzle"; +import { + encodeBase32LowerCaseNoPadding, + encodeHexLowerCase, +} from "@oslojs/encoding"; +import { sha256 } from "@oslojs/crypto/sha2"; +import { Session, sessions, User, users } from "@server/db/schema"; import db from "@server/db"; -import { sessions, users } from "@server/db/schema"; +import { eq } from "drizzle-orm"; import config from "@server/config"; +import type { RandomReader } from "@oslojs/crypto/random"; +import { generateRandomString } from "@oslojs/crypto/random"; -const adapter = new DrizzleSQLiteAdapter(db, sessions, users); +export const SESSION_COOKIE_NAME = "session"; +export const SESSION_COOKIE_EXPIRES = 1000 * 60 * 60 * 24 * 30; +export const SECURE_COOKIES = config.server.secure_cookies; +export const COOKIE_DOMAIN = + "." + new URL(config.app.base_url).hostname.split(".").slice(-2).join("."); -export const lucia = new Lucia(adapter, { - getUserAttributes: (attributes) => { - return { - email: attributes.email, - twoFactorEnabled: attributes.twoFactorEnabled, - twoFactorSecret: attributes.twoFactorSecret, - emailVerified: attributes.emailVerified, - dateCreated: attributes.dateCreated, - }; - }, - sessionCookie: { - name: "session", - expires: false, - attributes: { - sameSite: "strict", - secure: config.server.secure_cookies || false, - domain: - "." + new URL(config.app.base_url).hostname.split(".").slice(-2).join("."), - }, - }, - sessionExpiresIn: new TimeSpan(2, "w"), -}); +export function generateSessionToken(): string { + const bytes = new Uint8Array(20); + crypto.getRandomValues(bytes); + const token = encodeBase32LowerCaseNoPadding(bytes); + return token; +} -export default lucia; +export async function createSession( + token: string, + userId: string, +): Promise { + const sessionId = encodeHexLowerCase( + sha256(new TextEncoder().encode(token)), + ); + const session: Session = { + sessionId: sessionId, + userId, + expiresAt: new Date(Date.now() + SESSION_COOKIE_EXPIRES).getTime(), + }; + await db.insert(sessions).values(session); + return session; +} -declare module "lucia" { - interface Register { - Lucia: typeof lucia; - DatabaseUserAttributes: DatabaseUserAttributes; +export async function validateSessionToken( + token: string, +): Promise { + const sessionId = encodeHexLowerCase( + sha256(new TextEncoder().encode(token)), + ); + const result = await db + .select({ user: users, session: sessions }) + .from(sessions) + .innerJoin(users, eq(sessions.userId, users.userId)) + .where(eq(sessions.sessionId, sessionId)); + if (result.length < 1) { + return { session: null, user: null }; + } + const { user, session } = result[0]; + if (Date.now() >= session.expiresAt) { + await db + .delete(sessions) + .where(eq(sessions.sessionId, session.sessionId)); + return { session: null, user: null }; + } + if (Date.now() >= session.expiresAt - (SESSION_COOKIE_EXPIRES / 2)) { + session.expiresAt = new Date( + Date.now() + SESSION_COOKIE_EXPIRES, + ).getTime(); + await db + .update(sessions) + .set({ + expiresAt: session.expiresAt, + }) + .where(eq(sessions.sessionId, session.sessionId)); + } + return { session, user }; +} + +export async function invalidateSession(sessionId: string): Promise { + await db.delete(sessions).where(eq(sessions.sessionId, sessionId)); +} + +export async function invalidateAllSessions(userId: string): Promise { + await db.delete(sessions).where(eq(sessions.userId, userId)); +} + +export function serializeSessionCookie(token: string): string { + if (SECURE_COOKIES) { + return `${SESSION_COOKIE_NAME}=${token}; HttpOnly; SameSite=Lax; Max-Age=${SESSION_COOKIE_EXPIRES}; Path=/; Secure; Domain=${COOKIE_DOMAIN}`; + } else { + return `${SESSION_COOKIE_NAME}=${token}; HttpOnly; SameSite=Lax; Max-Age=${SESSION_COOKIE_EXPIRES}; Path=/; Domain=${COOKIE_DOMAIN}`; } } -interface DatabaseUserAttributes { - email: string; - passwordHash: string; - twoFactorEnabled: boolean; - twoFactorSecret?: string; - emailVerified: boolean; - dateCreated: string; +export function createBlankSessionTokenCookie(): string { + if (SECURE_COOKIES) { + return `${SESSION_COOKIE_NAME}=; HttpOnly; SameSite=Lax; Max-Age=0; Path=/; Secure; Domain=${COOKIE_DOMAIN}`; + } else { + return `${SESSION_COOKIE_NAME}=; HttpOnly; SameSite=Lax; Max-Age=0; Path=/; Domain=${COOKIE_DOMAIN}`; + } } + +const random: RandomReader = { + read(bytes: Uint8Array): void { + crypto.getRandomValues(bytes); + }, +}; + +export function generateId(length: number): string { + const alphabet = "abcdefghijklmnopqrstuvwxyz0123456789"; + return generateRandomString(random, alphabet, length); +} + +export function generateIdFromEntropySize(size: number): string { + const buffer = crypto.getRandomValues(new Uint8Array(size)); + return encodeBase32LowerCaseNoPadding(buffer); +} + +export type SessionValidationResult = + | { session: Session; user: User } + | { session: null; user: null }; diff --git a/server/auth/verifySession.ts b/server/auth/verifySession.ts index a371c336..fc9aecfa 100644 --- a/server/auth/verifySession.ts +++ b/server/auth/verifySession.ts @@ -1,9 +1,9 @@ import { Request } from "express"; -import { lucia } from "@server/auth"; +import { validateSessionToken, SESSION_COOKIE_NAME } from "@server/auth"; export async function verifySession(req: Request) { - const res = await lucia.validateSession( - req.cookies[lucia.sessionCookieName], + const res = await validateSessionToken( + req.cookies[SESSION_COOKIE_NAME] ?? "", ); return res; } diff --git a/server/db/schema.ts b/server/db/schema.ts index 8d566461..1d35da5c 100644 --- a/server/db/schema.ts +++ b/server/db/schema.ts @@ -1,14 +1,12 @@ import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core"; import { InferSelectModel } from "drizzle-orm"; -// Orgs table export const orgs = sqliteTable("orgs", { orgId: integer("orgId").primaryKey({ autoIncrement: true }), name: text("name").notNull(), domain: text("domain").notNull(), }); -// Sites table export const sites = sqliteTable("sites", { siteId: integer("siteId").primaryKey({ autoIncrement: true }), orgId: integer("orgId").references(() => orgs.orgId, { @@ -25,7 +23,6 @@ export const sites = sqliteTable("sites", { megabytesOut: integer("bytesOut"), }); -// Resources table export const resources = sqliteTable("resources", { resourceId: text("resourceId", { length: 2048 }).primaryKey(), siteId: integer("siteId").references(() => sites.siteId, { @@ -38,7 +35,6 @@ export const resources = sqliteTable("resources", { subdomain: text("subdomain"), }); -// Targets table export const targets = sqliteTable("targets", { targetId: integer("targetId").primaryKey({ autoIncrement: true }), resourceId: text("resourceId").references(() => resources.resourceId, { @@ -51,7 +47,6 @@ export const targets = sqliteTable("targets", { enabled: integer("enabled", { mode: "boolean" }).notNull().default(true), }); -// Exit Nodes table export const exitNodes = sqliteTable("exitNodes", { exitNodeId: integer("exitNodeId").primaryKey({ autoIncrement: true }), name: text("name").notNull(), @@ -60,7 +55,6 @@ export const exitNodes = sqliteTable("exitNodes", { listenPort: integer("listenPort"), }); -// Routes table export const routes = sqliteTable("routes", { routeId: integer("routeId").primaryKey({ autoIncrement: true }), exitNodeId: integer("exitNodeId").references(() => exitNodes.exitNodeId, { @@ -69,9 +63,8 @@ export const routes = sqliteTable("routes", { subnet: text("subnet").notNull(), }); -// Users table export const users = sqliteTable("user", { - id: text("id").primaryKey(), // has to be id not userId for lucia + userId: text("id").primaryKey(), email: text("email").notNull().unique(), passwordHash: text("passwordHash").notNull(), twoFactorEnabled: integer("twoFactorEnabled", { mode: "boolean" }) @@ -85,26 +78,25 @@ export const users = sqliteTable("user", { }); export const twoFactorBackupCodes = sqliteTable("twoFactorBackupCodes", { - id: integer("id").primaryKey({ autoIncrement: true }), + codeId: integer("id").primaryKey({ autoIncrement: true }), userId: text("userId") .notNull() - .references(() => users.id, { onDelete: "cascade" }), + .references(() => users.userId, { onDelete: "cascade" }), codeHash: text("codeHash").notNull(), }); -// Sessions table export const sessions = sqliteTable("session", { - id: text("id").primaryKey(), // has to be id not sessionId for lucia + sessionId: text("id").primaryKey(), userId: text("userId") .notNull() - .references(() => users.id, { onDelete: "cascade" }), + .references(() => users.userId, { onDelete: "cascade" }), expiresAt: integer("expiresAt").notNull(), }); export const userOrgs = sqliteTable("userOrgs", { userId: text("userId") .notNull() - .references(() => users.id), + .references(() => users.userId), orgId: integer("orgId") .notNull() .references(() => orgs.orgId), @@ -114,20 +106,20 @@ export const userOrgs = sqliteTable("userOrgs", { }); export const emailVerificationCodes = sqliteTable("emailVerificationCodes", { - id: integer("id").primaryKey({ autoIncrement: true }), + codeId: integer("id").primaryKey({ autoIncrement: true }), userId: text("userId") .notNull() - .references(() => users.id, { onDelete: "cascade" }), + .references(() => users.userId, { onDelete: "cascade" }), email: text("email").notNull(), code: text("code").notNull(), expiresAt: integer("expiresAt").notNull(), }); export const passwordResetTokens = sqliteTable("passwordResetTokens", { - id: integer("id").primaryKey({ autoIncrement: true }), + tokenId: integer("id").primaryKey({ autoIncrement: true }), userId: text("userId") .notNull() - .references(() => users.id, { onDelete: "cascade" }), + .references(() => users.userId, { onDelete: "cascade" }), tokenHash: text("tokenHash").notNull(), expiresAt: integer("expiresAt").notNull(), }); @@ -140,7 +132,9 @@ export const actions = sqliteTable("actions", { export const roles = sqliteTable("roles", { roleId: integer("roleId").primaryKey({ autoIncrement: true }), - orgId: integer("orgId").references(() => orgs.orgId, { onDelete: "cascade" }), + orgId: integer("orgId").references(() => orgs.orgId, { + onDelete: "cascade", + }), isSuperuserRole: integer("isSuperuserRole", { mode: "boolean" }), name: text("name").notNull(), description: text("description"), @@ -161,7 +155,7 @@ export const roleActions = sqliteTable("roleActions", { export const userActions = sqliteTable("userActions", { userId: text("userId") .notNull() - .references(() => users.id, { onDelete: "cascade" }), + .references(() => users.userId, { onDelete: "cascade" }), actionId: text("actionId") .notNull() .references(() => actions.actionId, { onDelete: "cascade" }), @@ -182,7 +176,7 @@ export const roleSites = sqliteTable("roleSites", { export const userSites = sqliteTable("userSites", { userId: text("userId") .notNull() - .references(() => users.id, { onDelete: "cascade" }), + .references(() => users.userId, { onDelete: "cascade" }), siteId: integer("siteId") .notNull() .references(() => sites.siteId, { onDelete: "cascade" }), @@ -200,7 +194,7 @@ export const roleResources = sqliteTable("roleResources", { export const userResources = sqliteTable("userResources", { userId: text("userId") .notNull() - .references(() => users.id, { onDelete: "cascade" }), + .references(() => users.userId, { onDelete: "cascade" }), resourceId: text("resourceId") .notNull() .references(() => resources.resourceId, { onDelete: "cascade" }), @@ -216,7 +210,6 @@ export const limitsTable = sqliteTable("limits", { description: text("description"), }); -// Define the model types for type inference export type Org = InferSelectModel; export type User = InferSelectModel; export type Site = InferSelectModel; diff --git a/server/middlewares/verifySession.ts b/server/middlewares/verifySession.ts index 688450ef..584529c5 100644 --- a/server/middlewares/verifySession.ts +++ b/server/middlewares/verifySession.ts @@ -20,7 +20,7 @@ export const verifySessionMiddleware = async ( const existingUser = await db .select() .from(users) - .where(eq(users.id, user.id)); + .where(eq(users.userId, user.userId)); if (!existingUser || !existingUser[0]) { return next( diff --git a/server/middlewares/verifyUser.ts b/server/middlewares/verifyUser.ts index 37fbcf7b..3b2b8963 100644 --- a/server/middlewares/verifyUser.ts +++ b/server/middlewares/verifyUser.ts @@ -20,7 +20,7 @@ export const verifySessionUserMiddleware = async ( const existingUser = await db .select() .from(users) - .where(eq(users.id, user.id)); + .where(eq(users.userId, user.userId)); if (!existingUser || !existingUser[0]) { return next( diff --git a/server/routers/auth/changePassword.ts b/server/routers/auth/changePassword.ts index 8094973e..aff5d3e7 100644 --- a/server/routers/auth/changePassword.ts +++ b/server/routers/auth/changePassword.ts @@ -2,7 +2,7 @@ import { Request, Response, NextFunction } from "express"; import createHttpError from "http-errors"; import HttpCode from "@server/types/HttpCode"; import { fromError } from "zod-validation-error"; -import lucia, { unauthorized } from "@server/auth"; +import { unauthorized, invalidateAllSessions } from "@server/auth"; import { z } from "zod"; import { db } from "@server/db"; import { User, users } from "@server/db/schema"; @@ -74,7 +74,7 @@ export async function changePassword( const validOTP = await verifyTotpCode( code!, user.twoFactorSecret!, - user.id, + user.userId, ); if (!validOTP) { @@ -94,9 +94,9 @@ export async function changePassword( .set({ passwordHash: hash, }) - .where(eq(users.id, user.id)); + .where(eq(users.userId, user.userId)); - await lucia.invalidateUserSessions(user.id); + await invalidateAllSessions(user.userId); // TODO: send email to user confirming password change diff --git a/server/routers/auth/disable2fa.ts b/server/routers/auth/disable2fa.ts index f0532812..f85bd677 100644 --- a/server/routers/auth/disable2fa.ts +++ b/server/routers/auth/disable2fa.ts @@ -69,7 +69,7 @@ export async function disable2fa( const validOTP = await verifyTotpCode( code, user.twoFactorSecret!, - user.id, + user.userId, ); if (!validOTP) { @@ -84,11 +84,11 @@ export async function disable2fa( await db .update(users) .set({ twoFactorEnabled: false }) - .where(eq(users.id, user.id)); + .where(eq(users.userId, user.userId)); await db .delete(twoFactorBackupCodes) - .where(eq(twoFactorBackupCodes.userId, user.id)); + .where(eq(twoFactorBackupCodes.userId, user.userId)); // TODO: send email to user confirming two-factor authentication is disabled diff --git a/server/routers/auth/getUserOrgs.ts b/server/routers/auth/getUserOrgs.ts index 224a33b2..2481f1df 100644 --- a/server/routers/auth/getUserOrgs.ts +++ b/server/routers/auth/getUserOrgs.ts @@ -6,7 +6,7 @@ import createHttpError from 'http-errors'; import HttpCode from '@server/types/HttpCode'; export async function getUserOrgs(req: Request, res: Response, next: NextFunction) { - const userId = req.user?.id; // Assuming you have user information in the request + const userId = req.user?.userId; // Assuming you have user information in the request if (!userId) { return next(createHttpError(HttpCode.UNAUTHORIZED, 'User not authenticated')); @@ -30,4 +30,4 @@ export async function getUserOrgs(req: Request, res: Response, next: NextFunctio } catch (error) { next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, 'Error retrieving user organizations')); } -} \ No newline at end of file +} diff --git a/server/routers/auth/login.ts b/server/routers/auth/login.ts index 39dcb0ae..a6fb1937 100644 --- a/server/routers/auth/login.ts +++ b/server/routers/auth/login.ts @@ -1,5 +1,10 @@ import { verify } from "@node-rs/argon2"; -import lucia, { verifySession } from "@server/auth"; +import { + createSession, + generateSessionToken, + serializeSessionCookie, + verifySession, +} from "@server/auth"; import db from "@server/db"; import { users } from "@server/db/schema"; import HttpCode from "@server/types/HttpCode"; @@ -102,7 +107,7 @@ export async function login( const validOTP = await verifyTotpCode( code, existingUser.twoFactorSecret!, - existingUser.id, + existingUser.userId, ); if (!validOTP) { @@ -115,13 +120,11 @@ export async function login( } } - const session = await lucia.createSession(existingUser.id, {}); - const cookie = lucia.createSessionCookie(session.id).serialize(); + const token = generateSessionToken(); + await createSession(token, existingUser.userId); + const cookie = serializeSessionCookie(token); - res.appendHeader( - "Set-Cookie", - cookie - ); + res.appendHeader("Set-Cookie", cookie); if (!existingUser.emailVerified) { return response(res, { diff --git a/server/routers/auth/logout.ts b/server/routers/auth/logout.ts index 9216a158..e5966e4d 100644 --- a/server/routers/auth/logout.ts +++ b/server/routers/auth/logout.ts @@ -1,16 +1,20 @@ import { Request, Response, NextFunction } from "express"; -import { lucia } from "@server/auth"; import createHttpError from "http-errors"; import HttpCode from "@server/types/HttpCode"; import response from "@server/utils/response"; import logger from "@server/logger"; +import { + createBlankSessionTokenCookie, + invalidateSession, + SESSION_COOKIE_NAME, +} from "@server/auth"; export async function logout( req: Request, res: Response, next: NextFunction, ): Promise { - const sessionId = req.cookies[lucia.sessionCookieName]; + const sessionId = req.cookies[SESSION_COOKIE_NAME]; if (!sessionId) { return next( @@ -22,11 +26,8 @@ export async function logout( } try { - await lucia.invalidateSession(sessionId); - res.setHeader( - "Set-Cookie", - lucia.createBlankSessionCookie().serialize(), - ); + await invalidateSession(sessionId); + res.setHeader("Set-Cookie", createBlankSessionTokenCookie()); return response(res, { data: null, diff --git a/server/routers/auth/requestEmailVerificationCode.ts b/server/routers/auth/requestEmailVerificationCode.ts index 7513a7d4..00329b04 100644 --- a/server/routers/auth/requestEmailVerificationCode.ts +++ b/server/routers/auth/requestEmailVerificationCode.ts @@ -26,7 +26,7 @@ export async function requestEmailVerificationCode( ); } - await sendEmailVerificationCode(user.email, user.id); + await sendEmailVerificationCode(user.email, user.userId); return response(res, { data: { diff --git a/server/routers/auth/requestPasswordReset.ts b/server/routers/auth/requestPasswordReset.ts index bbc7839d..e2cdb48f 100644 --- a/server/routers/auth/requestPasswordReset.ts +++ b/server/routers/auth/requestPasswordReset.ts @@ -8,10 +8,11 @@ import { db } from "@server/db"; import { passwordResetTokens, users } from "@server/db/schema"; import { eq } from "drizzle-orm"; import { sha256 } from "oslo/crypto"; -import { generateIdFromEntropySize, TimeSpan } from "lucia"; import { encodeHex } from "oslo/encoding"; import { createDate } from "oslo"; import logger from "@server/logger"; +import { generateIdFromEntropySize } from "@server/auth"; +import { TimeSpan } from "oslo"; export const requestPasswordResetBody = z.object({ email: z.string().email(), @@ -58,7 +59,7 @@ export async function requestPasswordReset( await db .delete(passwordResetTokens) - .where(eq(passwordResetTokens.userId, existingUser[0].id)); + .where(eq(passwordResetTokens.userId, existingUser[0].userId)); const token = generateIdFromEntropySize(25); const tokenHash = encodeHex( @@ -66,7 +67,7 @@ export async function requestPasswordReset( ); await db.insert(passwordResetTokens).values({ - userId: existingUser[0].id, + userId: existingUser[0].userId, tokenHash, expiresAt: createDate(new TimeSpan(2, "h")).getTime(), }); @@ -89,7 +90,7 @@ export async function requestPasswordReset( return next( createHttpError( HttpCode.INTERNAL_SERVER_ERROR, - "Failed to process password reset request" + "Failed to process password reset request", ), ); } diff --git a/server/routers/auth/requestTotpSecret.ts b/server/routers/auth/requestTotpSecret.ts index 7ea82cfa..2b2421d9 100644 --- a/server/routers/auth/requestTotpSecret.ts +++ b/server/routers/auth/requestTotpSecret.ts @@ -72,7 +72,7 @@ export async function requestTotpSecret( .set({ twoFactorSecret: secret, }) - .where(eq(users.id, user.id)); + .where(eq(users.userId, user.userId)); return response(res, { data: { diff --git a/server/routers/auth/resetPassword.ts b/server/routers/auth/resetPassword.ts index b13a16e5..0d12da2c 100644 --- a/server/routers/auth/resetPassword.ts +++ b/server/routers/auth/resetPassword.ts @@ -13,7 +13,7 @@ import { verifyTotpCode } from "@server/auth/2fa"; import { passwordSchema } from "@server/auth/passwordSchema"; import { encodeHex } from "oslo/encoding"; import { isWithinExpirationDate } from "oslo"; -import lucia from "@server/auth"; +import { invalidateAllSessions } from "@server/auth"; export const resetPasswordBody = z.object({ token: z.string(), @@ -71,7 +71,7 @@ export async function resetPassword( const user = await db .select() .from(users) - .where(eq(users.id, resetRequest[0].userId)); + .where(eq(users.userId, resetRequest[0].userId)); if (!user || !user.length) { return next( @@ -96,7 +96,7 @@ export async function resetPassword( const validOTP = await verifyTotpCode( code!, user[0].twoFactorSecret!, - user[0].id, + user[0].userId, ); if (!validOTP) { @@ -111,12 +111,12 @@ export async function resetPassword( const passwordHash = await hashPassword(newPassword); - await lucia.invalidateUserSessions(resetRequest[0].userId); + await invalidateAllSessions(resetRequest[0].userId); await db .update(users) .set({ passwordHash }) - .where(eq(users.id, resetRequest[0].userId)); + .where(eq(users.userId, resetRequest[0].userId)); await db .delete(passwordResetTokens) diff --git a/server/routers/auth/signup.ts b/server/routers/auth/signup.ts index 4344a160..c38c1290 100644 --- a/server/routers/auth/signup.ts +++ b/server/routers/auth/signup.ts @@ -3,9 +3,7 @@ import db from "@server/db"; import { hash } from "@node-rs/argon2"; import HttpCode from "@server/types/HttpCode"; import { z } from "zod"; -import { generateId } from "lucia"; import { users } from "@server/db/schema"; -import lucia from "@server/auth"; import { fromError } from "zod-validation-error"; import createHttpError from "http-errors"; import response from "@server/utils/response"; @@ -14,6 +12,12 @@ import { sendEmailVerificationCode } from "./sendEmailVerificationCode"; import { passwordSchema } from "@server/auth/passwordSchema"; import { eq } from "drizzle-orm"; import moment from "moment"; +import { + createSession, + generateId, + generateSessionToken, + serializeSessionCookie, +} from "@server/auth"; export const signupBodySchema = z.object({ email: z.string().email(), @@ -85,22 +89,21 @@ export async function signup( ); } else { // If the user was created more than 2 hours ago, we want to delete the old user and create a new one - await db.delete(users).where(eq(users.id, user.id)); + await db.delete(users).where(eq(users.userId, user.userId)); } } await db.insert(users).values({ - id: userId, + userId: userId, email: email, passwordHash, dateCreated: moment().toISOString(), }); - const session = await lucia.createSession(userId, {}); - res.appendHeader( - "Set-Cookie", - lucia.createSessionCookie(session.id).serialize(), - ); + const token = generateSessionToken(); + await createSession(token, userId); + const cookie = serializeSessionCookie(token); + res.appendHeader("Set-Cookie", cookie); sendEmailVerificationCode(email, userId); diff --git a/server/routers/auth/verifyEmail.ts b/server/routers/auth/verifyEmail.ts index 55ab146f..38ba13e4 100644 --- a/server/routers/auth/verifyEmail.ts +++ b/server/routers/auth/verifyEmail.ts @@ -51,14 +51,14 @@ export async function verifyEmail( if (valid) { await db .delete(emailVerificationCodes) - .where(eq(emailVerificationCodes.userId, user.id)); + .where(eq(emailVerificationCodes.userId, user.userId)); await db .update(users) .set({ emailVerified: true, }) - .where(eq(users.id, user.id)); + .where(eq(users.userId, user.userId)); } else { return next( createHttpError( @@ -93,7 +93,7 @@ async function isValidCode(user: User, code: string): Promise { const codeRecord = await db .select() .from(emailVerificationCodes) - .where(eq(emailVerificationCodes.userId, user.id)) + .where(eq(emailVerificationCodes.userId, user.userId)) .limit(1); if (user.email !== codeRecord[0].email) { diff --git a/server/routers/auth/verifyOrgAccess.ts b/server/routers/auth/verifyOrgAccess.ts index 45792072..d195b870 100644 --- a/server/routers/auth/verifyOrgAccess.ts +++ b/server/routers/auth/verifyOrgAccess.ts @@ -7,7 +7,7 @@ import HttpCode from '@server/types/HttpCode'; import { AuthenticatedRequest } from '@server/types/Auth'; export function verifyOrgAccess(req: Request, res: Response, next: NextFunction) { - const userId = req.user!.id; // Assuming you have user information in the request + const userId = req.user!.userId; // Assuming you have user information in the request const orgId = parseInt(req.params.orgId); if (!userId) { diff --git a/server/routers/auth/verifyResourceAccess.ts b/server/routers/auth/verifyResourceAccess.ts index 85b133eb..777e6fb7 100644 --- a/server/routers/auth/verifyResourceAccess.ts +++ b/server/routers/auth/verifyResourceAccess.ts @@ -6,7 +6,7 @@ import createHttpError from 'http-errors'; import HttpCode from '@server/types/HttpCode'; export async function verifyResourceAccess(req: Request, res: Response, next: NextFunction) { - const userId = req.user!.id; // Assuming you have user information in the request + const userId = req.user!.userId; // Assuming you have user information in the request const resourceId = req.params.resourceId || req.body.resourceId || req.query.resourceId; if (!userId) { @@ -75,4 +75,4 @@ export async function verifyResourceAccess(req: Request, res: Response, next: Ne } catch (error) { return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, 'Error verifying resource access')); } -} \ No newline at end of file +} diff --git a/server/routers/auth/verifyRoleAccess.ts b/server/routers/auth/verifyRoleAccess.ts index 70418de2..c520a330 100644 --- a/server/routers/auth/verifyRoleAccess.ts +++ b/server/routers/auth/verifyRoleAccess.ts @@ -7,7 +7,7 @@ import HttpCode from '@server/types/HttpCode'; import logger from '@server/logger'; export async function verifyRoleAccess(req: Request, res: Response, next: NextFunction) { - const userId = req.user?.id; // Assuming you have user information in the request + const userId = req.user?.userId; // Assuming you have user information in the request const roleId = parseInt(req.params.roleId || req.body.roleId || req.query.roleId); if (!userId) { @@ -47,4 +47,4 @@ export async function verifyRoleAccess(req: Request, res: Response, next: NextFu logger.error('Error verifying role access:', error); return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, 'Error verifying role access')); } -} \ No newline at end of file +} diff --git a/server/routers/auth/verifySiteAccess.ts b/server/routers/auth/verifySiteAccess.ts index 7f53ef65..7648c001 100644 --- a/server/routers/auth/verifySiteAccess.ts +++ b/server/routers/auth/verifySiteAccess.ts @@ -6,7 +6,7 @@ import createHttpError from 'http-errors'; import HttpCode from '@server/types/HttpCode'; export async function verifySiteAccess(req: Request, res: Response, next: NextFunction) { - const userId = req.user!.id; // Assuming you have user information in the request + const userId = req.user!.userId; // Assuming you have user information in the request const siteId = parseInt(req.params.siteId || req.body.siteId || req.query.siteId); if (!userId) { @@ -76,4 +76,4 @@ export async function verifySiteAccess(req: Request, res: Response, next: NextFu } catch (error) { return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, 'Error verifying site access')); } -} \ No newline at end of file +} diff --git a/server/routers/auth/verifySuperuser.ts b/server/routers/auth/verifySuperuser.ts index 0a4b311e..46b26c1d 100644 --- a/server/routers/auth/verifySuperuser.ts +++ b/server/routers/auth/verifySuperuser.ts @@ -7,7 +7,7 @@ import HttpCode from '@server/types/HttpCode'; import logger from '@server/logger'; export async function verifySuperuser(req: Request, res: Response, next: NextFunction) { - const userId = req.user?.id; // Assuming you have user information in the request + const userId = req.user?.userId; // Assuming you have user information in the request const orgId = req.userOrgId; if (!userId) { @@ -18,7 +18,7 @@ export async function verifySuperuser(req: Request, res: Response, next: NextFun return next(createHttpError(HttpCode.UNAUTHORIZED, 'User not authenticated')); } - try { + try { // Check if the user has a role in the organization const userOrgRole = await db.select() .from(userOrgs) @@ -35,7 +35,7 @@ export async function verifySuperuser(req: Request, res: Response, next: NextFun .from(roles) .where(eq(roles.roleId, userOrgRole[0].roleId)) .limit(1); - + if (userRole.length === 0 || !userRole[0].isSuperuserRole) { return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have superuser access')); } @@ -45,4 +45,4 @@ export async function verifySuperuser(req: Request, res: Response, next: NextFun logger.error('Error verifying role access:', error); return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, 'Error verifying role access')); } -} \ No newline at end of file +} diff --git a/server/routers/auth/verifyTargetAccess.ts b/server/routers/auth/verifyTargetAccess.ts index eb4db4b6..86d63e0d 100644 --- a/server/routers/auth/verifyTargetAccess.ts +++ b/server/routers/auth/verifyTargetAccess.ts @@ -6,7 +6,7 @@ import createHttpError from 'http-errors'; import HttpCode from '@server/types/HttpCode'; export async function verifyTargetAccess(req: Request, res: Response, next: NextFunction) { - const userId = req.user!.id; // Assuming you have user information in the request + const userId = req.user!.userId; // Assuming you have user information in the request const targetId = parseInt(req.params.targetId); if (!userId) { @@ -81,4 +81,4 @@ export async function verifyTargetAccess(req: Request, res: Response, next: Next .catch((error) => { next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, 'Error verifying organization access')); }); -} \ No newline at end of file +} diff --git a/server/routers/auth/verifyTotp.ts b/server/routers/auth/verifyTotp.ts index 805c97a9..22473301 100644 --- a/server/routers/auth/verifyTotp.ts +++ b/server/routers/auth/verifyTotp.ts @@ -61,7 +61,7 @@ export async function verifyTotp( } try { - const valid = await verifyTotpCode(code, user.twoFactorSecret, user.id); + const valid = await verifyTotpCode(code, user.twoFactorSecret, user.userId); let codes; if (valid) { @@ -69,7 +69,7 @@ export async function verifyTotp( await db .update(users) .set({ twoFactorEnabled: true }) - .where(eq(users.id, user.id)); + .where(eq(users.userId, user.userId)); const backupCodes = await generateBackupCodes(); codes = backupCodes; @@ -77,7 +77,7 @@ export async function verifyTotp( const hash = await hashPassword(code); await db.insert(twoFactorBackupCodes).values({ - userId: user.id, + userId: user.userId, codeHash: hash, }); } diff --git a/server/routers/auth/verifyUserAccess.ts b/server/routers/auth/verifyUserAccess.ts index 67e6c936..53606c48 100644 --- a/server/routers/auth/verifyUserAccess.ts +++ b/server/routers/auth/verifyUserAccess.ts @@ -6,7 +6,7 @@ import createHttpError from 'http-errors'; import HttpCode from '@server/types/HttpCode'; export async function verifyUserAccess(req: Request, res: Response, next: NextFunction) { - const userId = req.user!.id; // Assuming you have user information in the request + const userId = req.user!.userId; // Assuming you have user information in the request const reqUserId = req.params.userId || req.body.userId || req.query.userId; if (!userId) { @@ -18,7 +18,7 @@ export async function verifyUserAccess(req: Request, res: Response, next: NextFu } try { - + const userOrg = await db.select() .from(userOrgs) .where(and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, req.userOrgId!))) @@ -34,4 +34,4 @@ export async function verifyUserAccess(req: Request, res: Response, next: NextFu } catch (error) { return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, 'Error verifying site access')); } -} \ No newline at end of file +} diff --git a/server/routers/badger/verifyUser.ts b/server/routers/badger/verifyUser.ts index a157c421..952dfd7d 100644 --- a/server/routers/badger/verifyUser.ts +++ b/server/routers/badger/verifyUser.ts @@ -1,11 +1,10 @@ -import lucia from "@server/auth"; import HttpCode from "@server/types/HttpCode"; import { NextFunction, Request, Response } from "express"; import createHttpError from "http-errors"; import { z } from "zod"; import { fromError } from "zod-validation-error"; import { response } from "@server/utils/response"; -import logger from "@server/logger"; +import { validateSessionToken } from "@server/auth"; export const verifyUserBody = z.object({ sessionId: z.string(), @@ -36,7 +35,7 @@ export async function verifyUser( const { sessionId } = parsedBody.data; try { - const { session, user } = await lucia.validateSession(sessionId); + const { session, user } = await validateSessionToken(sessionId); if (!session || !user) { return next( diff --git a/server/routers/resource/createResource.ts b/server/routers/resource/createResource.ts index 9a68d9f9..88f999df 100644 --- a/server/routers/resource/createResource.ts +++ b/server/routers/resource/createResource.ts @@ -95,7 +95,7 @@ export async function createResource(req: Request, res: Response, next: NextFunc if (req.userOrgRoleId != superuserRole[0].roleId) { // make sure the user can access the resource await db.insert(userResources).values({ - userId: req.user?.id!, + userId: req.user?.userId!, resourceId: newResource[0].resourceId, }); } diff --git a/server/routers/resource/listResources.ts b/server/routers/resource/listResources.ts index 594c9665..20c24fb5 100644 --- a/server/routers/resource/listResources.ts +++ b/server/routers/resource/listResources.ts @@ -57,7 +57,7 @@ export async function listResources(req: RequestWithOrgAndRole, res: Response, n .fullJoin(roleResources, eq(userResources.resourceId, roleResources.resourceId)) .where( or( - eq(userResources.userId, req.user!.id), + eq(userResources.userId, req.user!.userId), eq(roleResources.roleId, req.userOrgRoleId!) ) ); @@ -111,4 +111,4 @@ export async function listResources(req: RequestWithOrgAndRole, res: Response, n logger.error(error); return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred...")); } -} \ No newline at end of file +} diff --git a/server/routers/site/createSite.ts b/server/routers/site/createSite.ts index 2ae99246..3f512955 100644 --- a/server/routers/site/createSite.ts +++ b/server/routers/site/createSite.ts @@ -75,7 +75,7 @@ export async function createSite(req: Request, res: Response, next: NextFunction .from(roles) .where(and(eq(roles.isSuperuserRole, true), eq(roles.orgId, orgId))) .limit(1); - + if (superuserRole.length === 0) { return next( createHttpError( @@ -84,20 +84,20 @@ export async function createSite(req: Request, res: Response, next: NextFunction ) ); } - - await db.insert(roleSites).values({ + + await db.insert(roleSites).values({ roleId: superuserRole[0].roleId, siteId: newSite[0].siteId, }); if (req.userOrgRoleId != superuserRole[0].roleId) { // make sure the user can access the site - db.insert(userSites).values({ - userId: req.user?.id!, + db.insert(userSites).values({ + userId: req.user?.userId!, siteId: newSite[0].siteId, }); } - + return response(res, { data: newSite[0], success: true, diff --git a/server/routers/site/listSites.ts b/server/routers/site/listSites.ts index 29436b51..bbac9917 100644 --- a/server/routers/site/listSites.ts +++ b/server/routers/site/listSites.ts @@ -49,7 +49,7 @@ export async function listSites(req: Request, res: Response, next: NextFunction) .fullJoin(roleSites, eq(userSites.siteId, roleSites.siteId)) .where( or( - eq(userSites.userId, req.user!.id), + eq(userSites.userId, req.user!.userId), eq(roleSites.roleId, req.userOrgRoleId!) ) ); @@ -104,4 +104,4 @@ export async function listSites(req: Request, res: Response, next: NextFunction) logger.error(error); return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred...")); } -} \ No newline at end of file +} diff --git a/server/routers/user/addUserAction.ts b/server/routers/user/addUserAction.ts index d84f717c..0ac899e3 100644 --- a/server/routers/user/addUserAction.ts +++ b/server/routers/user/addUserAction.ts @@ -36,7 +36,7 @@ export async function addUserAction(req: Request, res: Response, next: NextFunct } // Check if the user exists - const user = await db.select().from(users).where(eq(users.id, userId)).limit(1); + const user = await db.select().from(users).where(eq(users.userId, userId)).limit(1); if (user.length === 0) { return next(createHttpError(HttpCode.NOT_FOUND, `User with ID ${userId} not found`)); } @@ -58,4 +58,4 @@ export async function addUserAction(req: Request, res: Response, next: NextFunct logger.error(error); return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred...")); } -} \ No newline at end of file +} diff --git a/server/routers/user/addUserOrg.ts b/server/routers/user/addUserOrg.ts index 01b59aba..5c3e5148 100644 --- a/server/routers/user/addUserOrg.ts +++ b/server/routers/user/addUserOrg.ts @@ -51,7 +51,7 @@ export async function addUserOrg(req: Request, res: Response, next: NextFunction } // Check if the user exists - const user = await db.select().from(users).where(eq(users.id, userId)).limit(1); + const user = await db.select().from(users).where(eq(users.userId, userId)).limit(1); if (user.length === 0) { return next(createHttpError(HttpCode.NOT_FOUND, `User with ID ${userId} not found`)); } @@ -84,4 +84,4 @@ export async function addUserOrg(req: Request, res: Response, next: NextFunction logger.error(error); return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred...")); } -} \ No newline at end of file +} diff --git a/server/routers/user/getUser.ts b/server/routers/user/getUser.ts index bbec51ad..4cbd37f8 100644 --- a/server/routers/user/getUser.ts +++ b/server/routers/user/getUser.ts @@ -21,7 +21,7 @@ export async function getUser( next: NextFunction, ): Promise { try { - const userId = req.user?.id; + const userId = req.user?.userId; if (!userId) { return next( @@ -32,7 +32,7 @@ export async function getUser( const user = await db .select() .from(users) - .where(eq(users.id, userId)) + .where(eq(users.userId, userId)) .limit(1); if (user.length === 0) { diff --git a/server/routers/user/listUsers.ts b/server/routers/user/listUsers.ts index c8b2fa5a..fe240226 100644 --- a/server/routers/user/listUsers.ts +++ b/server/routers/user/listUsers.ts @@ -52,7 +52,7 @@ export async function listUsers(req: Request, res: Response, next: NextFunction) // Query to join users, userOrgs, and roles tables const usersWithRoles = await db .select({ - id: users.id, + id: users.userId, email: users.email, emailVerified: users.emailVerified, dateCreated: users.dateCreated, @@ -61,7 +61,7 @@ export async function listUsers(req: Request, res: Response, next: NextFunction) roleName: roles.name, }) .from(users) - .leftJoin(userOrgs, sql`${users.id} = ${userOrgs.userId}`) + .leftJoin(userOrgs, sql`${users.userId} = ${userOrgs.userId}`) .leftJoin(roles, sql`${userOrgs.roleId} = ${roles.roleId}`) .where(sql`${userOrgs.orgId} = ${orgId}`) .limit(limit) @@ -90,4 +90,4 @@ export async function listUsers(req: Request, res: Response, next: NextFunction) logger.error(error); return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred...")); } -} \ No newline at end of file +} diff --git a/server/types/Auth.ts b/server/types/Auth.ts index 69f6cefc..659a0e75 100644 --- a/server/types/Auth.ts +++ b/server/types/Auth.ts @@ -1,6 +1,6 @@ import { Request } from "express"; import { User } from "@server/db/schema"; -import { Session } from "lucia"; +import { Session } from "@server/db/schema"; export interface AuthenticatedRequest extends Request { user: User; diff --git a/src/app/page.tsx b/src/app/page.tsx index b2a804cd..d8f8b950 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -9,8 +9,6 @@ export default async function Page() { redirect("/auth/login"); } - console.log(user); - return ( <>