diff --git a/mvp/b2c/src/db/auth-queries.ts b/mvp/b2c/src/db/auth-queries.ts new file mode 100644 index 0000000..57536a6 --- /dev/null +++ b/mvp/b2c/src/db/auth-queries.ts @@ -0,0 +1,47 @@ +import { eq } from 'drizzle-orm'; +import { db } from './index'; +import { users, tenants } from './schema'; +import { sessionExpiry } from '@/lib/auth/tokens'; + +export async function getUserByEmail(email: string) { + const [row] = await db.select().from(users).where(eq(users.email, email)).limit(1); + return row ?? null; +} + +export async function getUserById(id: string) { + const [row] = await db.select().from(users).where(eq(users.id, id)).limit(1); + return row ?? null; +} + +export async function createTenantWithOwner(input: { + nombreEmpresa: string; + slug: string; + provincia: string | null; + email: string; + passwordHash: string; + nombre: string | null; +}) { + const [tenant] = await db + .insert(tenants) + .values({ + slug: input.slug, + nombreEmpresa: input.nombreEmpresa, + provincia: input.provincia, + subscriptionStatus: 'trial', + trialEndsAt: sessionExpiry(new Date()), + }) + .returning(); + + const [user] = await db + .insert(users) + .values({ + email: input.email, + passwordHash: input.passwordHash, + nombre: input.nombre, + role: 'reformista', + tenantId: tenant.id, + }) + .returning(); + + return { tenant, user }; +} diff --git a/mvp/b2c/src/lib/auth/current-user.ts b/mvp/b2c/src/lib/auth/current-user.ts new file mode 100644 index 0000000..133cad2 --- /dev/null +++ b/mvp/b2c/src/lib/auth/current-user.ts @@ -0,0 +1,25 @@ +import { redirect } from 'next/navigation'; +import { getSessionUser } from './session'; +import { resolveTenantId, type AuthUser } from './authz'; + +export async function getCurrentUser(): Promise { + return getSessionUser(); +} + +export async function requireUser(): Promise { + const user = await getSessionUser(); + if (!user) redirect('/login'); + return user; +} + +export async function requireAdmin(): Promise { + const user = await getSessionUser(); + if (!user) redirect('/login'); + if (user.role !== 'admin') redirect('/panel'); + return user; +} + +export async function getCurrentTenantId(): Promise { + const user = await getSessionUser(); + return resolveTenantId(user); +} diff --git a/mvp/b2c/src/lib/auth/session.ts b/mvp/b2c/src/lib/auth/session.ts new file mode 100644 index 0000000..8643761 --- /dev/null +++ b/mvp/b2c/src/lib/auth/session.ts @@ -0,0 +1,70 @@ +import { cookies } from 'next/headers'; +import { eq } from 'drizzle-orm'; +import { db } from '@/db'; +import { sessions } from '@/db/schema'; +import { getUserById } from '@/db/auth-queries'; +import { + generateSessionToken, + hashSessionToken, + isSessionExpired, + sessionExpiry, +} from './tokens'; +import type { AuthUser } from './authz'; + +const COOKIE = 'session'; + +export async function createSession(userId: string): Promise { + const token = generateSessionToken(); + await db.insert(sessions).values({ + userId, + tokenHash: hashSessionToken(token), + expiresAt: sessionExpiry(), + }); + const store = await cookies(); + store.set(COOKIE, token, { + httpOnly: true, + secure: true, + sameSite: 'lax', + path: '/', + expires: sessionExpiry(), + }); +} + +export async function destroySession(): Promise { + const store = await cookies(); + const token = store.get(COOKIE)?.value; + if (token) { + await db.delete(sessions).where(eq(sessions.tokenHash, hashSessionToken(token))); + store.delete(COOKIE); + } +} + +export async function getSessionUser(): Promise { + const store = await cookies(); + const token = store.get(COOKIE)?.value; + if (!token) return null; + + const [session] = await db + .select() + .from(sessions) + .where(eq(sessions.tokenHash, hashSessionToken(token))) + .limit(1); + if (!session) return null; + + if (isSessionExpired(session.expiresAt)) { + await db.delete(sessions).where(eq(sessions.id, session.id)); + return null; + } + + const user = await getUserById(session.userId); + if (!user || user.status !== 'activo') return null; + + return { + id: user.id, + email: user.email, + nombre: user.nombre, + role: user.role, + tenantId: user.tenantId, + status: user.status, + }; +}