fixed build errors and updated documentation for auth

This commit is contained in:
John Overton
2025-04-07 09:51:46 -05:00
parent 498393b851
commit 2e51699fa1
4 changed files with 99 additions and 94 deletions

View File

@@ -1,98 +1,6 @@
import { NextRequest, NextResponse } from 'next/server';
import { ApiResponse } from '../../types';
// In-memory store for tracking failed login attempts
// In a production environment, this should be replaced with a persistent store like Redis
interface FailedAttempt {
count: number;
lockoutUntil: number | null;
}
const failedAttempts: Record<string, FailedAttempt> = {};
// Maximum number of failed attempts before lockout
const MAX_ATTEMPTS = 3;
// Lockout duration in milliseconds (5 minutes)
const LOCKOUT_DURATION = 5 * 60 * 1000;
/**
* Check if an IP is currently locked out
* @param ip The IP address to check
* @returns Object containing lockout status and time remaining
*/
export function checkIpLockout(ip: string): { locked: boolean; remainingTime: number } {
const attempt = failedAttempts[ip];
// If no record exists for this IP, it's not locked
if (!attempt) {
return { locked: false, remainingTime: 0 };
}
// If there's a lockout time and it's in the future, the IP is locked
if (attempt.lockoutUntil && attempt.lockoutUntil > Date.now()) {
return {
locked: true,
remainingTime: attempt.lockoutUntil - Date.now()
};
}
// If the lockout time has passed, reset the record and return not locked
if (attempt.lockoutUntil && attempt.lockoutUntil <= Date.now()) {
attempt.count = 0;
attempt.lockoutUntil = null;
return { locked: false, remainingTime: 0 };
}
// If there's no lockout time but there are failed attempts, return not locked
return { locked: false, remainingTime: 0 };
}
/**
* Record a failed login attempt for an IP
* @param ip The IP address to record the failed attempt for
* @returns Object containing lockout status and time remaining
*/
export function recordFailedAttempt(ip: string): { locked: boolean; remainingTime: number } {
// Get or create the record for this IP
const attempt = failedAttempts[ip] || { count: 0, lockoutUntil: null };
// If the IP is already locked, return the current status
if (attempt.lockoutUntil && attempt.lockoutUntil > Date.now()) {
return {
locked: true,
remainingTime: attempt.lockoutUntil - Date.now()
};
}
// Increment the failed attempt count
attempt.count += 1;
// If the count exceeds the maximum, lock the IP
if (attempt.count >= MAX_ATTEMPTS) {
attempt.lockoutUntil = Date.now() + LOCKOUT_DURATION;
attempt.count = 0; // Reset count for next window
}
// Update the record
failedAttempts[ip] = attempt;
// Return the current status
return {
locked: attempt.lockoutUntil !== null && attempt.lockoutUntil > Date.now(),
remainingTime: attempt.lockoutUntil ? attempt.lockoutUntil - Date.now() : 0
};
}
/**
* Reset failed attempts for an IP after successful login
* @param ip The IP address to reset
*/
export function resetFailedAttempts(ip: string): void {
if (failedAttempts[ip]) {
failedAttempts[ip].count = 0;
failedAttempts[ip].lockoutUntil = null;
}
}
import { checkIpLockout, recordFailedAttempt, resetFailedAttempts } from '../../utils/ip-lockout';
/**
* API endpoint to check if an IP is locked out

View File

@@ -2,7 +2,7 @@ import { NextRequest, NextResponse } from 'next/server';
import prisma from '../db';
import { ApiResponse } from '../types';
import jwt from 'jsonwebtoken';
import { checkIpLockout, recordFailedAttempt, resetFailedAttempts } from './ip-lockout/route';
import { checkIpLockout, recordFailedAttempt, resetFailedAttempts } from '../utils/ip-lockout';
// Secret key for JWT signing - in production, use environment variable
const JWT_SECRET = process.env.JWT_SECRET || 'baby-tracker-jwt-secret';

View File

@@ -172,6 +172,11 @@ Authentication errors return appropriate HTTP status codes:
3. **Lockout Protection**:
- Three failed login attempts trigger a server-side IP-based 5-minute lockout
- This prevents automated attacks from bypassing client-side lockout mechanisms
- IP lockout functionality is implemented in `/app/api/utils/ip-lockout.ts` and provides:
- `checkIpLockout(ip)`: Checks if an IP is currently locked out
- `recordFailedAttempt(ip)`: Records a failed login attempt for an IP
- `resetFailedAttempts(ip)`: Resets failed attempts for an IP after successful login
- The lockout system is used in the authentication process to prevent brute force attacks
## Best Practices

View File

@@ -0,0 +1,92 @@
// In-memory store for tracking failed login attempts
// In a production environment, this should be replaced with a persistent store like Redis
interface FailedAttempt {
count: number;
lockoutUntil: number | null;
}
const failedAttempts: Record<string, FailedAttempt> = {};
// Maximum number of failed attempts before lockout
const MAX_ATTEMPTS = 3;
// Lockout duration in milliseconds (5 minutes)
const LOCKOUT_DURATION = 5 * 60 * 1000;
/**
* Check if an IP is currently locked out
* @param ip The IP address to check
* @returns Object containing lockout status and time remaining
*/
export function checkIpLockout(ip: string): { locked: boolean; remainingTime: number } {
const attempt = failedAttempts[ip];
// If no record exists for this IP, it's not locked
if (!attempt) {
return { locked: false, remainingTime: 0 };
}
// If there's a lockout time and it's in the future, the IP is locked
if (attempt.lockoutUntil && attempt.lockoutUntil > Date.now()) {
return {
locked: true,
remainingTime: attempt.lockoutUntil - Date.now()
};
}
// If the lockout time has passed, reset the record and return not locked
if (attempt.lockoutUntil && attempt.lockoutUntil <= Date.now()) {
attempt.count = 0;
attempt.lockoutUntil = null;
return { locked: false, remainingTime: 0 };
}
// If there's no lockout time but there are failed attempts, return not locked
return { locked: false, remainingTime: 0 };
}
/**
* Record a failed login attempt for an IP
* @param ip The IP address to record the failed attempt for
* @returns Object containing lockout status and time remaining
*/
export function recordFailedAttempt(ip: string): { locked: boolean; remainingTime: number } {
// Get or create the record for this IP
const attempt = failedAttempts[ip] || { count: 0, lockoutUntil: null };
// If the IP is already locked, return the current status
if (attempt.lockoutUntil && attempt.lockoutUntil > Date.now()) {
return {
locked: true,
remainingTime: attempt.lockoutUntil - Date.now()
};
}
// Increment the failed attempt count
attempt.count += 1;
// If the count exceeds the maximum, lock the IP
if (attempt.count >= MAX_ATTEMPTS) {
attempt.lockoutUntil = Date.now() + LOCKOUT_DURATION;
attempt.count = 0; // Reset count for next window
}
// Update the record
failedAttempts[ip] = attempt;
// Return the current status
return {
locked: attempt.lockoutUntil !== null && attempt.lockoutUntil > Date.now(),
remainingTime: attempt.lockoutUntil ? attempt.lockoutUntil - Date.now() : 0
};
}
/**
* Reset failed attempts for an IP after successful login
* @param ip The IP address to reset
*/
export function resetFailedAttempts(ip: string): void {
if (failedAttempts[ip]) {
failedAttempts[ip].count = 0;
failedAttempts[ip].lockoutUntil = null;
}
}