72 lines
2.1 KiB
TypeScript
72 lines
2.1 KiB
TypeScript
|
|
import bcrypt from 'bcryptjs';
|
||
|
|
import jwt from 'jsonwebtoken';
|
||
|
|
import crypto from 'node:crypto';
|
||
|
|
import { z } from 'zod';
|
||
|
|
import * as usersRepo from '../repositories/users.repository.js';
|
||
|
|
import * as tokensRepo from '../repositories/tokens.repository.js';
|
||
|
|
import { TokenType } from '../generated/prisma/enums.js';
|
||
|
|
|
||
|
|
const JWT_SECRET = process.env.JWT_SECRET ?? 'changeme';
|
||
|
|
const JWT_EXPIRES_IN = '15m';
|
||
|
|
const REFRESH_EXPIRES_DAYS = 7;
|
||
|
|
|
||
|
|
export const registerSchema = z.object({
|
||
|
|
name: z.string().check(z.minLength(2)),
|
||
|
|
email: z.email(),
|
||
|
|
password: z.string().check(z.minLength(6)),
|
||
|
|
});
|
||
|
|
|
||
|
|
export const loginSchema = z.object({
|
||
|
|
email: z.email(),
|
||
|
|
password: z.string().check(z.minLength(1)),
|
||
|
|
});
|
||
|
|
|
||
|
|
export async function register(input: z.infer<typeof registerSchema>) {
|
||
|
|
const existing = await usersRepo.findUserByEmail(input.email);
|
||
|
|
if (existing) throw new Error('Email already in use');
|
||
|
|
|
||
|
|
const hashed = await bcrypt.hash(input.password, 10);
|
||
|
|
const now = new Date();
|
||
|
|
|
||
|
|
const user = await usersRepo.createUser({
|
||
|
|
id: crypto.randomUUID(),
|
||
|
|
name: input.name,
|
||
|
|
email: input.email,
|
||
|
|
password: hashed,
|
||
|
|
updatedAt: now,
|
||
|
|
});
|
||
|
|
|
||
|
|
return { id: user.id, name: user.name, email: user.email };
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function login(input: z.infer<typeof loginSchema>) {
|
||
|
|
const user = await usersRepo.findUserByEmail(input.email);
|
||
|
|
if (!user) throw new Error('Invalid credentials');
|
||
|
|
|
||
|
|
const valid = await bcrypt.compare(input.password, user.password);
|
||
|
|
if (!valid) throw new Error('Invalid credentials');
|
||
|
|
|
||
|
|
const accessToken = jwt.sign({ sub: user.id }, JWT_SECRET, { expiresIn: JWT_EXPIRES_IN });
|
||
|
|
|
||
|
|
const refreshToken = crypto.randomUUID();
|
||
|
|
const expiresAt = new Date();
|
||
|
|
expiresAt.setDate(expiresAt.getDate() + REFRESH_EXPIRES_DAYS);
|
||
|
|
|
||
|
|
await tokensRepo.createToken({
|
||
|
|
id: crypto.randomUUID(),
|
||
|
|
token: refreshToken,
|
||
|
|
type: TokenType.REFRESH,
|
||
|
|
userId: user.id,
|
||
|
|
expiresAt,
|
||
|
|
});
|
||
|
|
|
||
|
|
return { accessToken, refreshToken };
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function logout(refreshToken: string) {
|
||
|
|
await tokensRepo.deleteToken(refreshToken);
|
||
|
|
}
|
||
|
|
|
||
|
|
export function verifyAccessToken(token: string): { sub: string } {
|
||
|
|
return jwt.verify(token, JWT_SECRET) as { sub: string };
|
||
|
|
}
|