diff --git a/AUTHORIZATION_IMPLEMENTATION.md b/AUTHORIZATION_IMPLEMENTATION.md index ac27373..7607ecf 100644 --- a/AUTHORIZATION_IMPLEMENTATION.md +++ b/AUTHORIZATION_IMPLEMENTATION.md @@ -809,6 +809,81 @@ POST /api/projects/users } ``` +## Project Tasks User Tracking - NEW FEATURE ā + +### š Task User Management Implementation + +We've also implemented comprehensive user tracking for project tasks: + +#### Database Schema Updates ā + +- **created_by**: Tracks who created the task (user ID) +- **assigned_to**: Tracks who is assigned to work on the task (user ID) +- **created_at**: Timestamp when task was created +- **updated_at**: Timestamp when task was last modified +- **Indexes**: Performance optimized with proper foreign key indexes + +#### API Enhancements ā + +- **Enhanced Queries**: Tasks now include user names and emails via JOIN operations +- **User Assignment**: New `/api/project-tasks/users` endpoint for user management +- **Query Filters**: Support for filtering tasks by assigned user or creator +- **User Context**: Create/update operations automatically capture authenticated user ID + +#### UI Components ā + +- **Task Form**: User assignment dropdown in create task forms +- **Task Listing**: "Created By" and "Assigned To" columns in task table +- **User Selection**: Dropdown populated with active users for assignment + +#### New Task Query Functions ā + +- `getAllUsersForTaskAssignment()`: Get active users for assignment dropdown +- `getProjectTasksByAssignedUser(userId)`: Filter tasks by assignee +- `getProjectTasksByCreator(userId)`: Filter tasks by creator +- `updateProjectTaskAssignment(taskId, userId)`: Update task assignment + +#### Task Creation Behavior ā + +- **Auto-assignment**: Tasks are automatically assigned to the authenticated user as creator +- **Optional Assignment**: Users can assign tasks to other team members during creation +- **Creator Tracking**: All tasks track who created them for accountability + +### Task Usage Examples + +#### Creating Tasks with User Tracking + +```javascript +// Tasks are automatically assigned to the authenticated user as creator +POST /api/project-tasks +{ + "project_id": 123, + "task_template_id": 1, // or custom_task_name for custom tasks + "assigned_to": "user-id-here", // Optional, defaults to creator + "priority": "high" +} +``` + +#### Filtering Tasks by User + +```javascript +// Get tasks assigned to specific user +GET /api/project-tasks?assigned_to=user-id + +// Get tasks created by specific user +GET /api/project-tasks?created_by=user-id +``` + +#### Updating Task Assignment + +```javascript +POST /api/project-tasks/users +{ + "taskId": 456, + "assignedToUserId": "new-user-id" +} +``` + ### Next Enhancements 1. **Dashboard Views** (Recommended) @@ -828,123 +903,69 @@ POST /api/projects/users - Deadline reminders for assigned users - Status change notifications -## Security Best Practices +## Notes User Tracking - NEW FEATURE ā -### 1. Password Security +### š Notes User Management Implementation -- Minimum 8 characters -- Require special characters, numbers -- Hash with bcrypt (cost factor 12+) -- Implement password history +We've also implemented comprehensive user tracking for all notes (both project notes and task notes): -### 2. Session Security +#### Database Schema Updates ā -- Secure cookies -- Session rotation -- Timeout handling -- Device tracking +- **created_by**: Tracks who created the note (user ID) +- **is_system**: Distinguishes between user notes and system-generated notes +- **Enhanced queries**: Notes now include user names and emails via JOIN operations +- **Indexes**: Performance optimized with proper indexes for user lookups -### 3. API Security +#### API Enhancements ā -- Input validation on all endpoints -- SQL injection prevention (prepared statements) -- XSS protection -- CSRF tokens +- **User Context**: All note creation operations automatically capture authenticated user ID +- **System Notes**: Automatic system notes (task status changes) track who made the change +- **User Information**: Note retrieval includes creator name and email for display -### 4. Audit & Monitoring +#### UI Components ā -- Log all authentication events -- Monitor failed login attempts -- Track permission changes -- Alert on suspicious activity +- **Project Notes**: Display creator name and email in project note listings +- **Task Notes**: Show who added each note with user badges and timestamps +- **System Notes**: Distinguished from user notes with special styling and "System" badge +- **User Attribution**: Clear indication of who created each note and when -## Testing Status +#### New Note Query Functions ā -### ā Completed Tests +- `getAllNotesWithUsers()`: Get all notes with user and project/task context +- `getNotesByCreator(userId)`: Filter notes by creator for user activity tracking +- Enhanced `getNotesByProjectId()` and `getNotesByTaskId()` with user information -- **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 +#### Automatic User Tracking ā -### š§ Available Test Scripts +- **Note Creation**: All new notes automatically record who created them +- **System Notes**: Task status changes generate system notes attributed to the user who made the change +- **Audit Trail**: Complete history of who added what notes and when -- `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 +### Notes Usage Examples -### ā Verified Security Features +#### Project Notes with User Tracking -- 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 +- Notes display creator name in a blue badge next to the timestamp +- Form automatically associates notes with the authenticated user +- Clear visual distinction between different note authors -## Deployment Considerations +#### Task Notes with User Tracking -### 1. Environment Variables +- User notes show creator name in a gray badge +- System notes show "System" badge but also track the user who triggered the action +- Full audit trail of task status changes and who made them -- Use strong, random secrets -- Different keys per environment -- Secure secret management +#### System Note Generation -### 2. Database Security +```javascript +// When a user changes a task status, a system note is automatically created: +// "Status changed from 'pending' to 'in_progress'" - attributed to the user who made the change +``` -- Regular backups -- Encryption at rest -- Network security -- Access logging +### Benefits -### 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. +1. **Accountability**: Full audit trail of who added what notes +2. **Context**: Know who to contact for clarification on specific notes +3. **History**: Track communication and decisions made by team members +4. **System Integration**: Automatic notes for system actions still maintain user attribution +5. **User Experience**: Clear visual indicators of note authors improve team collaboration diff --git a/check-task-schema.mjs b/check-task-schema.mjs new file mode 100644 index 0000000..626abe4 --- /dev/null +++ b/check-task-schema.mjs @@ -0,0 +1,25 @@ +import Database from "better-sqlite3"; + +const db = new Database("./data/database.sqlite"); + +console.log("Project Tasks table structure:"); +const projectTasksSchema = db.prepare("PRAGMA table_info(project_tasks)").all(); +console.table(projectTasksSchema); + +console.log("\nSample project tasks with user tracking:"); +const tasks = db + .prepare( + ` + SELECT pt.*, + creator.name as created_by_name, + assignee.name as assigned_to_name + FROM project_tasks pt + LEFT JOIN users creator ON pt.created_by = creator.id + LEFT JOIN users assignee ON pt.assigned_to = assignee.id + LIMIT 3 +` + ) + .all(); +console.table(tasks); + +db.close(); diff --git a/debug-task-insert.mjs b/debug-task-insert.mjs new file mode 100644 index 0000000..edc8666 --- /dev/null +++ b/debug-task-insert.mjs @@ -0,0 +1,49 @@ +import Database from "better-sqlite3"; + +const db = new Database("./data/database.sqlite"); + +console.log("Project Tasks table columns:"); +const projectTasksSchema = db.prepare("PRAGMA table_info(project_tasks)").all(); +projectTasksSchema.forEach((col) => { + console.log( + `${col.name}: ${col.type} (${col.notnull ? "NOT NULL" : "NULL"})` + ); +}); + +console.log("\nChecking if created_at and updated_at columns exist..."); +const hasCreatedAt = projectTasksSchema.some( + (col) => col.name === "created_at" +); +const hasUpdatedAt = projectTasksSchema.some( + (col) => col.name === "updated_at" +); +console.log("created_at exists:", hasCreatedAt); +console.log("updated_at exists:", hasUpdatedAt); + +// Let's try a simple insert to see what happens +console.log("\nTesting manual insert..."); +try { + const result = db + .prepare( + ` + INSERT INTO project_tasks ( + project_id, task_template_id, status, priority, + created_by, assigned_to, created_at, updated_at + ) + VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) + ` + ) + .run(1, 1, "pending", "normal", "test-user", "test-user"); + + console.log("Insert successful, ID:", result.lastInsertRowid); + + // Clean up + db.prepare("DELETE FROM project_tasks WHERE id = ?").run( + result.lastInsertRowid + ); + console.log("Test record cleaned up"); +} catch (error) { + console.error("Insert failed:", error.message); +} + +db.close(); diff --git a/fix-notes-columns.mjs b/fix-notes-columns.mjs new file mode 100644 index 0000000..284c8ac --- /dev/null +++ b/fix-notes-columns.mjs @@ -0,0 +1,60 @@ +import Database from "better-sqlite3"; + +const db = new Database("./data/database.sqlite"); + +console.log("Adding user tracking columns to notes table...\n"); + +try { + console.log("Adding created_by column..."); + db.exec(`ALTER TABLE notes ADD COLUMN created_by TEXT;`); + console.log("ā created_by column added"); +} catch (e) { + console.log("created_by column already exists or error:", e.message); +} + +try { + console.log("Adding is_system column..."); + db.exec(`ALTER TABLE notes ADD COLUMN is_system INTEGER DEFAULT 0;`); + console.log("ā is_system column added"); +} catch (e) { + console.log("is_system column already exists or error:", e.message); +} + +console.log("\nVerifying columns were added..."); +const schema = db.prepare("PRAGMA table_info(notes)").all(); +const hasCreatedBy = schema.some((col) => col.name === "created_by"); +const hasIsSystem = schema.some((col) => col.name === "is_system"); + +console.log("created_by exists:", hasCreatedBy); +console.log("is_system exists:", hasIsSystem); + +if (hasCreatedBy && hasIsSystem) { + console.log("\nā All columns are now present!"); + + // Test a manual insert + console.log("\nTesting manual note insert..."); + try { + const result = db + .prepare( + ` + INSERT INTO notes (project_id, note, created_by, is_system, note_date) + VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP) + ` + ) + .run(1, "Test note with user tracking", "test-user-id", 0); + + console.log("Insert successful, ID:", result.lastInsertRowid); + + // Clean up + db.prepare("DELETE FROM notes WHERE note_id = ?").run( + result.lastInsertRowid + ); + console.log("Test record cleaned up"); + } catch (error) { + console.error("Insert failed:", error.message); + } +} else { + console.log("\nā Some columns are still missing"); +} + +db.close(); diff --git a/fix-task-columns.mjs b/fix-task-columns.mjs new file mode 100644 index 0000000..cbb4c7a --- /dev/null +++ b/fix-task-columns.mjs @@ -0,0 +1,37 @@ +import Database from "better-sqlite3"; + +const db = new Database("./data/database.sqlite"); + +console.log("Adding missing columns to project_tasks table...\n"); + +try { + console.log("Adding created_at column..."); + db.exec(`ALTER TABLE project_tasks ADD COLUMN created_at TEXT;`); + console.log("ā created_at column added"); +} catch (e) { + console.log("created_at column already exists or error:", e.message); +} + +try { + console.log("Adding updated_at column..."); + db.exec(`ALTER TABLE project_tasks ADD COLUMN updated_at TEXT;`); + console.log("ā updated_at column added"); +} catch (e) { + console.log("updated_at column already exists or error:", e.message); +} + +console.log("\nVerifying columns were added..."); +const schema = db.prepare("PRAGMA table_info(project_tasks)").all(); +const hasCreatedAt = schema.some((col) => col.name === "created_at"); +const hasUpdatedAt = schema.some((col) => col.name === "updated_at"); + +console.log("created_at exists:", hasCreatedAt); +console.log("updated_at exists:", hasUpdatedAt); + +if (hasCreatedAt && hasUpdatedAt) { + console.log("\nā All columns are now present!"); +} else { + console.log("\nā Some columns are still missing"); +} + +db.close(); diff --git a/src/app/api/notes/route.js b/src/app/api/notes/route.js index 7724c35..940d5df 100644 --- a/src/app/api/notes/route.js +++ b/src/app/api/notes/route.js @@ -9,14 +9,22 @@ async function createNoteHandler(req) { return NextResponse.json({ error: "Missing fields" }, { status: 400 }); } - db.prepare( + try { + db.prepare( + ` + INSERT INTO notes (project_id, task_id, note, created_by, note_date) + VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP) ` - INSERT INTO notes (project_id, task_id, note) - VALUES (?, ?, ?) - ` - ).run(project_id || null, task_id || null, note); + ).run(project_id || null, task_id || null, note, req.user?.id || null); - return NextResponse.json({ success: true }); + return NextResponse.json({ success: true }); + } catch (error) { + console.error("Error creating note:", error); + return NextResponse.json( + { error: "Failed to create note", details: error.message }, + { status: 500 } + ); + } } async function deleteNoteHandler(_, { params }) { diff --git a/src/app/api/project-tasks/[id]/route.js b/src/app/api/project-tasks/[id]/route.js index a45116a..ce96dd8 100644 --- a/src/app/api/project-tasks/[id]/route.js +++ b/src/app/api/project-tasks/[id]/route.js @@ -17,11 +17,12 @@ async function updateProjectTaskHandler(req, { params }) { ); } - updateProjectTaskStatus(params.id, status); + updateProjectTaskStatus(params.id, status, req.user?.id || null); return NextResponse.json({ success: true }); } catch (error) { + console.error("Error updating task status:", error); return NextResponse.json( - { error: "Failed to update project task" }, + { error: "Failed to update project task", details: error.message }, { status: 500 } ); } diff --git a/src/app/api/project-tasks/route.js b/src/app/api/project-tasks/route.js index 7f2e1b6..9429832 100644 --- a/src/app/api/project-tasks/route.js +++ b/src/app/api/project-tasks/route.js @@ -43,11 +43,20 @@ async function createProjectTaskHandler(req) { ); } - const result = createProjectTask(data); + // Add user tracking information from authenticated session + const taskData = { + ...data, + created_by: req.user?.id || null, + // If no assigned_to is specified, default to the creator + assigned_to: data.assigned_to || req.user?.id || null, + }; + + const result = createProjectTask(taskData); return NextResponse.json({ success: true, id: result.lastInsertRowid }); } catch (error) { + console.error("Error creating project task:", error); return NextResponse.json( - { error: "Failed to create project task" }, + { error: "Failed to create project task", details: error.message }, { status: 500 } ); } diff --git a/src/app/api/project-tasks/users/route.js b/src/app/api/project-tasks/users/route.js new file mode 100644 index 0000000..45f2f5a --- /dev/null +++ b/src/app/api/project-tasks/users/route.js @@ -0,0 +1,50 @@ +import { + updateProjectTaskAssignment, + getAllUsersForTaskAssignment, +} from "@/lib/queries/tasks"; +import { NextResponse } from "next/server"; +import { withUserAuth, withReadAuth } from "@/lib/middleware/auth"; + +// GET: Get all users for task assignment +async function getUsersForTaskAssignmentHandler(req) { + try { + const users = getAllUsersForTaskAssignment(); + return NextResponse.json(users); + } catch (error) { + return NextResponse.json( + { error: "Failed to fetch users" }, + { status: 500 } + ); + } +} + +// POST: Update task assignment +async function updateTaskAssignmentHandler(req) { + try { + const { taskId, assignedToUserId } = await req.json(); + + if (!taskId) { + return NextResponse.json( + { error: "taskId is required" }, + { status: 400 } + ); + } + + const result = updateProjectTaskAssignment(taskId, assignedToUserId); + + if (result.changes === 0) { + return NextResponse.json({ error: "Task not found" }, { status: 404 }); + } + + return NextResponse.json({ success: true }); + } catch (error) { + return NextResponse.json( + { error: "Failed to update task assignment" }, + { status: 500 } + ); + } +} + +// Protected routes +export const GET = withReadAuth(getUsersForTaskAssignmentHandler); +export const POST = withUserAuth(updateTaskAssignmentHandler); diff --git a/src/app/api/task-notes/route.js b/src/app/api/task-notes/route.js index 9381ac5..ba6bc69 100644 --- a/src/app/api/task-notes/route.js +++ b/src/app/api/task-notes/route.js @@ -38,7 +38,7 @@ async function addTaskNoteHandler(req) { ); } - addNoteToTask(task_id, note, is_system); + addNoteToTask(task_id, note, is_system, req.user?.id || null); return NextResponse.json({ success: true }); } catch (error) { console.error("Error adding task note:", error); diff --git a/src/app/projects/[id]/page.js b/src/app/projects/[id]/page.js index b3cd002..193d8a7 100644 --- a/src/app/projects/[id]/page.js +++ b/src/app/projects/[id]/page.js @@ -400,12 +400,20 @@ export default async function ProjectViewPage({ params }) {