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>
This commit is contained in:
@@ -6,13 +6,15 @@ import {
|
|||||||
logLogin,
|
logLogin,
|
||||||
logLogout,
|
logLogout,
|
||||||
} from '../services/audit.service.js';
|
} 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
|
* KROK 1: Login s temporary password
|
||||||
* POST /api/auth/login
|
* POST /api/auth/login
|
||||||
*/
|
*/
|
||||||
export const login = async (req, res, next) => {
|
export const login = async (req, res, next) => {
|
||||||
const { username, password } = req.body;
|
const { username, password, rememberMe } = req.body;
|
||||||
const ipAddress = req.ip || req.connection.remoteAddress;
|
const ipAddress = req.ip || req.connection.remoteAddress;
|
||||||
const userAgent = req.headers['user-agent'];
|
const userAgent = req.headers['user-agent'];
|
||||||
|
|
||||||
@@ -37,12 +39,15 @@ export const login = async (req, res, next) => {
|
|||||||
maxAge: 60 * 60 * 1000, // 1 hodina
|
maxAge: 60 * 60 * 1000, // 1 hodina
|
||||||
});
|
});
|
||||||
|
|
||||||
res.cookie('refreshToken', result.tokens.refreshToken, {
|
// Refresh token iba ak user chce zostať prihlásený
|
||||||
httpOnly: true,
|
if (rememberMe) {
|
||||||
secure: isProduction,
|
res.cookie('refreshToken', result.tokens.refreshToken, {
|
||||||
sameSite: isProduction ? 'strict' : 'lax',
|
httpOnly: true,
|
||||||
maxAge: 7 * 24 * 60 * 60 * 1000, // 7 dní
|
secure: isProduction,
|
||||||
});
|
sameSite: isProduction ? 'strict' : 'lax',
|
||||||
|
maxAge: 7 * 24 * 60 * 60 * 1000, // 7 dní
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
success: true,
|
success: true,
|
||||||
@@ -62,6 +67,68 @@ export const login = async (req, res, next) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
* KROK 2: Nastavenie nového hesla
|
||||||
* POST /api/auth/set-password
|
* POST /api/auth/set-password
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ router.post(
|
|||||||
authController.login
|
authController.login
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Refresh access token (public - uses refresh token from cookie)
|
||||||
|
router.post('/refresh', authController.refreshToken);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Protected routes (vyžadujú autentifikáciu)
|
* Protected routes (vyžadujú autentifikáciu)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export const loginSchema = z.object({
|
|||||||
invalid_type_error: 'Heslo musí byť text',
|
invalid_type_error: 'Heslo musí byť text',
|
||||||
})
|
})
|
||||||
.min(1, 'Heslo nemôže byť prázdne'),
|
.min(1, 'Heslo nemôže byť prázdne'),
|
||||||
|
rememberMe: z.boolean().optional().default(false),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set new password schema (krok 2)
|
// Set new password schema (krok 2)
|
||||||
|
|||||||
Reference in New Issue
Block a user