import { createServerFn } from '@tanstack/react-start'; import { useAppSession } from '../utils/session'; import { database } from '@/db'; import { createInsertSchema } from 'drizzle-zod'; import { account } from '@/db/schema'; import { z } from 'zod/v4'; import { redirect } from '@tanstack/react-router'; import { createSession, invalidateSession, validateSessionToken, } from '../session/auth'; import { getIp } from '../utils'; import { generateSessionToken } from '../session'; import { password } from 'bun'; import { handleServerFnValidation, serverError, unauthorizedError, validationError, } from '../utils/http'; export const fetchUser = createServerFn({ method: 'GET', response: 'data', }).handler(async () => { const session = await useAppSession(); if (!session.data.sessionToken) { return { session: null, user: null }; } return await validateSessionToken(session.data.sessionToken, getIp()); }); export const RegisterSchema = createInsertSchema(account) .pick({ login: true, email: true, socialId: true, password: true, }) .extend({ redirectUrl: z.optional(z.string()), }); export const register = createServerFn({ method: 'POST', response: 'data', }) .validator((payload: z.infer) => handleServerFnValidation(RegisterSchema, payload), ) .handler(async ({ data }) => { const [user] = await database .insert(account) .values({ login: data.login, email: data.email, socialId: data.socialId, password: await password.hash(data.password, 'argon2id'), }) .returning(); if (!user) { throw serverError(); } const appSession = await useAppSession(); const session = await createSession( generateSessionToken(), user.id, getIp(), ); await appSession.update({ sessionToken: session.id, }); throw redirect({ href: data.redirectUrl || '/', }); }); export const LoginSchema = createInsertSchema(account) .pick({ login: true, password: true, }) .extend({ redirectUrl: z.optional(z.string()), }); export const login = createServerFn({ method: 'POST', response: 'data', }) .validator((payload: z.infer) => handleServerFnValidation(LoginSchema, payload), ) .handler(async ({ data }) => { const user = await database.query.account.findFirst({ where: { login: { eq: data.login, }, }, }); if (!user) { throw validationError({ login: ['Invalid credentials'], }); } if (!(await password.verify(data.password, user.password, 'argon2id'))) { throw validationError({ login: ['Invalid credentials'], }); } const appSession = await useAppSession(); const session = await createSession( generateSessionToken(), user.id, getIp(), ); await appSession.update({ sessionToken: session.id, }); throw redirect({ href: data.redirectUrl || '/', }); }); export const logout = createServerFn({ method: 'POST', response: 'data', }).handler(async () => { const appSession = await useAppSession(); if (!appSession.data.sessionToken) { throw unauthorizedError(); } await invalidateSession(appSession.data.sessionToken); await appSession.clear(); throw redirect({ href: '/', }); });