feat: add authentication test page and API testing scripts; implement debug auth endpoint and enhance task route with read permissions

This commit is contained in:
2025-06-25 12:54:37 +02:00
parent c1bb4c44fd
commit 1524e1e9bb
6 changed files with 520 additions and 17 deletions

142
public/test-auth.html Normal file
View File

@@ -0,0 +1,142 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Authentication Test Page</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.test-section { margin: 20px 0; padding: 20px; border: 1px solid #ccc; border-radius: 5px; }
.result { margin: 10px 0; padding: 10px; border-radius: 3px; }
.success { background-color: #d4edda; border: 1px solid #c3e6cb; color: #155724; }
.error { background-color: #f8d7da; border: 1px solid #f5c6cb; color: #721c24; }
.info { background-color: #d1ecf1; border: 1px solid #bee5eb; color: #0c5460; }
button { padding: 10px 20px; margin: 5px; cursor: pointer; }
pre { background: #f8f9fa; padding: 10px; border-radius: 3px; overflow-x: auto; }
</style>
</head>
<body>
<h1>Authentication & API Test Page</h1>
<div class="test-section">
<h2>Authentication Status</h2>
<button onclick="checkAuthStatus()">Check Authentication Status</button>
<div id="authStatus"></div>
</div>
<div class="test-section">
<h2>API Endpoint Tests</h2>
<button onclick="testAllEndpoints()">Test All API Endpoints</button>
<div id="apiResults"></div>
</div>
<div class="test-section">
<h2>Manual Login Instructions</h2>
<div class="info">
<p><strong>Test Credentials:</strong></p>
<p>Email: <code>admin@localhost.com</code></p>
<p>Password: <code>admin123456</code></p>
<p><a href="/auth/signin" target="_blank">Open Sign-in Page</a></p>
</div>
</div>
<script>
async function checkAuthStatus() {
const statusDiv = document.getElementById('authStatus');
statusDiv.innerHTML = '<div class="info">Checking authentication status...</div>';
try {
const response = await fetch('/api/auth/session');
const session = await response.json();
if (session && session.user) {
statusDiv.innerHTML = `
<div class="success">
<h3>✅ Authenticated</h3>
<pre>${JSON.stringify(session, null, 2)}</pre>
</div>
`;
} else {
statusDiv.innerHTML = `
<div class="error">
<h3>❌ Not Authenticated</h3>
<p>Please <a href="/auth/signin">sign in</a> first.</p>
</div>
`;
}
} catch (error) {
statusDiv.innerHTML = `
<div class="error">
<h3>❌ Error checking authentication</h3>
<p>${error.message}</p>
</div>
`;
}
}
async function testAllEndpoints() {
const resultsDiv = document.getElementById('apiResults');
resultsDiv.innerHTML = '<div class="info">Testing API endpoints...</div>';
const endpoints = [
{ url: '/api/debug-auth', method: 'GET', name: 'Debug Auth' },
{ url: '/api/projects', method: 'GET', name: 'Projects' },
{ url: '/api/contracts', method: 'GET', name: 'Contracts' },
{ url: '/api/tasks', method: 'GET', name: 'Tasks' },
{ url: '/api/tasks/templates', method: 'GET', name: 'Task Templates' },
{ url: '/api/project-tasks', method: 'GET', name: 'Project Tasks' }
];
let results = '';
for (const endpoint of endpoints) {
try {
const response = await fetch(endpoint.url, {
method: endpoint.method,
headers: {
'Content-Type': 'application/json'
}
});
if (response.ok) {
const data = await response.json();
const count = Array.isArray(data) ? data.length : 'object';
results += `
<div class="success">
<strong>✅ ${endpoint.name}</strong> (${endpoint.method} ${endpoint.url})
<br>Status: ${response.status} | Data: ${count} items
</div>
`;
} else if (response.status === 401) {
results += `
<div class="error">
<strong>🔒 ${endpoint.name}</strong> (${endpoint.method} ${endpoint.url})
<br>Status: ${response.status} - Unauthorized (Please sign in)
</div>
`;
} else {
results += `
<div class="error">
<strong>❌ ${endpoint.name}</strong> (${endpoint.method} ${endpoint.url})
<br>Status: ${response.status} - ${response.statusText}
</div>
`;
}
} catch (error) {
results += `
<div class="error">
<strong>💥 ${endpoint.name}</strong> (${endpoint.method} ${endpoint.url})
<br>Error: ${error.message}
</div>
`;
}
}
resultsDiv.innerHTML = results;
}
// Auto-check authentication status on page load
window.addEventListener('load', checkAuthStatus);
</script>
</body>
</html>

View File

@@ -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 })
}
})

View File

@@ -1,6 +1,7 @@
import db from "@/lib/db"; import db from "@/lib/db";
import { NextResponse } from "next/server"; 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 // POST: create new template
async function createTaskHandler(req) { async function createTaskHandler(req) {
@@ -20,5 +21,12 @@ async function createTaskHandler(req) {
return NextResponse.json({ success: true }); 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 // Protected routes - require authentication
export const GET = withReadAuth(getTasksHandler);
export const POST = withUserAuth(createTaskHandler); export const POST = withUserAuth(createTaskHandler);

View File

@@ -10,20 +10,21 @@ const ROLE_HIERARCHY = {
} }
export function withAuth(handler, options = {}) { export function withAuth(handler, options = {}) {
return async (req, context) => { return auth(async (req) => {
try { try {
const session = await auth(req)
// Check if user is authenticated // 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( return NextResponse.json(
{ error: "Authentication required" }, { error: "Authentication required" },
{ status: 401 } { status: 401 }
) )
} }
console.log("Session found for user:", req.auth.user.email)
// Check role-based permissions (without database access) // 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( return NextResponse.json(
{ error: "Insufficient permissions" }, { error: "Insufficient permissions" },
{ status: 403 } { status: 403 }
@@ -32,14 +33,14 @@ export function withAuth(handler, options = {}) {
// Add user info to request // Add user info to request
req.user = { req.user = {
id: session.user.id, id: req.auth.user.id,
email: session.user.email, email: req.auth.user.email,
name: session.user.name, name: req.auth.user.name,
role: session.user.role role: req.auth.user.role
} }
// Call the original handler // Call the original handler
return await handler(req, context) return await handler(req)
} catch (error) { } catch (error) {
console.error("Auth middleware error:", error) console.error("Auth middleware error:", error)
return NextResponse.json( return NextResponse.json(
@@ -47,7 +48,7 @@ export function withAuth(handler, options = {}) {
{ status: 500 } { status: 500 }
) )
} }
} })
} }
export function hasPermission(userRole, requiredRole) { export function hasPermission(userRole, requiredRole) {
@@ -64,12 +65,12 @@ export function withUserAuth(handler) {
return withAuth(handler, { requiredRole: 'user' }) return withAuth(handler, { requiredRole: 'user' })
} }
// Helper for admin-level operations
export function withAdminAuth(handler) {
return withAuth(handler, { requiredRole: 'admin' })
}
// Helper for project manager operations // Helper for project manager operations
export function withManagerAuth(handler) { 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' })
}

109
test-auth-api.mjs Normal file
View File

@@ -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();

206
test-logged-in-flow.mjs Normal file
View File

@@ -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();