Add ciclo de vida de sesión y helpers de usuario actual

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Carlos Narro
2026-05-30 19:39:31 +02:00
parent 7b3b8457c1
commit a6b77b9731
3 changed files with 142 additions and 0 deletions

View File

@@ -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 };
}

View File

@@ -0,0 +1,25 @@
import { redirect } from 'next/navigation';
import { getSessionUser } from './session';
import { resolveTenantId, type AuthUser } from './authz';
export async function getCurrentUser(): Promise<AuthUser | null> {
return getSessionUser();
}
export async function requireUser(): Promise<AuthUser> {
const user = await getSessionUser();
if (!user) redirect('/login');
return user;
}
export async function requireAdmin(): Promise<AuthUser> {
const user = await getSessionUser();
if (!user) redirect('/login');
if (user.role !== 'admin') redirect('/panel');
return user;
}
export async function getCurrentTenantId(): Promise<string> {
const user = await getSessionUser();
return resolveTenantId(user);
}

View File

@@ -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<void> {
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<void> {
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<AuthUser | null> {
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,
};
}