Production creates the SuperAdmin from env credentials, and dashboard seeding is authenticated, SuperAdmin-only, and locked after the first run.
112 lines
3.0 KiB
JavaScript
112 lines
3.0 KiB
JavaScript
// /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 { isBootstrapSuperAdminDisabled } = require('../../utils/superAdmin');
|
|
|
|
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 || isBootstrapSuperAdminDisabled(user)) {
|
|
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 || isBootstrapSuperAdminDisabled(user)) {
|
|
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
|
|
};
|