diff --git a/AUTHORIZATION_IMPLEMENTATION.md b/AUTHORIZATION_IMPLEMENTATION.md index 4424c2e..b2911fc 100644 --- a/AUTHORIZATION_IMPLEMENTATION.md +++ b/AUTHORIZATION_IMPLEMENTATION.md @@ -4,28 +4,52 @@ 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 +## Current State Analysis (Updated: June 25, 2025) + +### ✅ What We Have Implemented -### ✅ 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 +- **Authentication**: NextAuth.js v5 with credentials provider +- **User Management**: Complete user CRUD operations with bcrypt password hashing +- **Database Schema**: Users table with roles, audit logs, sessions +- **API Protection**: Middleware system with role-based access control +- **Session Management**: JWT-based sessions with 30-day expiration +- **Security Features**: Account lockout, failed login tracking, password validation +- **UI Components**: Authentication provider, navigation with user context +- **Auth Pages**: Sign-in page implemented -### ❌ What's Missing -- User authentication system -- Session management -- Role-based access control -- API route protection -- Input validation & sanitization -- Security middleware +### ✅ What's Protected + +- **API Routes**: All major endpoints (projects, contracts, tasks, notes) are protected +- **Role Hierarchy**: admin > project_manager > user > read_only +- **Navigation**: Role-based menu items (admin sees user management) +- **Session Security**: Automatic session management and validation + +### 🔄 Partially Implemented + +- **Auth Pages**: Sign-in exists, missing sign-out and error pages +- **User Interface**: Basic auth integration, could use more polish +- **Admin Features**: User management backend exists, UI needs completion +- **Audit Logging**: Database schema exists, not fully integrated + +### ❌ Still Missing + +- Complete user management UI for admins +- Password reset functionality +- Rate limiting implementation +- Enhanced input validation schemas +- CSRF protection +- Security headers middleware +- Comprehensive error handling +- Email notifications ## 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) @@ -36,181 +60,195 @@ This document outlines the implementation strategy for adding authentication and **Proposed User Roles:** -| Role | Permissions | Use Case | -|------|-------------|----------| -| **Admin** | Full system access, user management | System administrators | +| 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 | +| **User** | View/edit assigned projects/tasks | Regular employees | +| **Read-only** | View-only access to data | Clients, stakeholders | -## Implementation Plan +## Implementation Status -### Phase 1: Foundation Setup +### ✅ Phase 1: Foundation Setup - COMPLETED -#### 1.1 Install Dependencies +#### 1.1 Dependencies - ✅ INSTALLED -```bash -npm install next-auth@beta @auth/better-sqlite3-adapter -npm install bcryptjs zod -npm install @types/bcryptjs # if using TypeScript -``` +- NextAuth.js v5 (beta) +- bcryptjs for password hashing +- Zod for validation +- Better-sqlite3 adapter compatibility -#### 1.2 Environment Configuration +#### 1.2 Environment Configuration - ✅ COMPLETED -Create `.env.local`: -```env -# NextAuth.js Configuration -NEXTAUTH_SECRET=your-super-secret-key-here-minimum-32-characters -NEXTAUTH_URL=http://localhost:3000 +- `.env.local` configured with NEXTAUTH_SECRET and NEXTAUTH_URL +- Database URL configuration +- Development environment setup -# Database -DATABASE_URL=./data/database.sqlite +#### 1.3 Database Schema - ✅ IMPLEMENTED -# 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 -``` +- Users table with roles and security features +- Sessions table for NextAuth.js +- Audit logs table for security tracking +- Proper indexes for performance -#### 1.3 Database Schema Extension +#### 1.4 Initial Admin User - ✅ COMPLETED -Add to `src/lib/init-db.js`: +- `scripts/create-admin.js` script available +- Default admin user: admin@localhost.com / admin123456 -```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 -); +### ✅ Phase 2: Authentication Core - COMPLETED --- 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 -); +#### 2.1 NextAuth.js Configuration - ✅ IMPLEMENTED -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 -); +- **File**: `src/lib/auth.js` +- Credentials provider with email/password +- JWT session strategy with 30-day expiration +- Account lockout after 5 failed attempts (15-minute lockout) +- Password verification with bcrypt +- Failed login attempt tracking +- Session callbacks for role management -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) -); +#### 2.2 API Route Handlers - ✅ IMPLEMENTED --- 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) -); +- **File**: `src/app/api/auth/[...nextauth]/route.js` +- NextAuth.js handlers properly configured --- 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); -``` +#### 2.3 User Management System - ✅ IMPLEMENTED -### Phase 2: Authentication Core +- **File**: `src/lib/userManagement.js` +- Complete CRUD operations for users +- Password hashing and validation +- Role management functions +- User lookup by ID and email -#### 2.1 NextAuth.js Configuration +### ✅ Phase 3: Authorization Middleware - COMPLETED -Create `src/lib/auth.js`: +#### 3.1 API Protection Middleware - ✅ IMPLEMENTED + +- **File**: `src/lib/middleware/auth.js` +- `withAuth()` function for protecting routes +- Role hierarchy enforcement (admin=4, project_manager=3, user=2, read_only=1) +- Helper functions: `withReadAuth`, `withUserAuth`, `withAdminAuth`, `withManagerAuth` +- Proper error handling and status codes + +#### 3.2 Protected API Routes - ✅ IMPLEMENTED + +Example in `src/app/api/projects/route.js`: + +- GET requests require read_only access +- POST requests require user access +- All major API endpoints are protected + +#### 3.3 Session Provider - ✅ IMPLEMENTED + +- **File**: `src/components/auth/AuthProvider.js` +- NextAuth SessionProvider wrapper +- Integrated into root layout + +### 🔄 Phase 4: User Interface - PARTIALLY COMPLETED + +#### 4.1 Authentication Pages - 🔄 PARTIAL + +- ✅ **Sign-in page**: `src/app/auth/signin/page.js` - Complete with form validation +- ❌ **Sign-out page**: Missing +- 🔄 **Error page**: `src/app/auth/error/page.js` - Basic implementation +- ❌ **Unauthorized page**: Missing + +#### 4.2 Navigation Updates - ✅ COMPLETED + +- **File**: `src/components/ui/Navigation.js` +- User session integration with useSession +- Role-based menu items (admin sees user management) +- Sign-out functionality +- Conditional rendering based on auth status + +#### 4.3 User Management Interface - ❌ MISSING + +- Backend exists in userManagement.js +- Admin UI for user CRUD operations needed +- Role assignment interface needed + +### ❌ Phase 5: Security Enhancements - NOT STARTED + +#### 5.1 Input Validation Schemas - ❌ MISSING + +- Zod schemas for API endpoints +- Request validation middleware + +#### 5.2 Rate Limiting - ❌ MISSING + +- Rate limiting middleware +- IP-based request tracking + +#### 5.3 Security Headers - ❌ MISSING + +- CSRF protection +- Security headers middleware +- Content Security Policy ```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" +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") -}) + 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(` + 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(` + ` + ) + .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 @@ -219,87 +257,92 @@ export const { handlers, auth, signIn, signOut } = NextAuth({ ELSE locked_until END WHERE id = ? - `).run(user.id) - - throw new Error("Invalid credentials") - } - - // Reset failed attempts and update last login - db.prepare(` + ` + ).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) - } - } - } -}) + ` + ).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(` + 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) - } + ` + ).run( + userId, + action, + resourceType, + resourceId, + req?.ip || "unknown", + req?.headers?.["user-agent"] || "unknown" + ); + } catch (error) { + console.error("Audit log error:", error); + } } ``` @@ -308,9 +351,9 @@ function logAuditEvent(userId, action, resourceType, resourceId, req = null) { Create `src/app/api/auth/[...nextauth]/route.js`: ```javascript -import { handlers } from "@/lib/auth" +import { handlers } from "@/lib/auth"; -export const { GET, POST } = handlers +export const { GET, POST } = handlers; ``` ### Phase 3: Authorization Middleware @@ -320,111 +363,129 @@ export const { GET, POST } = handlers Create `src/lib/middleware/auth.js`: ```javascript -import { auth } from "@/lib/auth" -import { NextResponse } from "next/server" -import { z } from "zod" +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 -} + 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 } - ) - } - } + 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] + return ROLE_HIERARCHY[userRole] >= ROLE_HIERARCHY[requiredRole]; } // Helper for read-only operations export function withReadAuth(handler) { - return withAuth(handler, { requiredRole: 'read_only' }) + return withAuth(handler, { requiredRole: "read_only" }); } // Helper for user-level operations export function withUserAuth(handler) { - return withAuth(handler, { requiredRole: 'user' }) + return withAuth(handler, { requiredRole: "user" }); } // Helper for project manager operations export function withManagerAuth(handler) { - return withAuth(handler, { requiredRole: 'project_manager' }) + return withAuth(handler, { requiredRole: "project_manager" }); } // Helper for admin operations export function withAdminAuth(handler) { - return withAuth(handler, { requiredRole: 'admin' }) + return withAuth(handler, { requiredRole: "admin" }); } ``` @@ -433,54 +494,60 @@ export function withAdminAuth(handler) { Create `src/components/auth/ProtectedRoute.js`: ```javascript -"use client" +"use client"; -import { useSession } from "next-auth/react" -import { useRouter } from "next/navigation" -import { useEffect } from "react" +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
Loading...
- } - - if (!session) { - return fallback ||
Redirecting to login...
- } - - if (requiredRole && !hasPermission(session.user.role, requiredRole)) { - return fallback ||
Access denied
- } - - return children +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 ( +
Loading...
+ ); + } + + if (!session) { + return fallback ||
Redirecting to login...
; + } + + if (requiredRole && !hasPermission(session.user.role, requiredRole)) { + return fallback ||
Access denied
; + } + + return children; } function hasPermission(userRole, requiredRole) { - const roleHierarchy = { - 'admin': 4, - 'project_manager': 3, - 'user': 2, - 'read_only': 1 - } - - return roleHierarchy[userRole] >= roleHierarchy[requiredRole] + const roleHierarchy = { + admin: 4, + project_manager: 3, + user: 2, + read_only: 1, + }; + + return roleHierarchy[userRole] >= roleHierarchy[requiredRole]; } ``` @@ -489,6 +556,7 @@ function hasPermission(userRole, requiredRole) { #### 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 @@ -497,6 +565,7 @@ Pages to create: #### 4.2 Navigation Updates Update `src/components/ui/Navigation.js` to include: + - Login/logout buttons - User info display - Role-based menu items @@ -504,6 +573,7 @@ Update `src/components/ui/Navigation.js` to include: #### 4.3 User Management Interface For admin users: + - User listing and management - Role assignment - Account activation/deactivation @@ -516,17 +586,17 @@ Create `src/lib/schemas/` with Zod schemas for all API endpoints: ```javascript // src/lib/schemas/project.js -import { z } from "zod" +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 -}) + 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() +export const updateProjectSchema = createProjectSchema.partial(); ``` #### 5.2 Rate Limiting @@ -535,124 +605,209 @@ Implement rate limiting for sensitive endpoints: ```javascript // src/lib/middleware/rateLimit.js -const attempts = new Map() +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) - } +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 +## Implementation Checklist (Updated Status) -### Phase 1: Foundation -- [ ] Install dependencies -- [ ] Create environment configuration -- [ ] Extend database schema -- [ ] Create initial admin user script +### ✅ Phase 1: Foundation - COMPLETED -### Phase 2: Authentication -- [ ] Configure NextAuth.js -- [ ] Create API route handlers -- [ ] Test login/logout functionality +- [x] Install dependencies (NextAuth.js v5, bcryptjs, zod) +- [x] Create environment configuration (.env.local) +- [x] Extend database schema (users, sessions, audit_logs) +- [x] Create initial admin user script -### Phase 3: Authorization -- [ ] Implement API middleware -- [ ] Protect existing API routes -- [ ] Create client-side route protection +### ✅ Phase 2: Authentication - COMPLETED -### Phase 4: User Interface -- [ ] Create authentication pages -- [ ] Update navigation component -- [ ] Build user management interface +- [x] Configure NextAuth.js with credentials provider +- [x] Create API route handlers (/api/auth/[...nextauth]) +- [x] Implement user management system +- [x] Test login/logout functionality -### Phase 5: Security -- [ ] Add input validation to all endpoints -- [ ] Implement rate limiting -- [ ] Add audit logging +### ✅ Phase 3: Authorization - COMPLETED + +- [x] Implement API middleware (withAuth, role hierarchy) +- [x] Protect existing API routes (projects, contracts, tasks, notes) +- [x] Create role-based helper functions +- [x] Integrate session provider in app layout + +### 🔄 Phase 4: User Interface - IN PROGRESS + +- [x] Create sign-in page with form validation +- [x] Update navigation component with auth integration +- [x] Add role-based menu items +- [ ] Create sign-out confirmation page +- [ ] Create error handling page +- [ ] Create unauthorized access page +- [ ] Build admin user management interface + +### ❌ Phase 5: Security Enhancements - NOT STARTED + +- [ ] Add input validation schemas to all endpoints +- [ ] Implement rate limiting for sensitive operations +- [ ] Add comprehensive audit logging - [ ] Create security headers middleware +- [ ] Implement CSRF protection +- [ ] Add password reset functionality + +## Current Working Features + +### 🔐 Authentication System + +- **Login/Logout**: Fully functional with NextAuth.js +- **Session Management**: JWT-based with 30-day expiration +- **Password Security**: bcrypt hashing with salt rounds +- **Account Lockout**: 5 failed attempts = 15-minute lockout +- **Role System**: 4-tier hierarchy (admin, project_manager, user, read_only) + +### 🛡️ Authorization System + +- **API Protection**: All major endpoints require authentication +- **Role-Based Access**: Different permission levels per endpoint +- **Middleware**: Clean abstraction with helper functions +- **Session Validation**: Automatic session verification + +### 📱 User Interface + +- **Navigation**: Context-aware with user info and sign-out +- **Auth Pages**: Professional sign-in form with error handling +- **Role Integration**: Admin users see additional menu items +- **Responsive**: Works across device sizes + +### 🗄️ Database Security + +- **User Management**: Complete CRUD with proper validation +- **Audit Schema**: Ready for comprehensive logging +- **Indexes**: Optimized for performance +- **Constraints**: Role validation and data integrity + +## Next Priority Tasks + +1. **Complete Auth UI** (High Priority) + + - Sign-out confirmation page + - Unauthorized access page + - Enhanced error handling + +2. **Admin User Management** (High Priority) + + - User listing interface + - Create/edit user forms + - Role assignment controls + +3. **Security Enhancements** (Medium Priority) + + - Input validation schemas + - Rate limiting middleware + - Comprehensive audit logging + +4. **Password Management** (Medium Priority) + - Password reset functionality + - Password strength requirements + - Password change interface ## 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 +## Testing Status -### 1. Authentication Tests -- Valid/invalid login attempts -- Password reset functionality -- Session expiration -- Account lockout +### ✅ Completed Tests -### 2. Authorization Tests -- Role-based access control -- API endpoint protection -- Resource-level permissions -- Privilege escalation attempts +- **Authentication Flow**: Login/logout working correctly +- **API Protection**: All endpoints properly secured +- **Role Validation**: Permission levels enforced +- **Session Management**: JWT tokens and expiration working +- **Password Security**: bcrypt hashing and verification functional +- **Account Lockout**: Failed attempt tracking and temporary lockout -### 3. Security Tests -- SQL injection attempts -- XSS attacks -- CSRF attacks -- Rate limiting +### 🔧 Available Test Scripts + +- `test-auth.mjs` - Tests API route protection and auth endpoints +- `test-auth-detailed.mjs` - Comprehensive authentication flow testing +- `test-complete-auth.mjs` - Full system authentication validation +- `test-logged-in-flow.mjs` - Authenticated user session testing + +### ✅ Verified Security Features + +- Unauthorized API requests return 401 +- Role-based access control working +- Session tokens properly validated +- Password attempts tracked and limited +- Admin user creation and management functional ## 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 @@ -661,17 +816,20 @@ export function withRateLimit(handler, options = { maxAttempts: 5, windowMs: 15 ## 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 @@ -680,14 +838,17 @@ export function withRateLimit(handler, options = { maxAttempts: 5, windowMs: 15 ## 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)