Initial commit: teaching institution management API.
Express/MongoDB backend with JWT auth, RBAC, S3 uploads, notifications, and scheduled jobs.
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
// /components/auth/authController.js
|
||||
|
||||
const catchAsync = require('../../utils/catchAsync');
|
||||
const authService = require('./authService');
|
||||
const { successResponse } = require('../../utils/apiResponse');
|
||||
|
||||
exports.login = catchAsync(async (req, res, next) => {
|
||||
const { username, password } = req.body;
|
||||
const result = await authService.login(username, password);
|
||||
return successResponse(res, 200, 'Login successful', result);
|
||||
});
|
||||
|
||||
exports.refresh = catchAsync(async (req, res, next) => {
|
||||
const { refreshToken } = req.body;
|
||||
const result = await authService.refreshToken(refreshToken);
|
||||
return successResponse(res, 200, 'Tokens refreshed successfully', result);
|
||||
});
|
||||
|
||||
exports.logout = catchAsync(async (req, res, next) => {
|
||||
const { refreshToken } = req.body;
|
||||
const userId = req.user?._id;
|
||||
await authService.logout(userId, refreshToken);
|
||||
return successResponse(res, 200, 'Logout successful');
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
// /components/auth/authRoutes.js
|
||||
|
||||
const express = require('express');
|
||||
const authController = require('./authController');
|
||||
const { validateLogin, validateRefresh } = require('./authValidator');
|
||||
const authMiddleware = require('../../middlewares/authMiddleware');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.post('/login', validateLogin, authController.login);
|
||||
router.post('/refresh', validateRefresh, authController.refresh);
|
||||
router.post('/logout', authMiddleware, authController.logout);
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,110 @@
|
||||
// /components/auth/authService.js
|
||||
|
||||
const jwt = require('jsonwebtoken');
|
||||
const bcrypt = require('bcryptjs');
|
||||
const User = require('../users/userModel');
|
||||
const config = require('../../config/config');
|
||||
const AppError = require('../../utils/AppError');
|
||||
|
||||
const generateTokens = (user) => {
|
||||
const payload = {
|
||||
id: user._id,
|
||||
username: user.username,
|
||||
role: user.role?._id || user.role
|
||||
};
|
||||
|
||||
const accessToken = jwt.sign(payload, config.JWT_ACCESS_SECRET, {
|
||||
expiresIn: config.JWT_ACCESS_EXPIRES_IN
|
||||
});
|
||||
|
||||
const refreshToken = jwt.sign({ id: user._id }, config.JWT_REFRESH_SECRET, {
|
||||
expiresIn: config.JWT_REFRESH_EXPIRES_IN
|
||||
});
|
||||
|
||||
return { accessToken, refreshToken };
|
||||
};
|
||||
|
||||
const login = async (username, password) => {
|
||||
const user = await User.findOne({ username }).populate('role');
|
||||
if (!user || !user.isActive) {
|
||||
throw new AppError('INVALID_CREDENTIALS');
|
||||
}
|
||||
|
||||
const isMatch = await bcrypt.compare(password, user.passwordHash);
|
||||
if (!isMatch) {
|
||||
throw new AppError('INVALID_CREDENTIALS');
|
||||
}
|
||||
|
||||
const { accessToken, refreshToken: refreshTokenString } = generateTokens(user);
|
||||
|
||||
const expiresAt = new Date();
|
||||
expiresAt.setDate(expiresAt.getDate() + 7);
|
||||
|
||||
user.refreshTokens.push({ token: refreshTokenString, expiresAt });
|
||||
await user.save();
|
||||
|
||||
const userObject = user.toObject();
|
||||
delete userObject.passwordHash;
|
||||
delete userObject.refreshTokens;
|
||||
|
||||
return {
|
||||
user: userObject,
|
||||
accessToken,
|
||||
refreshToken: refreshTokenString
|
||||
};
|
||||
};
|
||||
|
||||
const refreshToken = async (refreshTokenString) => {
|
||||
let decoded;
|
||||
try {
|
||||
decoded = jwt.verify(refreshTokenString, config.JWT_REFRESH_SECRET);
|
||||
} catch (err) {
|
||||
throw new AppError('INVALID_REFRESH_TOKEN');
|
||||
}
|
||||
|
||||
const user = await User.findById(decoded.id).populate('role');
|
||||
if (!user || !user.isActive) {
|
||||
throw new AppError('INVALID_REFRESH_TOKEN');
|
||||
}
|
||||
|
||||
const tokenIndex = user.refreshTokens.findIndex(rt => rt.token === refreshTokenString);
|
||||
if (tokenIndex === -1) {
|
||||
throw new AppError('INVALID_REFRESH_TOKEN');
|
||||
}
|
||||
|
||||
if (new Date() > new Date(user.refreshTokens[tokenIndex].expiresAt)) {
|
||||
user.refreshTokens.splice(tokenIndex, 1);
|
||||
await user.save();
|
||||
throw new AppError('TOKEN_EXPIRED');
|
||||
}
|
||||
|
||||
user.refreshTokens.splice(tokenIndex, 1);
|
||||
|
||||
const { accessToken, refreshToken: newRefreshToken } = generateTokens(user);
|
||||
|
||||
const expiresAt = new Date();
|
||||
expiresAt.setDate(expiresAt.getDate() + 7);
|
||||
|
||||
user.refreshTokens.push({ token: newRefreshToken, expiresAt });
|
||||
await user.save();
|
||||
|
||||
return {
|
||||
accessToken,
|
||||
refreshToken: newRefreshToken
|
||||
};
|
||||
};
|
||||
|
||||
const logout = async (userId, refreshTokenString) => {
|
||||
const user = await User.findById(userId);
|
||||
if (user) {
|
||||
user.refreshTokens = user.refreshTokens.filter(rt => rt.token !== refreshTokenString);
|
||||
await user.save();
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
login,
|
||||
refreshToken,
|
||||
logout
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
// Stub validator — pass-through middleware (no validation yet)
|
||||
const passThrough = (req, res, next) => next();
|
||||
|
||||
module.exports = new Proxy({}, {
|
||||
get: () => passThrough
|
||||
});
|
||||
Reference in New Issue
Block a user