# 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 (Updated: June 25, 2025) ### ✅ What We Have Implemented - **Framework**: Next.js 15 with App Router - **Database**: SQLite with better-sqlite3 - **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 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) - ✅ 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 Status ### ✅ Phase 1: Foundation Setup - COMPLETED #### 1.1 Dependencies - ✅ INSTALLED - NextAuth.js v5 (beta) - bcryptjs for password hashing - Zod for validation - Better-sqlite3 adapter compatibility #### 1.2 Environment Configuration - ✅ COMPLETED - `.env.local` configured with NEXTAUTH_SECRET and NEXTAUTH_URL - Database URL configuration - Development environment setup #### 1.3 Database Schema - ✅ IMPLEMENTED - Users table with roles and security features - Sessions table for NextAuth.js - Audit logs table for security tracking - Proper indexes for performance #### 1.4 Initial Admin User - ✅ COMPLETED - `scripts/create-admin.js` script available - Default admin user: admin@localhost.com / admin123456 ### ✅ Phase 2: Authentication Core - COMPLETED #### 2.1 NextAuth.js Configuration - ✅ IMPLEMENTED - **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 #### 2.2 API Route Handlers - ✅ IMPLEMENTED - **File**: `src/app/api/auth/[...nextauth]/route.js` - NextAuth.js handlers properly configured #### 2.3 User Management System - ✅ IMPLEMENTED - **File**: `src/lib/userManagement.js` - Complete CRUD operations for users - Password hashing and validation - Role management functions - User lookup by ID and email ### ✅ Phase 3: Authorization Middleware - COMPLETED #### 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"; 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 (