Added auth
This commit is contained in:
114
src/services/user.service.ts
Normal file
114
src/services/user.service.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
// User service
|
||||
|
||||
import { db } from "./db";
|
||||
import type { AuthUser, UserRole, RegisterRequest } from "../types/auth";
|
||||
|
||||
interface UserRow {
|
||||
id: string;
|
||||
email: string;
|
||||
password_hash: string;
|
||||
role: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
function rowToUser(row: UserRow): AuthUser & { passwordHash: string } {
|
||||
return {
|
||||
id: row.id,
|
||||
email: row.email,
|
||||
passwordHash: row.password_hash,
|
||||
role: row.role as UserRole,
|
||||
createdAt: new Date(row.created_at),
|
||||
updatedAt: new Date(row.updated_at),
|
||||
};
|
||||
}
|
||||
|
||||
export async function hashPassword(password: string): Promise<string> {
|
||||
return await Bun.password.hash(password, {
|
||||
algorithm: "argon2id",
|
||||
memoryCost: 65536,
|
||||
timeCost: 2,
|
||||
});
|
||||
}
|
||||
|
||||
export async function verifyPassword(
|
||||
password: string,
|
||||
hash: string
|
||||
): Promise<boolean> {
|
||||
return await Bun.password.verify(password, hash);
|
||||
}
|
||||
|
||||
export async function findUserByEmail(
|
||||
email: string
|
||||
): Promise<(AuthUser & { passwordHash: string }) | null> {
|
||||
const row = db.query("SELECT * FROM users WHERE email = ?").get(email.toLowerCase()) as UserRow | null;
|
||||
return row ? rowToUser(row) : null;
|
||||
}
|
||||
|
||||
export async function findUserById(id: string): Promise<AuthUser | null> {
|
||||
const row = db.query("SELECT * FROM users WHERE id = ?").get(id) as UserRow | null;
|
||||
if (!row) return null;
|
||||
|
||||
const user = rowToUser(row);
|
||||
const { passwordHash, ...userWithoutPassword } = user;
|
||||
return userWithoutPassword;
|
||||
}
|
||||
|
||||
export async function createUser(
|
||||
data: RegisterRequest
|
||||
): Promise<{ user: AuthUser; error?: never } | { user?: never; error: string }> {
|
||||
const email = data.email.toLowerCase().trim();
|
||||
|
||||
const existing = db.query("SELECT id FROM users WHERE email = ?").get(email);
|
||||
if (existing) {
|
||||
return { error: "User with this email already exists" };
|
||||
}
|
||||
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
if (!emailRegex.test(email)) {
|
||||
return { error: "Invalid email format" };
|
||||
}
|
||||
|
||||
if (data.password.length < 8) {
|
||||
return { error: "Password must be at least 8 characters long" };
|
||||
}
|
||||
|
||||
const passwordHash = await hashPassword(data.password);
|
||||
const now = new Date().toISOString();
|
||||
const userId = crypto.randomUUID();
|
||||
|
||||
db.prepare(`
|
||||
INSERT INTO users (id, email, password_hash, role, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
`).run(userId, email, passwordHash, "user", now, now);
|
||||
|
||||
return {
|
||||
user: {
|
||||
id: userId,
|
||||
email,
|
||||
role: "user" as UserRole,
|
||||
createdAt: new Date(now),
|
||||
updatedAt: new Date(now),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export async function authenticateUser(
|
||||
email: string,
|
||||
password: string
|
||||
): Promise<{ user: AuthUser; error?: never } | { user?: never; error: string }> {
|
||||
const user = await findUserByEmail(email);
|
||||
|
||||
if (!user) {
|
||||
return { error: "Invalid email or password" };
|
||||
}
|
||||
|
||||
const isValidPassword = await verifyPassword(password, user.passwordHash);
|
||||
|
||||
if (!isValidPassword) {
|
||||
return { error: "Invalid email or password" };
|
||||
}
|
||||
|
||||
const { passwordHash, ...userWithoutPassword } = user;
|
||||
return { user: userWithoutPassword };
|
||||
}
|
||||
Reference in New Issue
Block a user