feat: Add comprehensive Authorization Implementation Guide for Next.js application, detailing authentication, role-based access control, and security best practices
This commit is contained in:
696
AUTHORIZATION_IMPLEMENTATION.md
Normal file
696
AUTHORIZATION_IMPLEMENTATION.md
Normal file
@@ -0,0 +1,696 @@
|
|||||||
|
# Authorization Implementation Guide
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
This document outlines the implementation strategy for adding authentication and authorization to the Project Management Panel - a Next.js 15 application with SQLite database.
|
||||||
|
|
||||||
|
## Current State Analysis
|
||||||
|
|
||||||
|
### ✅ What We Have
|
||||||
|
- **Framework**: Next.js 15 with App Router
|
||||||
|
- **Database**: SQLite with better-sqlite3
|
||||||
|
- **API Routes**: Multiple unprotected endpoints (projects, contracts, tasks, notes)
|
||||||
|
- **UI**: Basic navigation and CRUD interfaces
|
||||||
|
- **Security**: ⚠️ **NONE** - All endpoints are publicly accessible
|
||||||
|
|
||||||
|
### ❌ What's Missing
|
||||||
|
- User authentication system
|
||||||
|
- Session management
|
||||||
|
- Role-based access control
|
||||||
|
- API route protection
|
||||||
|
- Input validation & sanitization
|
||||||
|
- Security middleware
|
||||||
|
|
||||||
|
## Recommended Implementation Strategy
|
||||||
|
|
||||||
|
### 1. Authentication Solution: NextAuth.js
|
||||||
|
|
||||||
|
**Why NextAuth.js?**
|
||||||
|
- ✅ Native Next.js 15 App Router support
|
||||||
|
- ✅ Database session management
|
||||||
|
- ✅ Built-in security features (CSRF, JWT handling)
|
||||||
|
- ✅ Flexible provider system
|
||||||
|
- ✅ SQLite adapter available
|
||||||
|
|
||||||
|
### 2. Role-Based Access Control (RBAC)
|
||||||
|
|
||||||
|
**Proposed User Roles:**
|
||||||
|
|
||||||
|
| Role | Permissions | Use Case |
|
||||||
|
|------|-------------|----------|
|
||||||
|
| **Admin** | Full system access, user management | System administrators |
|
||||||
|
| **Project Manager** | Manage all projects/tasks, view reports | Team leads, supervisors |
|
||||||
|
| **User** | View/edit assigned projects/tasks | Regular employees |
|
||||||
|
| **Read-only** | View-only access to data | Clients, stakeholders |
|
||||||
|
|
||||||
|
## Implementation Plan
|
||||||
|
|
||||||
|
### Phase 1: Foundation Setup
|
||||||
|
|
||||||
|
#### 1.1 Install Dependencies
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install next-auth@beta @auth/better-sqlite3-adapter
|
||||||
|
npm install bcryptjs zod
|
||||||
|
npm install @types/bcryptjs # if using TypeScript
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 1.2 Environment Configuration
|
||||||
|
|
||||||
|
Create `.env.local`:
|
||||||
|
```env
|
||||||
|
# NextAuth.js Configuration
|
||||||
|
NEXTAUTH_SECRET=your-super-secret-key-here-minimum-32-characters
|
||||||
|
NEXTAUTH_URL=http://localhost:3000
|
||||||
|
|
||||||
|
# Database
|
||||||
|
DATABASE_URL=./data/database.sqlite
|
||||||
|
|
||||||
|
# Optional: Email configuration for password reset
|
||||||
|
EMAIL_SERVER_HOST=smtp.gmail.com
|
||||||
|
EMAIL_SERVER_PORT=587
|
||||||
|
EMAIL_SERVER_USER=your-email@gmail.com
|
||||||
|
EMAIL_SERVER_PASSWORD=your-app-password
|
||||||
|
EMAIL_FROM=noreply@yourapp.com
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 1.3 Database Schema Extension
|
||||||
|
|
||||||
|
Add to `src/lib/init-db.js`:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Users table
|
||||||
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
email TEXT UNIQUE NOT NULL,
|
||||||
|
password_hash TEXT NOT NULL,
|
||||||
|
role TEXT CHECK(role IN ('admin', 'project_manager', 'user', 'read_only')) DEFAULT 'user',
|
||||||
|
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
is_active INTEGER DEFAULT 1,
|
||||||
|
last_login TEXT,
|
||||||
|
failed_login_attempts INTEGER DEFAULT 0,
|
||||||
|
locked_until TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
-- NextAuth.js required tables
|
||||||
|
CREATE TABLE IF NOT EXISTS accounts (
|
||||||
|
id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
|
||||||
|
userId TEXT NOT NULL,
|
||||||
|
type TEXT NOT NULL,
|
||||||
|
provider TEXT NOT NULL,
|
||||||
|
providerAccountId TEXT NOT NULL,
|
||||||
|
refresh_token TEXT,
|
||||||
|
access_token TEXT,
|
||||||
|
expires_at INTEGER,
|
||||||
|
token_type TEXT,
|
||||||
|
scope TEXT,
|
||||||
|
id_token TEXT,
|
||||||
|
session_state TEXT,
|
||||||
|
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (userId) REFERENCES users(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS sessions (
|
||||||
|
id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
|
||||||
|
sessionToken TEXT UNIQUE NOT NULL,
|
||||||
|
userId TEXT NOT NULL,
|
||||||
|
expires TEXT NOT NULL,
|
||||||
|
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (userId) REFERENCES users(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS verification_tokens (
|
||||||
|
identifier TEXT NOT NULL,
|
||||||
|
token TEXT UNIQUE NOT NULL,
|
||||||
|
expires TEXT NOT NULL,
|
||||||
|
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (identifier, token)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Audit log table for security tracking
|
||||||
|
CREATE TABLE IF NOT EXISTS audit_logs (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
user_id TEXT,
|
||||||
|
action TEXT NOT NULL,
|
||||||
|
resource_type TEXT,
|
||||||
|
resource_id TEXT,
|
||||||
|
ip_address TEXT,
|
||||||
|
user_agent TEXT,
|
||||||
|
timestamp TEXT DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
details TEXT,
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Create indexes for performance
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_sessions_token ON sessions(sessionToken);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_accounts_user ON accounts(userId);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_audit_user_timestamp ON audit_logs(user_id, timestamp);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 2: Authentication Core
|
||||||
|
|
||||||
|
#### 2.1 NextAuth.js Configuration
|
||||||
|
|
||||||
|
Create `src/lib/auth.js`:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import NextAuth from "next-auth"
|
||||||
|
import CredentialsProvider from "next-auth/providers/credentials"
|
||||||
|
import { BetterSQLite3Adapter } from "@auth/better-sqlite3-adapter"
|
||||||
|
import db from "./db.js"
|
||||||
|
import bcrypt from "bcryptjs"
|
||||||
|
import { z } from "zod"
|
||||||
|
|
||||||
|
const loginSchema = z.object({
|
||||||
|
email: z.string().email("Invalid email format"),
|
||||||
|
password: z.string().min(6, "Password must be at least 6 characters")
|
||||||
|
})
|
||||||
|
|
||||||
|
export const { handlers, auth, signIn, signOut } = NextAuth({
|
||||||
|
adapter: BetterSQLite3Adapter(db),
|
||||||
|
session: {
|
||||||
|
strategy: "database",
|
||||||
|
maxAge: 30 * 24 * 60 * 60, // 30 days
|
||||||
|
updateAge: 24 * 60 * 60, // 24 hours
|
||||||
|
},
|
||||||
|
providers: [
|
||||||
|
CredentialsProvider({
|
||||||
|
name: "credentials",
|
||||||
|
credentials: {
|
||||||
|
email: { label: "Email", type: "email" },
|
||||||
|
password: { label: "Password", type: "password" }
|
||||||
|
},
|
||||||
|
async authorize(credentials, req) {
|
||||||
|
try {
|
||||||
|
// Validate input
|
||||||
|
const validatedFields = loginSchema.parse(credentials)
|
||||||
|
|
||||||
|
// Check if user exists and is active
|
||||||
|
const user = db.prepare(`
|
||||||
|
SELECT id, email, name, password_hash, role, is_active,
|
||||||
|
failed_login_attempts, locked_until
|
||||||
|
FROM users
|
||||||
|
WHERE email = ? AND is_active = 1
|
||||||
|
`).get(validatedFields.email)
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw new Error("Invalid credentials")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if account is locked
|
||||||
|
if (user.locked_until && new Date(user.locked_until) > new Date()) {
|
||||||
|
throw new Error("Account temporarily locked")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify password
|
||||||
|
const isValidPassword = await bcrypt.compare(validatedFields.password, user.password_hash)
|
||||||
|
|
||||||
|
if (!isValidPassword) {
|
||||||
|
// Increment failed attempts
|
||||||
|
db.prepare(`
|
||||||
|
UPDATE users
|
||||||
|
SET failed_login_attempts = failed_login_attempts + 1,
|
||||||
|
locked_until = CASE
|
||||||
|
WHEN failed_login_attempts >= 4
|
||||||
|
THEN datetime('now', '+15 minutes')
|
||||||
|
ELSE locked_until
|
||||||
|
END
|
||||||
|
WHERE id = ?
|
||||||
|
`).run(user.id)
|
||||||
|
|
||||||
|
throw new Error("Invalid credentials")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset failed attempts and update last login
|
||||||
|
db.prepare(`
|
||||||
|
UPDATE users
|
||||||
|
SET failed_login_attempts = 0,
|
||||||
|
locked_until = NULL,
|
||||||
|
last_login = CURRENT_TIMESTAMP
|
||||||
|
WHERE id = ?
|
||||||
|
`).run(user.id)
|
||||||
|
|
||||||
|
// Log successful login
|
||||||
|
logAuditEvent(user.id, 'LOGIN_SUCCESS', 'user', user.id, req)
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: user.id,
|
||||||
|
email: user.email,
|
||||||
|
name: user.name,
|
||||||
|
role: user.role
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Login error:", error)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
],
|
||||||
|
callbacks: {
|
||||||
|
async jwt({ token, user, account }) {
|
||||||
|
if (user) {
|
||||||
|
token.role = user.role
|
||||||
|
token.userId = user.id
|
||||||
|
}
|
||||||
|
return token
|
||||||
|
},
|
||||||
|
async session({ session, token, user }) {
|
||||||
|
if (token) {
|
||||||
|
session.user.id = token.userId || token.sub
|
||||||
|
session.user.role = token.role || user?.role
|
||||||
|
}
|
||||||
|
return session
|
||||||
|
},
|
||||||
|
async signIn({ user, account, profile, email, credentials }) {
|
||||||
|
// Additional sign-in logic if needed
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
pages: {
|
||||||
|
signIn: '/auth/signin',
|
||||||
|
signOut: '/auth/signout',
|
||||||
|
error: '/auth/error'
|
||||||
|
},
|
||||||
|
events: {
|
||||||
|
async signOut({ session, token }) {
|
||||||
|
if (session?.user?.id) {
|
||||||
|
logAuditEvent(session.user.id, 'LOGOUT', 'user', session.user.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Audit logging helper
|
||||||
|
function logAuditEvent(userId, action, resourceType, resourceId, req = null) {
|
||||||
|
try {
|
||||||
|
db.prepare(`
|
||||||
|
INSERT INTO audit_logs (user_id, action, resource_type, resource_id, ip_address, user_agent)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?)
|
||||||
|
`).run(
|
||||||
|
userId,
|
||||||
|
action,
|
||||||
|
resourceType,
|
||||||
|
resourceId,
|
||||||
|
req?.ip || 'unknown',
|
||||||
|
req?.headers?.['user-agent'] || 'unknown'
|
||||||
|
)
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Audit log error:", error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2.2 API Route Handlers
|
||||||
|
|
||||||
|
Create `src/app/api/auth/[...nextauth]/route.js`:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import { handlers } from "@/lib/auth"
|
||||||
|
|
||||||
|
export const { GET, POST } = handlers
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 3: Authorization Middleware
|
||||||
|
|
||||||
|
#### 3.1 API Protection Middleware
|
||||||
|
|
||||||
|
Create `src/lib/middleware/auth.js`:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import { auth } from "@/lib/auth"
|
||||||
|
import { NextResponse } from "next/server"
|
||||||
|
import { z } from "zod"
|
||||||
|
|
||||||
|
// Role hierarchy for permission checking
|
||||||
|
const ROLE_HIERARCHY = {
|
||||||
|
'admin': 4,
|
||||||
|
'project_manager': 3,
|
||||||
|
'user': 2,
|
||||||
|
'read_only': 1
|
||||||
|
}
|
||||||
|
|
||||||
|
export function withAuth(handler, options = {}) {
|
||||||
|
return async (req, context) => {
|
||||||
|
try {
|
||||||
|
const session = await auth()
|
||||||
|
|
||||||
|
// Check if user is authenticated
|
||||||
|
if (!session?.user) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Authentication required" },
|
||||||
|
{ status: 401 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if user account is active
|
||||||
|
const user = db.prepare("SELECT is_active FROM users WHERE id = ?").get(session.user.id)
|
||||||
|
if (!user?.is_active) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Account deactivated" },
|
||||||
|
{ status: 403 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check role-based permissions
|
||||||
|
if (options.requiredRole && !hasPermission(session.user.role, options.requiredRole)) {
|
||||||
|
logAuditEvent(session.user.id, 'ACCESS_DENIED', options.resource || 'api', req.url)
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Insufficient permissions" },
|
||||||
|
{ status: 403 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check resource-specific permissions
|
||||||
|
if (options.checkResourceAccess) {
|
||||||
|
const hasAccess = await options.checkResourceAccess(session.user, context.params)
|
||||||
|
if (!hasAccess) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Access denied to this resource" },
|
||||||
|
{ status: 403 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate request body if schema provided
|
||||||
|
if (options.bodySchema && (req.method === 'POST' || req.method === 'PUT' || req.method === 'PATCH')) {
|
||||||
|
try {
|
||||||
|
const body = await req.json()
|
||||||
|
options.bodySchema.parse(body)
|
||||||
|
} catch (error) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Invalid request data", details: error.errors },
|
||||||
|
{ status: 400 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add user info to request
|
||||||
|
req.user = session.user
|
||||||
|
req.session = session
|
||||||
|
|
||||||
|
// Call the original handler
|
||||||
|
return await handler(req, context)
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Auth middleware error:", error)
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Internal server error" },
|
||||||
|
{ status: 500 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hasPermission(userRole, requiredRole) {
|
||||||
|
return ROLE_HIERARCHY[userRole] >= ROLE_HIERARCHY[requiredRole]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper for read-only operations
|
||||||
|
export function withReadAuth(handler) {
|
||||||
|
return withAuth(handler, { requiredRole: 'read_only' })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper for user-level operations
|
||||||
|
export function withUserAuth(handler) {
|
||||||
|
return withAuth(handler, { requiredRole: 'user' })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper for project manager operations
|
||||||
|
export function withManagerAuth(handler) {
|
||||||
|
return withAuth(handler, { requiredRole: 'project_manager' })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper for admin operations
|
||||||
|
export function withAdminAuth(handler) {
|
||||||
|
return withAuth(handler, { requiredRole: 'admin' })
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3.2 Client-Side Route Protection
|
||||||
|
|
||||||
|
Create `src/components/auth/ProtectedRoute.js`:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useSession } from "next-auth/react"
|
||||||
|
import { useRouter } from "next/navigation"
|
||||||
|
import { useEffect } from "react"
|
||||||
|
|
||||||
|
export function ProtectedRoute({ children, requiredRole = null, fallback = null }) {
|
||||||
|
const { data: session, status } = useSession()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (status === "loading") return // Still loading
|
||||||
|
|
||||||
|
if (!session) {
|
||||||
|
router.push('/auth/signin')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requiredRole && !hasPermission(session.user.role, requiredRole)) {
|
||||||
|
router.push('/unauthorized')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}, [session, status, router, requiredRole])
|
||||||
|
|
||||||
|
if (status === "loading") {
|
||||||
|
return <div className="flex justify-center items-center h-64">Loading...</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!session) {
|
||||||
|
return fallback || <div>Redirecting to login...</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requiredRole && !hasPermission(session.user.role, requiredRole)) {
|
||||||
|
return fallback || <div>Access denied</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
return children
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasPermission(userRole, requiredRole) {
|
||||||
|
const roleHierarchy = {
|
||||||
|
'admin': 4,
|
||||||
|
'project_manager': 3,
|
||||||
|
'user': 2,
|
||||||
|
'read_only': 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return roleHierarchy[userRole] >= roleHierarchy[requiredRole]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 4: User Interface Components
|
||||||
|
|
||||||
|
#### 4.1 Authentication Pages
|
||||||
|
|
||||||
|
Pages to create:
|
||||||
|
- `src/app/auth/signin/page.js` - Login form
|
||||||
|
- `src/app/auth/signout/page.js` - Logout confirmation
|
||||||
|
- `src/app/auth/error/page.js` - Error handling
|
||||||
|
- `src/app/unauthorized/page.js` - Access denied page
|
||||||
|
|
||||||
|
#### 4.2 Navigation Updates
|
||||||
|
|
||||||
|
Update `src/components/ui/Navigation.js` to include:
|
||||||
|
- Login/logout buttons
|
||||||
|
- User info display
|
||||||
|
- Role-based menu items
|
||||||
|
|
||||||
|
#### 4.3 User Management Interface
|
||||||
|
|
||||||
|
For admin users:
|
||||||
|
- User listing and management
|
||||||
|
- Role assignment
|
||||||
|
- Account activation/deactivation
|
||||||
|
|
||||||
|
### Phase 5: Security Enhancements
|
||||||
|
|
||||||
|
#### 5.1 Input Validation Schemas
|
||||||
|
|
||||||
|
Create `src/lib/schemas/` with Zod schemas for all API endpoints:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// src/lib/schemas/project.js
|
||||||
|
import { z } from "zod"
|
||||||
|
|
||||||
|
export const createProjectSchema = z.object({
|
||||||
|
contract_id: z.number().int().positive(),
|
||||||
|
project_name: z.string().min(1).max(255),
|
||||||
|
project_number: z.string().min(1).max(50),
|
||||||
|
address: z.string().optional(),
|
||||||
|
// ... other fields
|
||||||
|
})
|
||||||
|
|
||||||
|
export const updateProjectSchema = createProjectSchema.partial()
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 5.2 Rate Limiting
|
||||||
|
|
||||||
|
Implement rate limiting for sensitive endpoints:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// src/lib/middleware/rateLimit.js
|
||||||
|
const attempts = new Map()
|
||||||
|
|
||||||
|
export function withRateLimit(handler, options = { maxAttempts: 5, windowMs: 15 * 60 * 1000 }) {
|
||||||
|
return async (req, context) => {
|
||||||
|
const key = req.ip || 'unknown'
|
||||||
|
const now = Date.now()
|
||||||
|
const window = attempts.get(key) || { count: 0, resetTime: now + options.windowMs }
|
||||||
|
|
||||||
|
if (now > window.resetTime) {
|
||||||
|
window.count = 1
|
||||||
|
window.resetTime = now + options.windowMs
|
||||||
|
} else {
|
||||||
|
window.count++
|
||||||
|
}
|
||||||
|
|
||||||
|
attempts.set(key, window)
|
||||||
|
|
||||||
|
if (window.count > options.maxAttempts) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Too many requests" },
|
||||||
|
{ status: 429 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return handler(req, context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Implementation Checklist
|
||||||
|
|
||||||
|
### Phase 1: Foundation
|
||||||
|
- [ ] Install dependencies
|
||||||
|
- [ ] Create environment configuration
|
||||||
|
- [ ] Extend database schema
|
||||||
|
- [ ] Create initial admin user script
|
||||||
|
|
||||||
|
### Phase 2: Authentication
|
||||||
|
- [ ] Configure NextAuth.js
|
||||||
|
- [ ] Create API route handlers
|
||||||
|
- [ ] Test login/logout functionality
|
||||||
|
|
||||||
|
### Phase 3: Authorization
|
||||||
|
- [ ] Implement API middleware
|
||||||
|
- [ ] Protect existing API routes
|
||||||
|
- [ ] Create client-side route protection
|
||||||
|
|
||||||
|
### Phase 4: User Interface
|
||||||
|
- [ ] Create authentication pages
|
||||||
|
- [ ] Update navigation component
|
||||||
|
- [ ] Build user management interface
|
||||||
|
|
||||||
|
### Phase 5: Security
|
||||||
|
- [ ] Add input validation to all endpoints
|
||||||
|
- [ ] Implement rate limiting
|
||||||
|
- [ ] Add audit logging
|
||||||
|
- [ ] Create security headers middleware
|
||||||
|
|
||||||
|
## Security Best Practices
|
||||||
|
|
||||||
|
### 1. Password Security
|
||||||
|
- Minimum 8 characters
|
||||||
|
- Require special characters, numbers
|
||||||
|
- Hash with bcrypt (cost factor 12+)
|
||||||
|
- Implement password history
|
||||||
|
|
||||||
|
### 2. Session Security
|
||||||
|
- Secure cookies
|
||||||
|
- Session rotation
|
||||||
|
- Timeout handling
|
||||||
|
- Device tracking
|
||||||
|
|
||||||
|
### 3. API Security
|
||||||
|
- Input validation on all endpoints
|
||||||
|
- SQL injection prevention (prepared statements)
|
||||||
|
- XSS protection
|
||||||
|
- CSRF tokens
|
||||||
|
|
||||||
|
### 4. Audit & Monitoring
|
||||||
|
- Log all authentication events
|
||||||
|
- Monitor failed login attempts
|
||||||
|
- Track permission changes
|
||||||
|
- Alert on suspicious activity
|
||||||
|
|
||||||
|
## Testing Strategy
|
||||||
|
|
||||||
|
### 1. Authentication Tests
|
||||||
|
- Valid/invalid login attempts
|
||||||
|
- Password reset functionality
|
||||||
|
- Session expiration
|
||||||
|
- Account lockout
|
||||||
|
|
||||||
|
### 2. Authorization Tests
|
||||||
|
- Role-based access control
|
||||||
|
- API endpoint protection
|
||||||
|
- Resource-level permissions
|
||||||
|
- Privilege escalation attempts
|
||||||
|
|
||||||
|
### 3. Security Tests
|
||||||
|
- SQL injection attempts
|
||||||
|
- XSS attacks
|
||||||
|
- CSRF attacks
|
||||||
|
- Rate limiting
|
||||||
|
|
||||||
|
## Deployment Considerations
|
||||||
|
|
||||||
|
### 1. Environment Variables
|
||||||
|
- Use strong, random secrets
|
||||||
|
- Different keys per environment
|
||||||
|
- Secure secret management
|
||||||
|
|
||||||
|
### 2. Database Security
|
||||||
|
- Regular backups
|
||||||
|
- Encryption at rest
|
||||||
|
- Network security
|
||||||
|
- Access logging
|
||||||
|
|
||||||
|
### 3. Application Security
|
||||||
|
- HTTPS enforcement
|
||||||
|
- Security headers
|
||||||
|
- Content Security Policy
|
||||||
|
- Regular security updates
|
||||||
|
|
||||||
|
## Migration Strategy
|
||||||
|
|
||||||
|
### 1. Development Phase
|
||||||
|
- Implement on development branch
|
||||||
|
- Test thoroughly with sample data
|
||||||
|
- Document all changes
|
||||||
|
|
||||||
|
### 2. Staging Deployment
|
||||||
|
- Deploy to staging environment
|
||||||
|
- Performance testing
|
||||||
|
- Security testing
|
||||||
|
- User acceptance testing
|
||||||
|
|
||||||
|
### 3. Production Deployment
|
||||||
|
- Database backup before migration
|
||||||
|
- Gradual rollout
|
||||||
|
- Monitor for issues
|
||||||
|
- Rollback plan ready
|
||||||
|
|
||||||
|
## Resources and Documentation
|
||||||
|
|
||||||
|
### NextAuth.js
|
||||||
|
- [Official Documentation](https://next-auth.js.org/)
|
||||||
|
- [Better SQLite3 Adapter](https://authjs.dev/reference/adapter/better-sqlite3)
|
||||||
|
|
||||||
|
### Security Libraries
|
||||||
|
- [Zod Validation](https://zod.dev/)
|
||||||
|
- [bcryptjs](https://www.npmjs.com/package/bcryptjs)
|
||||||
|
|
||||||
|
### Best Practices
|
||||||
|
- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
|
||||||
|
- [Next.js Security Guidelines](https://nextjs.org/docs/advanced-features/security-headers)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Next Steps**: Choose which phase to implement first and create detailed implementation tickets for development.
|
||||||
Reference in New Issue
Block a user