mirror of
https://github.com/Oak-and-Sprout/sprout-track.git
synced 2026-01-06 08:20:33 -06:00
fixed build errors and updated documentation for auth
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
92
app/api/utils/ip-lockout.ts
Normal file
92
app/api/utils/ip-lockout.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user