Files
crm-server/src/controllers/auth.controller.js
richardtekula dd15be93a9 feat: Add refresh token endpoint and remember me support
- Add POST /auth/refresh endpoint for token renewal
- Only set refresh token cookie when rememberMe is true
- Add rememberMe field to login validator schema

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 07:21:58 +01:00

275 lines
6.4 KiB
JavaScript

import * as authService from '../services/auth.service.js';
import {
logLoginAttempt,
logPasswordChange,
logEmailLink,
logLogin,
logLogout,
} from '../services/audit.service.js';
import { verifyRefreshToken, generateAccessToken } from '../utils/jwt.js';
import { getUserById } from '../services/auth.service.js';
/**
* KROK 1: Login s temporary password
* POST /api/auth/login
*/
export const login = async (req, res, next) => {
const { username, password, rememberMe } = req.body;
const ipAddress = req.ip || req.connection.remoteAddress;
const userAgent = req.headers['user-agent'];
try {
const result = await authService.loginWithTempPassword(
username,
password,
ipAddress,
userAgent
);
// Log successful login
await logLoginAttempt(username, true, ipAddress, userAgent);
await logLogin(result.user.id, username, ipAddress, userAgent);
// Nastav cookie s access tokenom (httpOnly, secure)
const isProduction = process.env.NODE_ENV === 'production';
res.cookie('accessToken', result.tokens.accessToken, {
httpOnly: true,
secure: isProduction,
sameSite: isProduction ? 'strict' : 'lax',
maxAge: 60 * 60 * 1000, // 1 hodina
});
// Refresh token iba ak user chce zostať prihlásený
if (rememberMe) {
res.cookie('refreshToken', result.tokens.refreshToken, {
httpOnly: true,
secure: isProduction,
sameSite: isProduction ? 'strict' : 'lax',
maxAge: 7 * 24 * 60 * 60 * 1000, // 7 dní
});
}
res.status(200).json({
success: true,
data: {
user: result.user,
tokens: result.tokens,
needsPasswordChange: result.needsPasswordChange,
needsEmailSetup: result.needsEmailSetup,
},
message: 'Prihlásenie úspešné',
});
} catch (error) {
// Log failed login
await logLoginAttempt(username, false, ipAddress, userAgent, error.message);
next(error);
}
};
/**
* Refresh access token using refresh token
* POST /api/auth/refresh
*/
export const refreshToken = async (req, res, next) => {
try {
const token = req.cookies?.refreshToken;
if (!token) {
return res.status(401).json({
success: false,
error: {
message: 'Refresh token nie je k dispozícii',
statusCode: 401,
},
});
}
// Verify refresh token
const decoded = verifyRefreshToken(token);
// Get user from DB
const user = await getUserById(decoded.id);
// Generate new access token
const newAccessToken = generateAccessToken({
id: user.id,
username: user.username,
role: user.role,
});
// Set new access token cookie
const isProduction = process.env.NODE_ENV === 'production';
res.cookie('accessToken', newAccessToken, {
httpOnly: true,
secure: isProduction,
sameSite: isProduction ? 'strict' : 'lax',
maxAge: 60 * 60 * 1000, // 1 hodina
});
res.status(200).json({
success: true,
data: {
accessToken: newAccessToken,
},
message: 'Token obnovený',
});
} catch (error) {
// Clear invalid cookies
res.clearCookie('accessToken');
res.clearCookie('refreshToken');
return res.status(401).json({
success: false,
error: {
message: 'Refresh token expiroval alebo je neplatný',
statusCode: 401,
},
});
}
};
/**
* KROK 2: Nastavenie nového hesla
* POST /api/auth/set-password
* Requires: authentication
*/
export const setPassword = async (req, res, next) => {
const { newPassword } = req.body;
const userId = req.userId;
const ipAddress = req.ip || req.connection.remoteAddress;
const userAgent = req.headers['user-agent'];
try {
const result = await authService.setNewPassword(userId, newPassword);
// Log password change
await logPasswordChange(userId, ipAddress, userAgent);
res.status(200).json({
success: true,
data: result,
message: 'Heslo úspešne nastavené',
});
} catch (error) {
next(error);
}
};
/**
* KROK 3: Pripojenie emailu s JMAP validáciou
* POST /api/auth/link-email
* Requires: authentication
*/
export const linkEmail = async (req, res, next) => {
const { email, emailPassword } = req.body;
const userId = req.userId;
const ipAddress = req.ip || req.connection.remoteAddress;
const userAgent = req.headers['user-agent'];
try {
const result = await authService.linkEmail(userId, email, emailPassword);
// Log email link
await logEmailLink(userId, email, ipAddress, userAgent);
res.status(200).json({
success: true,
data: {
email,
accountId: result.accountId,
},
message: 'Email účet úspešne pripojený a overený',
});
} catch (error) {
next(error);
}
};
/**
* KROK 3 (alternatíva): Skip email setup
* POST /api/auth/skip-email
* Requires: authentication
*/
export const skipEmail = async (req, res, next) => {
const userId = req.userId;
try {
const result = await authService.skipEmailSetup(userId);
res.status(200).json({
success: true,
data: result,
message: 'Email setup preskočený',
});
} catch (error) {
next(error);
}
};
/**
* Logout
* POST /api/auth/logout
* Requires: authentication
*/
export const logout = async (req, res, next) => {
try {
const userId = req.userId;
const ipAddress = req.ip || req.connection.remoteAddress;
const userAgent = req.headers['user-agent'];
// Log logout event
await logLogout(userId, ipAddress, userAgent);
const result = await authService.logout();
// Vymaž cookies
res.clearCookie('accessToken');
res.clearCookie('refreshToken');
res.status(200).json({
success: true,
message: result.message,
});
} catch (error) {
next(error);
}
};
/**
* Získanie aktuálnej session info
* GET /api/auth/session
* Requires: authentication
*/
export const getSession = async (req, res, next) => {
try {
res.status(200).json({
success: true,
data: {
user: req.user,
authenticated: true,
},
});
} catch (error) {
next(error);
}
};
/**
* Profil aktuálneho usera
* GET /api/auth/me
* Requires: authentication
*/
export const getMe = async (req, res, next) => {
try {
res.status(200).json({
success: true,
data: {
user: req.user,
},
});
} catch (error) {
next(error);
}
};