const path = require('path'); const { models } = require(path.resolve(process.env.ROOT_PATH, './db/db.js')); const BaseController = require(path.resolve(process.env.ROOT_PATH, './controllers/BaseController.js')); const jwt = require('jsonwebtoken'); const bcrypt = require('bcrypt'); const crypto = require('crypto'); const { format, differenceInHours, addHours } = require('date-fns'); class AuthController extends BaseController { constructor() { super(); this.protected('me', 'refreshToken', 'login'); this.admin('register'); } /** * @swagger * /auth/login: * post: * summary: Authenticate a user * tags: [Authentication] * requestBody: * required: true * content: * application/json: * schema: * type: object * required: * - username * - password * properties: * username: * type: string * description: Username or email * password: * type: string * format: password * responses: * 200: * description: Login successful * content: * application/json: * schema: * type: object * properties: * api_key: * type: string * description: JWT token for authentication * 401: * description: Invalid credentials */ async login(req, res) { const { username, password } = req.body; const user = await models.User.query().where('username', username).orWhere('email', username).first(); if (!user) { return res.status(401).json({ error: 'Invalid username or password' }); } const passwordMatch = await bcrypt.compare(password, user.password); if (!passwordMatch) { return res.status(401).json({ error: 'Invalid username or password' }); } const token = jwt.sign({ id: user.id, username: user.username, is_admin: user.is_admin, email: user.email }, process.env.JWT_SECRET, { expiresIn: '6h' }); res.json({ api_key: token }); } /** * @swagger * /auth/me: * get: * summary: Get current user details * tags: [Authentication] * security: * - bearerAuth: [] * responses: * 200: * description: User details * content: * application/json: * schema: * type: object * properties: * id: * type: integer * username: * type: string * email: * type: string * is_admin: * type: boolean * is_active: * type: boolean * created: * type: string * format: date-time * modified: * type: string * format: date-time * token_expiration: * type: string * format: date-time * is_token_expired: * type: boolean * 401: * description: Unauthorized */ async me(req, res) { const user = await models.User.query().findById(req.user.id); const token = req.headers.authorization?.split(' ')[1]; const decodedToken = token ? jwt.decode(token) : null; const tokenExpiration = decodedToken?.exp ? new Date(decodedToken.exp * 1000) : null; const isTokenExpired = tokenExpiration ? tokenExpiration < new Date() : true; res.json({ id: user.id, username: user.username, email: user.email, is_admin: user.is_admin, is_active: user.is_active, created: user.created, modified: user.modified, token_expiration: tokenExpiration, is_token_expired: isTokenExpired }); } /** * @swagger * /auth/refresh-token: * post: * summary: Refresh authentication token * tags: [Authentication] * security: * - bearerAuth: [] * responses: * 200: * description: Token refreshed successfully * content: * application/json: * schema: * type: object * properties: * api_key: * type: string * description: New JWT token * 401: * description: Unauthorized */ async refreshToken(req, res) { const user = await models.User.query().findById(req.user.id); const token = jwt.sign({ id: user.id, username: user.username, is_admin: user.is_admin, email: user.email }, process.env.JWT_SECRET, { expiresIn: '6h' }); res.json({ api_key: token }); } async registerView(req, res) { res.render('auth/register'); } async register(req, res) { const { username, email, password } = req.body; const invite_token = crypto.randomBytes(32).toString('hex'); const salt = await bcrypt.genSalt(10); const passwordEncrypted = await bcrypt.hash(password, salt); const user = await models.User.query().insert({ username, password: passwordEncrypted, email, is_admin: 0, is_active: 0, created: format(new Date(), 'yyyy-MM-dd HH:mm:ss'), modified: format(new Date(), 'yyyy-MM-dd HH:mm:ss') }); await models.Invite.query().insert({ user_id: user.id, token: invite_token, expires_at: format(addHours(new Date(), 12), 'yyyy-MM-dd HH:mm:ss') }); res.json({ message: 'User registered successfully', invite_token }); } async activateView(req, res) { res.render('auth/activate'); } async activate(req, res) { const { invite_token } = req.body; const invite = await models.Invite.query().where('token', invite_token).first(); if (!invite) { return res.status(400).json({ error: 'Invalid invite token' }); } if (differenceInHours(new Date(), new Date(invite.expires_at)) > 0) { return res.status(400).json({ error: 'Invite token expired' }); } const user = await models.User.query().where('id', invite.user_id).first(); if (!user) { return res.status(400).json({ error: 'User not found' }); } await models.User.query().update({ is_active: 1, modified: format(new Date(), 'yyyy-MM-dd HH:mm:ss') }).where('id', user.id); res.json({ message: 'User activated successfully', invite_token }); } } module.exports = new AuthController();