enterprisecargo / src /services /authService.js
vish85521's picture
Upload 64 files
eeb3436 verified
const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken");
const { AppError } = require("../utils/appError");
const ACTIVE_USER_STATUS = "ACTIVE";
const ACCESS_TOKEN_TYPE = "access";
const REFRESH_TOKEN_TYPE = "refresh";
function normalizeRoleCodes(rawRoles) {
if (!Array.isArray(rawRoles)) {
return [];
}
const unique = new Set();
for (const role of rawRoles) {
if (typeof role !== "string") {
continue;
}
const normalized = role.trim().toLowerCase();
if (normalized) {
unique.add(normalized);
}
}
return Array.from(unique.values());
}
function toPublicUser(userRecord) {
return {
id: userRecord.id,
email: userRecord.email,
fullName: userRecord.full_name,
status: userRecord.status,
isActive: userRecord.is_active,
lastLoginAt: userRecord.last_login_at,
tenant: {
id: userRecord.tenant_id,
code: userRecord.tenant_code,
name: userRecord.tenant_name,
isActive: userRecord.tenant_is_active,
},
roles: normalizeRoleCodes(userRecord.roles),
};
}
function assertAccountIsActive(userRecord) {
if (!userRecord) {
throw new AppError("Invalid email or password.", 401);
}
if (userRecord.deleted_at) {
throw new AppError("User account is disabled.", 403, {
code: "USER_DELETED",
});
}
if (!userRecord.is_active || String(userRecord.status || "").toUpperCase() !== ACTIVE_USER_STATUS) {
throw new AppError("User account is disabled.", 403, {
code: "USER_DISABLED",
});
}
if (!userRecord.tenant_is_active) {
throw new AppError("Tenant is inactive.", 403, {
code: "TENANT_INACTIVE",
});
}
const roles = normalizeRoleCodes(userRecord.roles);
if (roles.length === 0) {
throw new AppError("User account has no assigned roles.", 403, {
code: "ROLE_MISSING",
});
}
}
function createAuthService({ config, pool, authRepository }) {
const authConfig = config.auth;
function signToken(userRecord, tokenType) {
const secret =
tokenType === ACCESS_TOKEN_TYPE
? authConfig.jwtAccessSecret
: authConfig.jwtRefreshSecret;
const expiresIn =
tokenType === ACCESS_TOKEN_TYPE
? authConfig.jwtAccessExpiresIn
: authConfig.jwtRefreshExpiresIn;
const roles = normalizeRoleCodes(userRecord.roles);
return jwt.sign(
{
tokenType,
tenantId: userRecord.tenant_id,
tenantCode: userRecord.tenant_code,
roles,
},
secret,
{
subject: String(userRecord.id),
expiresIn,
issuer: authConfig.jwtIssuer,
audience: authConfig.jwtAudience,
}
);
}
function issueTokenPair(userRecord) {
return {
tokenType: "Bearer",
accessToken: signToken(userRecord, ACCESS_TOKEN_TYPE),
refreshToken: signToken(userRecord, REFRESH_TOKEN_TYPE),
accessTokenExpiresIn: authConfig.jwtAccessExpiresIn,
refreshTokenExpiresIn: authConfig.jwtRefreshExpiresIn,
};
}
function verifyToken(token, tokenType) {
const secret =
tokenType === ACCESS_TOKEN_TYPE
? authConfig.jwtAccessSecret
: authConfig.jwtRefreshSecret;
let decoded;
try {
decoded = jwt.verify(token, secret, {
issuer: authConfig.jwtIssuer,
audience: authConfig.jwtAudience,
});
} catch (error) {
throw new AppError("Invalid or expired token.", 401, {
code: "TOKEN_INVALID",
});
}
if (typeof decoded !== "object" || decoded === null) {
throw new AppError("Invalid token payload.", 401, {
code: "TOKEN_PAYLOAD_INVALID",
});
}
if (decoded.tokenType !== tokenType) {
throw new AppError("Invalid token type.", 401, {
code: "TOKEN_TYPE_INVALID",
});
}
return decoded;
}
async function getAuthenticatedUserById(userId) {
const userRecord = await authRepository.getUserAuthById(pool, userId);
if (!userRecord) {
throw new AppError("User not found.", 401, {
code: "USER_NOT_FOUND",
});
}
assertAccountIsActive(userRecord);
const user = toPublicUser(userRecord);
return {
...user,
isSuperAdmin: user.roles.includes("super_admin"),
};
}
async function login(input) {
const email = String(input?.email || "").trim();
const password = String(input?.password || "");
if (!email || !password) {
throw new AppError("Email and password are required.", 400);
}
const userRecord = await authRepository.findUserAuthByEmail(pool, email);
if (!userRecord) {
throw new AppError("Invalid email or password.", 401);
}
const passwordMatches = await bcrypt.compare(password, userRecord.password_hash || "");
if (!passwordMatches) {
throw new AppError("Invalid email or password.", 401);
}
assertAccountIsActive(userRecord);
await authRepository.touchLastLoginAt(pool, userRecord.id);
const refreshedUser = await authRepository.getUserAuthById(pool, userRecord.id);
assertAccountIsActive(refreshedUser);
return {
user: toPublicUser(refreshedUser),
tokens: issueTokenPair(refreshedUser),
};
}
async function refresh(refreshToken) {
if (!refreshToken || typeof refreshToken !== "string") {
throw new AppError("refreshToken is required.", 400);
}
const decoded = verifyToken(refreshToken, REFRESH_TOKEN_TYPE);
const userId = String(decoded.sub || "").trim();
if (!userId) {
throw new AppError("Invalid token subject.", 401);
}
const userRecord = await authRepository.getUserAuthById(pool, userId);
assertAccountIsActive(userRecord);
return {
user: toPublicUser(userRecord),
tokens: issueTokenPair(userRecord),
};
}
function verifyAccessToken(accessToken) {
return verifyToken(accessToken, ACCESS_TOKEN_TYPE);
}
return {
login,
refresh,
verifyAccessToken,
getAuthenticatedUserById,
};
}
module.exports = {
createAuthService,
};