From 4b2a544870bd72fb91b88449ad15b4f5609b4e26 Mon Sep 17 00:00:00 2001 From: Chop <28534054+RChopin@users.noreply.github.com> Date: Tue, 24 Jun 2025 23:54:30 +0200 Subject: [PATCH] feat: Add comprehensive Authorization Implementation Guide for Next.js application, detailing authentication, role-based access control, and security best practices --- AUTHORIZATION_IMPLEMENTATION.md | 696 ++++++++++++++++++++++++++++++++ 1 file changed, 696 insertions(+) create mode 100644 AUTHORIZATION_IMPLEMENTATION.md diff --git a/AUTHORIZATION_IMPLEMENTATION.md b/AUTHORIZATION_IMPLEMENTATION.md new file mode 100644 index 0000000..4424c2e --- /dev/null +++ b/AUTHORIZATION_IMPLEMENTATION.md @@ -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
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] +} +``` + +### 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.