feat: auth endpoints (session-based)

This commit is contained in:
Alexander Andreev 2026-01-02 15:10:20 +03:00
parent 0f85059e92
commit 2c48c8b55b
5 changed files with 168 additions and 0 deletions

View File

@ -0,0 +1,56 @@
import { PrismaClient } from '@prisma/client';
import { verifyPassword } from '../utils/password';
import { useSession } from 'h3';
const prisma = new PrismaClient();
export default defineEventHandler(async (event) => {
const body = await readBody(event);
const { email, password } = body;
// 1. Validate input
if (!email || !password) {
throw createError({
statusCode: 400,
statusMessage: 'Email and password are required',
});
}
// 2. Find the user
const user = await prisma.user.findUnique({
where: { email },
});
if (!user) {
throw createError({
statusCode: 401, // Unauthorized
statusMessage: 'Invalid credentials',
});
}
// 3. Verify the password
const isPasswordValid = await verifyPassword(password, user.password);
if (!isPasswordValid) {
throw createError({
statusCode: 401,
statusMessage: 'Invalid credentials',
});
}
// 4. Create and update the session
const session = await useSession(event, {
password: process.env.SESSION_PASSWORD || 'your-super-secret-32-character-password', // Should be in .env
maxAge: 60 * 60 * 24 * 7, // 1 week
});
await session.update({
user: {
id: user.id,
email: user.email,
}
});
// 5. Return user data
const { password: _password, ...userWithoutPassword } = user;
return { user: userWithoutPassword };
});

View File

@ -0,0 +1,11 @@
import { useSession } from 'h3';
export default defineEventHandler(async (event) => {
const session = await useSession(event, {
password: process.env.SESSION_PASSWORD || 'your-super-secret-32-character-password',
});
await session.clear();
return { message: 'Logged out successfully.' };
});

38
server/api/auth/me.get.ts Normal file
View File

@ -0,0 +1,38 @@
import { PrismaClient } from '@prisma/client';
import { useSession } from 'h3';
const prisma = new PrismaClient();
export default defineEventHandler(async (event) => {
// 1. Get the session
const session = await useSession(event, {
password: process.env.SESSION_PASSWORD || 'your-super-secret-32-character-password',
});
// 2. Check if user is in session
if (!session.data?.user?.id) {
throw createError({
statusCode: 401,
statusMessage: 'Unauthorized',
});
}
// 3. Fetch the full user from the database
const user = await prisma.user.findUnique({
where: { id: session.data.user.id },
});
if (!user) {
// This case might happen if the user was deleted but the session still exists.
// Clear the invalid session.
await session.clear();
throw createError({
statusCode: 401,
statusMessage: 'Unauthorized',
});
}
// 4. Return user data
const { password: _password, ...userWithoutPassword } = user;
return { user: userWithoutPassword };
});

View File

@ -0,0 +1,50 @@
import { PrismaClient } from '@prisma/client';
import { hashPassword } from '../utils/password';
const prisma = new PrismaClient();
export default defineEventHandler(async (event) => {
const body = await readBody(event);
const { email, password, nickname } = body;
// 1. Validate input
if (!email || !password) {
throw createError({
statusCode: 400,
statusMessage: 'Email and password are required',
});
}
if (password.length < 8) {
throw createError({
statusCode: 400,
statusMessage: 'Password must be at least 8 characters long',
});
}
// 2. Check if user already exists
const existingUser = await prisma.user.findUnique({
where: { email },
});
if (existingUser) {
throw createError({
statusCode: 409, // Conflict
statusMessage: 'Email already in use',
});
}
// 3. Hash password and create user
const hashedPassword = await hashPassword(password);
const user = await prisma.user.create({
data: {
email,
password: hashedPassword,
nickname: nickname || 'New Smurf',
},
});
// 4. Return the new user, excluding the password
const { password: _password, ...userWithoutPassword } = user;
return { user: userWithoutPassword };
});

13
server/utils/password.ts Normal file
View File

@ -0,0 +1,13 @@
// WARNING: This is a placeholder for demonstration purposes only.
// It is NOT secure and should be replaced with a proper password hashing
// library like bcrypt or argon2 before any real-world use.
export const hashPassword = async (password: string): Promise<string> => {
// In a real implementation, you would use a salt and a strong hashing algorithm.
return `${password}-hashed`;
};
export const verifyPassword = async (password: string, hash: string): Promise<boolean> => {
// In a real implementation, the hashing library would handle this comparison securely.
return `${password}-hashed` === hash;
};