Admin role added, dev side has hot reload again, characters AND groups tied to accounts now.
This commit is contained in:
135
server/src/routes/auth.ts
Normal file
135
server/src/routes/auth.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
import { Hono } from 'hono';
|
||||
import { prisma } from '../db';
|
||||
import { hashPassword, verifyPassword } from '../utils/password';
|
||||
import { createSession, destroySession, requireAuth } from '../middleware/session';
|
||||
|
||||
const auth = new Hono();
|
||||
|
||||
/**
|
||||
* POST /api/register
|
||||
* Create a new user account
|
||||
*/
|
||||
auth.post('/register', async (c) => {
|
||||
const body = await c.req.json();
|
||||
const { username, email, password } = body;
|
||||
|
||||
// Validation
|
||||
if (!username || !email || !password) {
|
||||
return c.json({ error: 'Username, email, and password are required' }, 400);
|
||||
}
|
||||
|
||||
if (username.length < 3 || username.length > 30) {
|
||||
return c.json({ error: 'Username must be 3-30 characters' }, 400);
|
||||
}
|
||||
|
||||
if (password.length < 8) {
|
||||
return c.json({ error: 'Password must be at least 8 characters' }, 400);
|
||||
}
|
||||
|
||||
// Check if username or email already exists
|
||||
const existingUser = await prisma.user.findFirst({
|
||||
where: {
|
||||
OR: [{ username }, { email }],
|
||||
},
|
||||
});
|
||||
|
||||
if (existingUser) {
|
||||
if (existingUser.username === username) {
|
||||
return c.json({ error: 'Username already taken' }, 400);
|
||||
}
|
||||
return c.json({ error: 'Email already registered' }, 400);
|
||||
}
|
||||
|
||||
// Create user
|
||||
const passwordHash = await hashPassword(password);
|
||||
const user = await prisma.user.create({
|
||||
data: {
|
||||
username,
|
||||
email,
|
||||
passwordHash,
|
||||
},
|
||||
});
|
||||
|
||||
// Create session
|
||||
await createSession(c, user.id);
|
||||
|
||||
return c.json({
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
role: user.role,
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* POST /api/login
|
||||
* Authenticate and create session
|
||||
*/
|
||||
auth.post('/login', async (c) => {
|
||||
const body = await c.req.json();
|
||||
const { username, password } = body;
|
||||
|
||||
if (!username || !password) {
|
||||
return c.json({ error: 'Username and password are required' }, 400);
|
||||
}
|
||||
|
||||
// Find user by username or email
|
||||
const user = await prisma.user.findFirst({
|
||||
where: {
|
||||
OR: [{ username }, { email: username }],
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return c.json({ error: 'Invalid username or password' }, 401);
|
||||
}
|
||||
|
||||
// Verify password
|
||||
const isValid = await verifyPassword(password, user.passwordHash);
|
||||
if (!isValid) {
|
||||
return c.json({ error: 'Invalid username or password' }, 401);
|
||||
}
|
||||
|
||||
// Create session
|
||||
await createSession(c, user.id);
|
||||
|
||||
return c.json({
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
role: user.role,
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* POST /api/logout
|
||||
* Destroy session
|
||||
*/
|
||||
auth.post('/logout', async (c) => {
|
||||
await destroySession(c);
|
||||
return c.json({ success: true });
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /api/auth/status
|
||||
* Check if user is authenticated
|
||||
*/
|
||||
auth.get('/auth/status', (c) => {
|
||||
const user = c.get('user');
|
||||
return c.json({
|
||||
authenticated: !!user,
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /api/me
|
||||
* Get current user info
|
||||
*/
|
||||
auth.get('/me', requireAuth, (c) => {
|
||||
const user = c.get('user');
|
||||
return c.json({
|
||||
username: user!.username,
|
||||
email: user!.email,
|
||||
role: user!.role,
|
||||
});
|
||||
});
|
||||
|
||||
export default auth;
|
||||
Reference in New Issue
Block a user