web.ts/src/lib/actions/auth.ts
2025-06-05 01:08:50 +01:00

158 lines
3.4 KiB
TypeScript

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<typeof RegisterSchema>) =>
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<typeof LoginSchema>) =>
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: '/',
});
});