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 form = document.getElementById('pin-form');
form.innerHTML = ''; // Clear existing inputs
for (let i = 0; i < length; i++) {
const input = document.createElement('input');
input.type = 'password';
@@ -130,29 +129,32 @@
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ pin })
});
const data = await response.json();
// Simplified success and error handling
if (data.success) {
window.location.href = '/';
} else {
// 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
if (!data.error.includes('try again in')) {
const inputs = [...document.querySelectorAll('.pin-digit')];
// Determine if it's a lockout scenario
const isLockedOut = data.error.includes('Too many PIN verification attempts');
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 => {
input.value = '';
input.classList.remove('filled');
});
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) {
@@ -239,4 +241,4 @@
});
</script>
</body>
</html>
</html>

View File

@@ -14,6 +14,7 @@ const { config, validateConfig } = require('./config');
const logger = require('./utils/logger');
const { ensureDirectoryExists } = require('./utils/fileUtils');
const { securityHeaders, requirePin } = require('./middleware/security');
const { safeCompare } = require('./utils/security');
const { initUploadLimiter, pinVerifyLimiter, downloadLimiter } = require('./middleware/rateLimiter');
// Create Express app
@@ -40,7 +41,8 @@ app.use('/api/files', requirePin(config.pin), downloadLimiter, fileRoutes);
// Root route
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');
}

View File

@@ -24,11 +24,11 @@ router.post('/verify-pin', (req, res) => {
if (!config.pin) {
res.cookie('DUMBDROP_PIN', '', {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
secure: req.secure || (process.env.NODE_ENV === 'production' && config.baseUrl.startsWith('https')),
sameSite: 'strict',
path: '/'
});
return res.json({ success: true });
return res.json({ success: true, error: null });
}
// Validate PIN format
@@ -36,6 +36,7 @@ router.post('/verify-pin', (req, res) => {
if (!cleanedPin) {
logger.warn(`Invalid PIN format from IP: ${ip}`);
return res.status(401).json({
success: false,
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}`);
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
res.cookie('DUMBDROP_PIN', cleanedPin, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
secure: req.secure || (process.env.NODE_ENV === 'production' && config.baseUrl.startsWith('https')),
sameSite: 'strict',
path: '/'
});
logger.info(`Successful PIN verification from IP: ${ip}`);
res.json({ success: true });
res.json({ success: true, error: null });
} else {
// Record failed attempt
const attempts = recordAttempt(ip);
@@ -78,12 +80,12 @@ router.post('/verify-pin', (req, res) => {
success: false,
error: attemptsLeft > 0 ?
`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) {
logger.error(`PIN verification error: ${err.message}`);
res.status(500).json({ error: 'Authentication failed' });
res.status(500).json({ success: false, error: 'Authentication failed' });
}
});