97 lines
2.6 KiB
TypeScript
97 lines
2.6 KiB
TypeScript
/**
|
|
* This is a user authentication API route demo.
|
|
* Handle user registration, login, token management, etc.
|
|
*/
|
|
import { Router, type Request, type Response } from 'express'
|
|
import bcrypt from 'bcryptjs'
|
|
import { z } from 'zod'
|
|
import { signToken, toPublicUser } from '../auth.js'
|
|
import { createUser, findUserByUsername } from '../db.js'
|
|
import { requireAuth } from '../middleware/requireAuth.js'
|
|
import type { AuthedRequest } from '../middleware/requireAuth.js'
|
|
import type { Role } from '../../shared/types.js'
|
|
|
|
const router = Router()
|
|
|
|
/**
|
|
* User Login
|
|
* POST /api/auth/register
|
|
*/
|
|
const registerSchema = z.object({
|
|
username: z.string().min(3).max(32),
|
|
password: z.string().min(6).max(64),
|
|
email: z.string().email().optional(),
|
|
})
|
|
|
|
router.post('/register', async (req: Request, res: Response): Promise<void> => {
|
|
const parsed = registerSchema.safeParse(req.body)
|
|
if (!parsed.success) {
|
|
res.status(400).json({ success: false, error: 'BAD_REQUEST' })
|
|
return
|
|
}
|
|
|
|
const existed = await findUserByUsername(parsed.data.username)
|
|
if (existed) {
|
|
res.status(409).json({ success: false, error: 'USERNAME_TAKEN' })
|
|
return
|
|
}
|
|
|
|
const user = await createUser({
|
|
username: parsed.data.username,
|
|
password: parsed.data.password,
|
|
role: 'sales',
|
|
email: parsed.data.email,
|
|
})
|
|
|
|
const token = signToken(user)
|
|
res.json({ success: true, data: { token, user } })
|
|
})
|
|
|
|
/**
|
|
* User Login
|
|
* POST /api/auth/login
|
|
*/
|
|
const loginSchema = z.object({
|
|
username: z.string().min(1),
|
|
password: z.string().min(1),
|
|
})
|
|
|
|
router.post('/login', async (req: Request, res: Response): Promise<void> => {
|
|
const parsed = loginSchema.safeParse(req.body)
|
|
if (!parsed.success) {
|
|
res.status(400).json({ success: false, error: 'BAD_REQUEST' })
|
|
return
|
|
}
|
|
|
|
const user = await findUserByUsername(parsed.data.username)
|
|
if (!user || user.status !== 'active') {
|
|
res.status(401).json({ success: false, error: 'INVALID_CREDENTIALS' })
|
|
return
|
|
}
|
|
|
|
const ok = await bcrypt.compare(parsed.data.password, user.passwordHash)
|
|
if (!ok) {
|
|
res.status(401).json({ success: false, error: 'INVALID_CREDENTIALS' })
|
|
return
|
|
}
|
|
|
|
const pub = toPublicUser(user)
|
|
const token = signToken(pub)
|
|
res.json({ success: true, data: { token, user: pub } })
|
|
})
|
|
|
|
/**
|
|
* User Logout
|
|
* POST /api/auth/logout
|
|
*/
|
|
router.post('/logout', async (_req: Request, res: Response): Promise<void> => {
|
|
res.json({ success: true })
|
|
})
|
|
|
|
router.get('/me', requireAuth, async (req: Request, res: Response): Promise<void> => {
|
|
const user = (req as AuthedRequest).user
|
|
res.json({ success: true, data: user })
|
|
})
|
|
|
|
export default router
|