220 lines
6.6 KiB
JavaScript
220 lines
6.6 KiB
JavaScript
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(); |