- 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>
275 lines
6.4 KiB
JavaScript
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);
|
|
}
|
|
};
|