158 lines
3.4 KiB
TypeScript
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: '/',
|
|
});
|
|
});
|