mirror of
https://github.com/DumbWareio/DumbDrop.git
synced 2025-11-03 05:23:39 +00:00
Bruteforce protection & new dockerhub config
This commit is contained in:
4
.github/workflows/docker-publish.yml
vendored
4
.github/workflows/docker-publish.yml
vendored
@@ -24,9 +24,9 @@ jobs:
|
||||
# Step 3: Build the Docker image
|
||||
- name: Build Docker Image
|
||||
run: |
|
||||
docker build -t abite3/dumbdrop:latest .
|
||||
docker build -t dumbwareio/dumbdrop:latest .
|
||||
|
||||
# Step 4: Push the Docker image to Docker Hub
|
||||
- name: Push Docker Image
|
||||
run: |
|
||||
docker push abite3/dumbdrop:latest
|
||||
docker push dumbwareio/dumbdrop:latest
|
||||
@@ -41,6 +41,16 @@
|
||||
.error-message {
|
||||
text-align: center;
|
||||
margin-top: 1rem;
|
||||
color: var(--danger-color);
|
||||
max-width: 300px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.pin-digit.locked {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
border-color: var(--danger-color);
|
||||
background-color: var(--textarea-bg);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
@@ -125,13 +135,25 @@
|
||||
if (data.success) {
|
||||
window.location.href = '/';
|
||||
} else {
|
||||
document.getElementById('pin-error').textContent = 'Invalid PIN';
|
||||
const inputs = [...document.querySelectorAll('.pin-digit')];
|
||||
inputs.forEach(input => {
|
||||
input.value = '';
|
||||
input.classList.remove('filled');
|
||||
});
|
||||
inputs[0].focus();
|
||||
// Show error message
|
||||
document.getElementById('pin-error').textContent = data.error;
|
||||
|
||||
// Only clear inputs and focus if not locked out
|
||||
if (!data.error.includes('try again in')) {
|
||||
const inputs = [...document.querySelectorAll('.pin-digit')];
|
||||
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) {
|
||||
console.error('Error verifying PIN:', err);
|
||||
|
||||
68
server.js
68
server.js
@@ -12,6 +12,50 @@ const port = process.env.PORT || 3000;
|
||||
const uploadDir = './uploads'; // Local development
|
||||
const maxFileSize = parseInt(process.env.MAX_FILE_SIZE || '1024') * 1024 * 1024; // Convert MB to bytes
|
||||
|
||||
// Brute force protection setup
|
||||
const loginAttempts = new Map(); // Stores IP addresses and their attempt counts
|
||||
const MAX_ATTEMPTS = 5; // Maximum allowed attempts
|
||||
const LOCKOUT_TIME = 15 * 60 * 1000; // 15 minutes in milliseconds
|
||||
|
||||
// Reset attempts for an IP
|
||||
function resetAttempts(ip) {
|
||||
loginAttempts.delete(ip);
|
||||
}
|
||||
|
||||
// Check if an IP is locked out
|
||||
function isLockedOut(ip) {
|
||||
const attempts = loginAttempts.get(ip);
|
||||
if (!attempts) return false;
|
||||
|
||||
if (attempts.count >= MAX_ATTEMPTS) {
|
||||
const timeElapsed = Date.now() - attempts.lastAttempt;
|
||||
if (timeElapsed < LOCKOUT_TIME) {
|
||||
return true;
|
||||
}
|
||||
resetAttempts(ip);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Record an attempt for an IP
|
||||
function recordAttempt(ip) {
|
||||
const attempts = loginAttempts.get(ip) || { count: 0, lastAttempt: 0 };
|
||||
attempts.count += 1;
|
||||
attempts.lastAttempt = Date.now();
|
||||
loginAttempts.set(ip, attempts);
|
||||
return attempts;
|
||||
}
|
||||
|
||||
// Cleanup old lockouts every minute
|
||||
setInterval(() => {
|
||||
const now = Date.now();
|
||||
for (const [ip, attempts] of loginAttempts.entries()) {
|
||||
if (now - attempts.lastAttempt >= LOCKOUT_TIME) {
|
||||
loginAttempts.delete(ip);
|
||||
}
|
||||
}
|
||||
}, 60000);
|
||||
|
||||
// Validate and set PIN
|
||||
const validatePin = (pin) => {
|
||||
if (!pin) return null;
|
||||
@@ -78,14 +122,27 @@ function safeCompare(a, b) {
|
||||
// Pin verification endpoint
|
||||
app.post('/api/verify-pin', (req, res) => {
|
||||
const { pin } = req.body;
|
||||
const ip = req.ip;
|
||||
|
||||
// If no PIN is set in env, always return success
|
||||
if (!PIN) {
|
||||
return res.json({ success: true });
|
||||
}
|
||||
|
||||
// Check for lockout
|
||||
if (isLockedOut(ip)) {
|
||||
const attempts = loginAttempts.get(ip);
|
||||
const timeLeft = Math.ceil((LOCKOUT_TIME - (Date.now() - attempts.lastAttempt)) / 1000 / 60);
|
||||
return res.status(429).json({
|
||||
error: `Too many attempts. Please try again in ${timeLeft} minutes.`
|
||||
});
|
||||
}
|
||||
|
||||
// Verify the PIN using constant-time comparison
|
||||
if (safeCompare(pin, PIN)) {
|
||||
// Reset attempts on successful login
|
||||
resetAttempts(ip);
|
||||
|
||||
// Set secure cookie
|
||||
res.cookie('DUMBDROP_PIN', pin, {
|
||||
httpOnly: true,
|
||||
@@ -95,7 +152,16 @@ app.post('/api/verify-pin', (req, res) => {
|
||||
});
|
||||
res.json({ success: true });
|
||||
} else {
|
||||
res.status(401).json({ success: false, error: 'Invalid PIN' });
|
||||
// Record failed attempt
|
||||
const attempts = recordAttempt(ip);
|
||||
const attemptsLeft = MAX_ATTEMPTS - attempts.count;
|
||||
|
||||
res.status(401).json({
|
||||
success: false,
|
||||
error: attemptsLeft > 0 ?
|
||||
`Invalid PIN. ${attemptsLeft} attempts remaining.` :
|
||||
'Too many attempts. Account locked for 15 minutes.'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user