From 1524e1e9bb041d63d57de8bb1c49ac925e2b9e65 Mon Sep 17 00:00:00 2001 From: RKWojs Date: Wed, 25 Jun 2025 12:54:37 +0200 Subject: [PATCH] feat: add authentication test page and API testing scripts; implement debug auth endpoint and enhance task route with read permissions --- public/test-auth.html | 142 ++++++++++++++++++++++ src/app/api/debug-auth/route.js | 37 ++++++ src/app/api/tasks/route.js | 10 +- src/lib/middleware/auth.js | 33 ++--- test-auth-api.mjs | 109 +++++++++++++++++ test-logged-in-flow.mjs | 206 ++++++++++++++++++++++++++++++++ 6 files changed, 520 insertions(+), 17 deletions(-) create mode 100644 public/test-auth.html create mode 100644 src/app/api/debug-auth/route.js create mode 100644 test-auth-api.mjs create mode 100644 test-logged-in-flow.mjs diff --git a/public/test-auth.html b/public/test-auth.html new file mode 100644 index 0000000..a12361d --- /dev/null +++ b/public/test-auth.html @@ -0,0 +1,142 @@ + + + + + + Authentication Test Page + + + +

Authentication & API Test Page

+ +
+

Authentication Status

+ +
+
+ +
+

API Endpoint Tests

+ +
+
+ +
+

Manual Login Instructions

+
+

Test Credentials:

+

Email: admin@localhost.com

+

Password: admin123456

+

Open Sign-in Page

+
+
+ + + + diff --git a/src/app/api/debug-auth/route.js b/src/app/api/debug-auth/route.js new file mode 100644 index 0000000..e63f04e --- /dev/null +++ b/src/app/api/debug-auth/route.js @@ -0,0 +1,37 @@ +import { auth } from "@/lib/auth" +import { NextResponse } from "next/server" + +export const GET = auth(async (req) => { + try { + console.log("=== DEBUG AUTH ENDPOINT ===") + console.log("Request URL:", req.url) + console.log("Auth object:", req.auth) + + if (!req.auth?.user) { + return NextResponse.json({ + error: "No session found", + debug: { + hasAuth: !!req.auth, + authKeys: req.auth ? Object.keys(req.auth) : [], + } + }, { status: 401 }) + } + + return NextResponse.json({ + message: "Authenticated", + user: req.auth.user, + debug: { + authKeys: Object.keys(req.auth), + userKeys: Object.keys(req.auth.user) + } + }) + + } catch (error) { + console.error("Auth debug error:", error) + return NextResponse.json({ + error: "Auth error", + message: error.message, + stack: error.stack + }, { status: 500 }) + } +}) diff --git a/src/app/api/tasks/route.js b/src/app/api/tasks/route.js index 7dc61a0..24d63bc 100644 --- a/src/app/api/tasks/route.js +++ b/src/app/api/tasks/route.js @@ -1,6 +1,7 @@ import db from "@/lib/db"; import { NextResponse } from "next/server"; -import { withUserAuth } from "@/lib/middleware/auth"; +import { withUserAuth, withReadAuth } from "@/lib/middleware/auth"; +import { getAllTaskTemplates } from "@/lib/queries/tasks"; // POST: create new template async function createTaskHandler(req) { @@ -20,5 +21,12 @@ async function createTaskHandler(req) { return NextResponse.json({ success: true }); } +// GET: Get all task templates +async function getTasksHandler(req) { + const templates = getAllTaskTemplates(); + return NextResponse.json(templates); +} + // Protected routes - require authentication +export const GET = withReadAuth(getTasksHandler); export const POST = withUserAuth(createTaskHandler); diff --git a/src/lib/middleware/auth.js b/src/lib/middleware/auth.js index 1adcba7..70169c2 100644 --- a/src/lib/middleware/auth.js +++ b/src/lib/middleware/auth.js @@ -10,20 +10,21 @@ const ROLE_HIERARCHY = { } export function withAuth(handler, options = {}) { - return async (req, context) => { + return auth(async (req) => { try { - const session = await auth(req) - // Check if user is authenticated - if (!session?.user) { + if (!req.auth?.user) { + console.log("No session found for request to:", req.url) return NextResponse.json( { error: "Authentication required" }, { status: 401 } ) } + console.log("Session found for user:", req.auth.user.email) + // Check role-based permissions (without database access) - if (options.requiredRole && !hasPermission(session.user.role, options.requiredRole)) { + if (options.requiredRole && !hasPermission(req.auth.user.role, options.requiredRole)) { return NextResponse.json( { error: "Insufficient permissions" }, { status: 403 } @@ -32,14 +33,14 @@ export function withAuth(handler, options = {}) { // Add user info to request req.user = { - id: session.user.id, - email: session.user.email, - name: session.user.name, - role: session.user.role + id: req.auth.user.id, + email: req.auth.user.email, + name: req.auth.user.name, + role: req.auth.user.role } // Call the original handler - return await handler(req, context) + return await handler(req) } catch (error) { console.error("Auth middleware error:", error) return NextResponse.json( @@ -47,7 +48,7 @@ export function withAuth(handler, options = {}) { { status: 500 } ) } - } + }) } export function hasPermission(userRole, requiredRole) { @@ -64,12 +65,12 @@ export function withUserAuth(handler) { return withAuth(handler, { requiredRole: 'user' }) } +// Helper for admin-level operations +export function withAdminAuth(handler) { + return withAuth(handler, { requiredRole: 'admin' }) +} + // 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' }) -} diff --git a/test-auth-api.mjs b/test-auth-api.mjs new file mode 100644 index 0000000..a88b3f4 --- /dev/null +++ b/test-auth-api.mjs @@ -0,0 +1,109 @@ +// Test authenticated API access using NextAuth.js client-side approach + +const BASE_URL = 'http://localhost:3000'; + +async function testAuthenticatedAPI() { + console.log('šŸ” Testing Authenticated API Access\n'); + + try { + // Test 1: Check if server is running + console.log('1ļøāƒ£ Checking server status...'); + const healthResponse = await fetch(`${BASE_URL}/api/auth/session`); + console.log(`Server status: ${healthResponse.status}`); + + if (!healthResponse.ok) { + console.log('āŒ Server not responding properly'); + return; + } + + // Test 2: Test unauthenticated access to protected endpoints + console.log('\n2ļøāƒ£ Testing unauthenticated access...'); + const protectedEndpoints = [ + '/api/projects', + '/api/contracts', + '/api/tasks', + '/api/project-tasks' + ]; + + for (const endpoint of protectedEndpoints) { + const response = await fetch(`${BASE_URL}${endpoint}`); + console.log(`${endpoint}: ${response.status} ${response.status === 401 ? 'āœ… (properly protected)' : 'āŒ (not protected)'}`); + } + + // Test 3: Check protected pages + console.log('\n3ļøāƒ£ Testing protected pages...'); + const protectedPages = ['/projects', '/contracts', '/tasks']; + + for (const page of protectedPages) { + const response = await fetch(`${BASE_URL}${page}`, { + redirect: 'manual' + }); + + if (response.status === 302) { + const location = response.headers.get('location'); + if (location && location.includes('/auth/signin')) { + console.log(`${page}: āœ… Properly redirects to sign-in`); + } else { + console.log(`${page}: āš ļø Redirects to: ${location}`); + } + } else if (response.status === 200) { + console.log(`${page}: āŒ Accessible without authentication`); + } else { + console.log(`${page}: ā“ Status ${response.status}`); + } + } + + // Test 4: Test sign-in page accessibility + console.log('\n4ļøāƒ£ Testing sign-in page...'); + const signinResponse = await fetch(`${BASE_URL}/auth/signin`); + if (signinResponse.ok) { + console.log('āœ… Sign-in page accessible'); + const content = await signinResponse.text(); + const hasEmailField = content.includes('name="email"') || content.includes('id="email"'); + const hasPasswordField = content.includes('name="password"') || content.includes('id="password"'); + console.log(` Email field: ${hasEmailField ? 'āœ…' : 'āŒ'}`); + console.log(` Password field: ${hasPasswordField ? 'āœ…' : 'āŒ'}`); + } else { + console.log('āŒ Sign-in page not accessible'); + } + + // Test 5: Check NextAuth.js providers endpoint + console.log('\n5ļøāƒ£ Testing NextAuth.js configuration...'); + const providersResponse = await fetch(`${BASE_URL}/api/auth/providers`); + if (providersResponse.ok) { + const providers = await providersResponse.json(); + console.log('āœ… NextAuth.js providers endpoint accessible'); + console.log('Available providers:', Object.keys(providers)); + } else { + console.log('āŒ NextAuth.js providers endpoint failed'); + } + + // Test 6: Check CSRF token endpoint + console.log('\n6ļøāƒ£ Testing CSRF token...'); + const csrfResponse = await fetch(`${BASE_URL}/api/auth/csrf`); + if (csrfResponse.ok) { + const csrf = await csrfResponse.json(); + console.log('āœ… CSRF token endpoint accessible'); + console.log('CSRF token available:', !!csrf.csrfToken); + } else { + console.log('āŒ CSRF token endpoint failed'); + } + + console.log('\nšŸŽÆ Manual Testing Instructions:'); + console.log('1. Open browser to: http://localhost:3000/auth/signin'); + console.log('2. Use credentials:'); + console.log(' Email: admin@localhost.com'); + console.log(' Password: admin123456'); + console.log('3. After login, test these pages:'); + protectedPages.forEach(page => { + console.log(` - http://localhost:3000${page}`); + }); + console.log('4. Test API endpoints with browser dev tools or Postman'); + + } catch (error) { + console.error('āŒ Test failed with error:', error.message); + } +} + +// Run the test +testAuthenticatedAPI(); diff --git a/test-logged-in-flow.mjs b/test-logged-in-flow.mjs new file mode 100644 index 0000000..4e0b70d --- /dev/null +++ b/test-logged-in-flow.mjs @@ -0,0 +1,206 @@ +// Test authenticated flow without external dependencies + +const BASE_URL = 'http://localhost:3000'; + +// Test data +const TEST_CREDENTIALS = { + email: 'admin@localhost.com', + password: 'admin123456' +}; + +// Helper function to extract cookies from response +function extractCookies(response) { + const cookies = response.headers.raw()['set-cookie']; + if (!cookies) return ''; + + return cookies + .map(cookie => cookie.split(';')[0]) + .join('; '); +} + +// Helper function to make authenticated requests +async function makeAuthenticatedRequest(url, options = {}, cookies = '') { + return fetch(url, { + ...options, + headers: { + 'Cookie': cookies, + 'Content-Type': 'application/json', + ...options.headers + } + }); +} + +async function testCompleteAuthenticatedFlow() { + console.log('šŸ” Testing Complete Authenticated Flow\n'); + + try { + // Step 1: Get CSRF token from sign-in page + console.log('1ļøāƒ£ Getting CSRF token...'); + const signinResponse = await fetch(`${BASE_URL}/auth/signin`); + const signinHtml = await signinResponse.text(); + + // Extract CSRF token (NextAuth.js typically includes it in the form) + const csrfMatch = signinHtml.match(/name="csrfToken" value="([^"]+)"/); + const csrfToken = csrfMatch ? csrfMatch[1] : null; + + if (!csrfToken) { + console.log('āŒ Could not extract CSRF token'); + return; + } + + console.log('āœ… CSRF token extracted'); + const initialCookies = extractCookies(signinResponse); + + // Step 2: Attempt login + console.log('\n2ļøāƒ£ Attempting login...'); + const loginResponse = await fetch(`${BASE_URL}/api/auth/callback/credentials`, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Cookie': initialCookies + }, + body: new URLSearchParams({ + csrfToken, + email: TEST_CREDENTIALS.email, + password: TEST_CREDENTIALS.password, + callbackUrl: `${BASE_URL}/projects`, + json: 'true' + }), + redirect: 'manual' + }); + + console.log(`Login response status: ${loginResponse.status}`); + + if (loginResponse.status === 200) { + const loginResult = await loginResponse.json(); + console.log('Login result:', loginResult); + + if (loginResult.url) { + console.log('āœ… Login successful, redirecting to:', loginResult.url); + } else if (loginResult.error) { + console.log('āŒ Login failed:', loginResult.error); + return; + } + } else if (loginResponse.status === 302) { + console.log('āœ… Login successful (redirect)'); + } else { + console.log('āŒ Login failed with status:', loginResponse.status); + const errorText = await loginResponse.text(); + console.log('Error response:', errorText.substring(0, 500)); + return; + } + + // Get session cookies + const sessionCookies = extractCookies(loginResponse) || initialCookies; + console.log('Session cookies:', sessionCookies ? 'Present' : 'Missing'); + + // Step 3: Test session endpoint + console.log('\n3ļøāƒ£ Testing session endpoint...'); + const sessionResponse = await makeAuthenticatedRequest( + `${BASE_URL}/api/auth/session`, + {}, + sessionCookies + ); + + if (sessionResponse.ok) { + const session = await sessionResponse.json(); + console.log('āœ… Session data:', JSON.stringify(session, null, 2)); + } else { + console.log('āŒ Session check failed:', sessionResponse.status); + } + + // Step 4: Test protected pages + console.log('\n4ļøāƒ£ Testing protected pages...'); + const protectedPages = ['/projects', '/contracts', '/tasks']; + + for (const page of protectedPages) { + const pageResponse = await makeAuthenticatedRequest( + `${BASE_URL}${page}`, + {}, + sessionCookies + ); + + if (pageResponse.ok) { + console.log(`āœ… ${page} - accessible`); + } else if (pageResponse.status === 302) { + console.log(`āš ļø ${page} - redirected (status: 302)`); + } else { + console.log(`āŒ ${page} - failed (status: ${pageResponse.status})`); + } + } + + // Step 5: Test API endpoints + console.log('\n5ļøāƒ£ Testing API endpoints...'); + const apiEndpoints = [ + { url: '/api/projects', method: 'GET' }, + { url: '/api/contracts', method: 'GET' }, + { url: '/api/tasks', method: 'GET' }, + { url: '/api/tasks/templates', method: 'GET' } + ]; + + for (const endpoint of apiEndpoints) { + const apiResponse = await makeAuthenticatedRequest( + `${BASE_URL}${endpoint.url}`, + { method: endpoint.method }, + sessionCookies + ); + + if (apiResponse.ok) { + const data = await apiResponse.json(); + console.log(`āœ… ${endpoint.method} ${endpoint.url} - success (${Array.isArray(data) ? data.length : 'object'} items)`); + } else if (apiResponse.status === 401) { + console.log(`āŒ ${endpoint.method} ${endpoint.url} - unauthorized (status: 401)`); + } else { + console.log(`āŒ ${endpoint.method} ${endpoint.url} - failed (status: ${apiResponse.status})`); + const errorText = await apiResponse.text(); + console.log(` Error: ${errorText.substring(0, 200)}`); + } + } + + // Step 6: Test creating data + console.log('\n6ļøāƒ£ Testing data creation...'); + + // Test creating a project + const projectData = { + name: 'Test Project Auth', + description: 'Testing authentication flow', + deadline: '2025-12-31', + status: 'active' + }; + + const createProjectResponse = await makeAuthenticatedRequest( + `${BASE_URL}/api/projects`, + { + method: 'POST', + body: JSON.stringify(projectData) + }, + sessionCookies + ); + + if (createProjectResponse.ok) { + const newProject = await createProjectResponse.json(); + console.log('āœ… Project creation successful:', newProject.name); + + // Clean up - delete the test project + const deleteResponse = await makeAuthenticatedRequest( + `${BASE_URL}/api/projects/${newProject.id}`, + { method: 'DELETE' }, + sessionCookies + ); + + if (deleteResponse.ok) { + console.log('āœ… Test project cleaned up'); + } + } else { + console.log('āŒ Project creation failed:', createProjectResponse.status); + const errorText = await createProjectResponse.text(); + console.log(' Error:', errorText.substring(0, 200)); + } + + } catch (error) { + console.error('āŒ Test failed with error:', error.message); + } +} + +// Run the test +testCompleteAuthenticatedFlow();