feat: auth endpoints (session-based)
This commit is contained in:
parent
0f85059e92
commit
2c48c8b55b
56
server/api/auth/login.post.ts
Normal file
56
server/api/auth/login.post.ts
Normal 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 };
|
||||||
|
});
|
||||||
11
server/api/auth/logout.post.ts
Normal file
11
server/api/auth/logout.post.ts
Normal 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
38
server/api/auth/me.get.ts
Normal 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 };
|
||||||
|
});
|
||||||
50
server/api/auth/register.post.ts
Normal file
50
server/api/auth/register.post.ts
Normal 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
13
server/utils/password.ts
Normal 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;
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue
Block a user