import { env } from "@config/env.js";
import { AuthService } from "@services/auth.service.js";
import { logger } from "../config/logger.js";
import { CacheService } from "../utils/cache.utils.js";
// import type { IAuthRequest, ILoginRequest, IChangePasswordRequest, IAvatarUploadRequest } from '../types/auth/index.js';
/**
* Authentication controller handling login, profile, and session endpoints.
* @category Controllers
*/
export class AuthController {
/**
* Handle login and issue access/refresh tokens.
* @param {IGenericBodyResponse<ILoginDto>} req Express request with login credentials.
* @param {Response} res Express response instance.
* @returns {Promise<void>} Resolves when the response has been sent.
*/
static async login(req, res) {
try {
const credentials = req.body;
const authService = new AuthService();
const result = await authService.login(credentials);
// Set refresh token as HTTP-only cookie
res.cookie("refreshToken", result.refreshToken, {
httpOnly: true,
secure: env.NODE_ENV === "production", // Use secure cookies in production
sameSite: "strict",
maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
path: "/",
});
// Only send access token in response body
res.status(200).json({
success: true,
message: "Login successful",
data: {
accessToken: result.accessToken,
},
});
}
catch (error) {
logger.error({
error,
message: "Login controller error",
});
const message = error instanceof Error ? error.message : "Login failed";
res.status(401).json({
success: false,
message,
});
}
}
/**
* Change the authenticated user's password.
* @param {IChangePasswordPayload} req Express request containing the user context and payload.
* @param {Response} res Express response instance.
* @returns {Promise<void>} Resolves when the response has been sent.
*/
static async changePassword(req, res) {
try {
const userId = req.user?.userId;
const authService = new AuthService();
if (!userId) {
res.status(401).json({
success: false,
message: "User not found",
});
return;
}
const passwordData = req.body;
await authService.changePassword(userId, passwordData);
res.status(200).json({
success: true,
message: "Password changed successfully",
});
}
catch (error) {
logger.error({
error,
message: "Change password controller error",
});
const message = error instanceof Error ? error.message : "Password change failed";
const statusCode = message.includes("not found")
? 404
: message.includes("incorrect")
? 400
: 500;
res.status(statusCode).json({
success: false,
message,
});
}
}
/**
* Retrieve the currently authenticated user's profile.
* @param {IChangePasswordPayload} req Express request with authenticated user.
* @param {Response} res Express response instance.
* @returns {Promise<void>} Resolves when the response has been sent.
*/
static async getCurrentUser(req, res) {
try {
const userId = req.user?.userId;
const authService = new AuthService();
if (!userId) {
res.status(401).json({
success: false,
message: "Unauthorized",
});
return;
}
const user = await authService.getUser(userId);
res.status(200).json({
success: true,
data: user,
});
}
catch (error) {
logger.error({
error,
message: "Get current user controller error",
});
const message = error instanceof Error ? error.message : "Failed to get user info";
const statusCode = message.includes("not found") ? 404 : 500;
res.status(statusCode).json({
success: false,
message,
});
}
}
/**
* Update the authenticated user's avatar.
* @param {IChangeAvatarPayload} req Express request with avatar payload.
* @param {Response} res Express response instance.
* @returns {Promise<void>} Resolves when the response has been sent.
*/
static async changeAvatar(req, res) {
try {
const userId = req.user?.userId;
const authService = new AuthService();
if (!userId) {
res.status(401).json({
success: false,
message: "Unauthorized",
});
return;
}
const { avatar } = req.body;
if (!avatar) {
res.status(400).json({
success: false,
message: "Avatar data is required",
});
return;
}
const avatarUrl = await authService.changeAvatar(userId, avatar);
res.status(200).json({
success: true,
message: "Avatar changed successfully",
data: { avatarUrl },
});
}
catch (error) {
logger.error({
error,
message: "Change avatar controller error",
});
const message = error instanceof Error ? error.message : "Avatar change failed";
const statusCode = message.includes("not found")
? 404
: message.includes("Invalid") || message.includes("exceeds")
? 400
: 500;
res.status(statusCode).json({
success: false,
message,
});
}
}
// async changeAvatar(req: IAuthRequest, res: Response): Promise<void> {
// try {
// const userId = req.user!.userId;
// const avatarData: IAvatarUploadRequest = req.body as IAvatarUploadRequest;
// const avatarUrl = await authService.changeAvatar(userId, avatarData);
// res.status(200).json({
// success: true,
// message: 'Avatar changed successfully',
// data: { avatarUrl }
// });
// } catch (error) {
// logger.error('Change avatar controller error:', error);
// const message = error instanceof Error ? error.message : 'Avatar change failed';
// const statusCode = message.includes('not found') ? 404 : 500;
// res.status(statusCode).json({
// success: false,
// message
// });
// }
// }
/**
* Exchange a refresh token for a new access token.
* @param {IRefreshTokenPayload} req Express request with cookie header.
* @param {Response} res Express response instance.
* @returns {Promise<void>} Resolves when the response has been sent.
*/
static async refreshToken(req, res) {
try {
const refreshToken = req.cookies?.refreshToken;
const authService = new AuthService();
if (!refreshToken || typeof refreshToken !== "string") {
res.status(400).json({
success: false,
message: "Refresh token is required",
});
return;
}
const result = await authService.refreshToken(refreshToken);
res.status(200).json({
success: true,
data: result,
});
}
catch (error) {
logger.error({
error,
message: "Refresh token controller error",
});
const message = error instanceof Error ? error.message : "Token refresh failed";
const statusCode = message.includes("not found") ? 404 : 401;
res.status(statusCode).json({
success: false,
message,
});
}
}
/**
* Clear refresh token cookies and invalidate the session.
* @param {IAuthRequest} req Express request.
* @param {Response} res Express response instance.
* @returns {Promise<void>} Resolves when the response has been sent.
*/
static async logout(req, res) {
try {
// Delete user from cache if user ID is available (from optional auth middleware)
if (req.user?.userId) {
await CacheService.deleteUser(req.user.userId);
}
// Clear the refresh token cookie
res.clearCookie("refreshToken", {
httpOnly: true,
secure: env.NODE_ENV === "production",
sameSite: "strict",
path: "/",
});
res.status(200).json({
success: true,
message: "Logged out successfully",
});
}
catch (error) {
logger.error({
error,
message: "Logout controller error",
});
const message = error instanceof Error ? error.message : "Logout failed";
res.status(500).json({
success: false,
message,
});
}
}
/**
* Update the authenticated user's name.
* @param {IUpdateNamePayload} req Express request containing new name.
* @param {Response} res Express response instance.
* @returns {Promise<void>} Resolves when the response has been sent.
*/
static async updateOwnName(req, res) {
try {
const userId = req.user?.userId;
const authService = new AuthService();
if (!userId) {
res.status(401).json({
success: false,
message: "Unauthorized",
});
return;
}
const { name } = req.body;
await authService.updateOwnName(userId, name);
res.status(200).json({
success: true,
message: "Name updated successfully",
});
}
catch (error) {
logger.error({
error,
message: "Update own name controller error",
});
const message = error instanceof Error ? error.message : "Failed to update name";
const statusCode = message.includes("not found") ? 404 : 500;
res.status(statusCode).json({
success: false,
message,
});
}
}
}
Source