feat: pin not working when rate limited redirect fix & allow non https baseUrl pin fix (#32)

* feat: ratelimit pin not working with baseUrl fix

* Remove white space changes

* Refactor PIN verification error handling and input state management

- Improve error handling in login page JavaScript
- Standardize API response structure with explicit success and error fields
- Enhance user feedback for PIN authentication failures
- Implement more robust input state management during login attempts

* Fix PIN verification logic in root route

- Improve PIN verification check to handle missing cookie scenario
- Add explicit check for cookie existence before comparing PIN
- Enhance root route authentication logic for more robust access control
This commit is contained in:
Chris
2025-02-24 10:56:57 -08:00
committed by GitHub
parent d42ca55c08
commit c6a969b5cd
3 changed files with 28 additions and 22 deletions

View File

@@ -78,7 +78,6 @@
const createPinInputs = (length) => { const createPinInputs = (length) => {
const form = document.getElementById('pin-form'); const form = document.getElementById('pin-form');
form.innerHTML = ''; // Clear existing inputs form.innerHTML = ''; // Clear existing inputs
for (let i = 0; i < length; i++) { for (let i = 0; i < length; i++) {
const input = document.createElement('input'); const input = document.createElement('input');
input.type = 'password'; input.type = 'password';
@@ -130,29 +129,32 @@
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ pin }) body: JSON.stringify({ pin })
}); });
const data = await response.json(); const data = await response.json();
// Simplified success and error handling
if (data.success) { if (data.success) {
window.location.href = '/'; window.location.href = '/';
} else { } else {
// Show error message // Show error message
document.getElementById('pin-error').textContent = data.error; document.getElementById('pin-error').textContent = data.error || 'Authentication failed';
// Only clear inputs and focus if not locked out // Determine if it's a lockout scenario
if (!data.error.includes('try again in')) { const isLockedOut = data.error.includes('Too many PIN verification attempts');
const inputs = [...document.querySelectorAll('.pin-digit')];
const inputs = [...document.querySelectorAll('.pin-digit')];
if (isLockedOut) {
// Disable inputs if locked out
inputs.forEach(input => {
input.disabled = true;
input.classList.add('locked');
});
} else {
// Reset inputs for other error types
inputs.forEach(input => { inputs.forEach(input => {
input.value = ''; input.value = '';
input.classList.remove('filled'); input.classList.remove('filled');
}); });
inputs[0].focus(); inputs[0].focus();
} else {
// If locked out, disable all inputs
const inputs = [...document.querySelectorAll('.pin-digit')];
inputs.forEach(input => {
input.disabled = true;
input.classList.add('locked');
});
} }
} }
} catch (err) { } catch (err) {
@@ -239,4 +241,4 @@
}); });
</script> </script>
</body> </body>
</html> </html>

View File

@@ -14,6 +14,7 @@ const { config, validateConfig } = require('./config');
const logger = require('./utils/logger'); const logger = require('./utils/logger');
const { ensureDirectoryExists } = require('./utils/fileUtils'); const { ensureDirectoryExists } = require('./utils/fileUtils');
const { securityHeaders, requirePin } = require('./middleware/security'); const { securityHeaders, requirePin } = require('./middleware/security');
const { safeCompare } = require('./utils/security');
const { initUploadLimiter, pinVerifyLimiter, downloadLimiter } = require('./middleware/rateLimiter'); const { initUploadLimiter, pinVerifyLimiter, downloadLimiter } = require('./middleware/rateLimiter');
// Create Express app // Create Express app
@@ -40,7 +41,8 @@ app.use('/api/files', requirePin(config.pin), downloadLimiter, fileRoutes);
// Root route // Root route
app.get('/', (req, res) => { app.get('/', (req, res) => {
if (config.pin && !req.cookies.DUMBDROP_PIN) { // Check if the PIN is configured and the cookie exists
if (config.pin && (!req.cookies?.DUMBDROP_PIN || !safeCompare(req.cookies.DUMBDROP_PIN, config.pin))) {
return res.redirect('/login.html'); return res.redirect('/login.html');
} }

View File

@@ -24,11 +24,11 @@ router.post('/verify-pin', (req, res) => {
if (!config.pin) { if (!config.pin) {
res.cookie('DUMBDROP_PIN', '', { res.cookie('DUMBDROP_PIN', '', {
httpOnly: true, httpOnly: true,
secure: process.env.NODE_ENV === 'production', secure: req.secure || (process.env.NODE_ENV === 'production' && config.baseUrl.startsWith('https')),
sameSite: 'strict', sameSite: 'strict',
path: '/' path: '/'
}); });
return res.json({ success: true }); return res.json({ success: true, error: null });
} }
// Validate PIN format // Validate PIN format
@@ -36,6 +36,7 @@ router.post('/verify-pin', (req, res) => {
if (!cleanedPin) { if (!cleanedPin) {
logger.warn(`Invalid PIN format from IP: ${ip}`); logger.warn(`Invalid PIN format from IP: ${ip}`);
return res.status(401).json({ return res.status(401).json({
success: false,
error: 'Invalid PIN format. PIN must be 4-10 digits.' error: 'Invalid PIN format. PIN must be 4-10 digits.'
}); });
} }
@@ -49,7 +50,8 @@ router.post('/verify-pin', (req, res) => {
logger.warn(`Login attempt from locked out IP: ${ip}`); logger.warn(`Login attempt from locked out IP: ${ip}`);
return res.status(429).json({ return res.status(429).json({
error: `Too many attempts. Please try again in ${timeLeft} minutes.` success: false,
error: `Too many PIN verification attempts. Please try again in ${timeLeft} minutes.`
}); });
} }
@@ -61,13 +63,13 @@ router.post('/verify-pin', (req, res) => {
// Set secure cookie with cleaned PIN // Set secure cookie with cleaned PIN
res.cookie('DUMBDROP_PIN', cleanedPin, { res.cookie('DUMBDROP_PIN', cleanedPin, {
httpOnly: true, httpOnly: true,
secure: process.env.NODE_ENV === 'production', secure: req.secure || (process.env.NODE_ENV === 'production' && config.baseUrl.startsWith('https')),
sameSite: 'strict', sameSite: 'strict',
path: '/' path: '/'
}); });
logger.info(`Successful PIN verification from IP: ${ip}`); logger.info(`Successful PIN verification from IP: ${ip}`);
res.json({ success: true }); res.json({ success: true, error: null });
} else { } else {
// Record failed attempt // Record failed attempt
const attempts = recordAttempt(ip); const attempts = recordAttempt(ip);
@@ -78,12 +80,12 @@ router.post('/verify-pin', (req, res) => {
success: false, success: false,
error: attemptsLeft > 0 ? error: attemptsLeft > 0 ?
`Invalid PIN. ${attemptsLeft} attempts remaining.` : `Invalid PIN. ${attemptsLeft} attempts remaining.` :
'Too many attempts. Account locked for 15 minutes.' 'Too many PIN verification attempts. Account locked for 15 minutes.'
}); });
} }
} catch (err) { } catch (err) {
logger.error(`PIN verification error: ${err.message}`); logger.error(`PIN verification error: ${err.message}`);
res.status(500).json({ error: 'Authentication failed' }); res.status(500).json({ success: false, error: 'Authentication failed' });
} }
}); });