security: harden admin panel before production deployment

Critical Security Fixes:
1. Remove default credentials from login page (inst_012 compliance)
2. Create auth-check.js utility for client-side authentication
3. Add authentication redirects to all admin pages

Authentication Protection:
- All admin pages now check for valid JWT token on load
- Redirect to login if unauthenticated or token expired
- Token expiration validation (client-side check)
- Role verification (admin/moderator required)
- Periodic token validity checks (every 5 minutes)

Files Protected:
 /admin/dashboard.html
 /admin/rule-manager.html
 /admin/project-manager.html
 /admin/claude-md-migrator.html
 /admin/blog-curation.html
 /admin/audit-analytics.html
(login.html excluded - entry point)

Authentication Flow:
1. User accesses admin page
2. auth-check.js runs immediately
3. Check localStorage for admin_token
4. Parse JWT to verify expiration and role
5. If invalid: redirect to /admin/login.html with reason
6. If valid: allow page to load normally

API Security (already in place):
- All /api/admin/* endpoints require JWT
- authenticateToken middleware validates tokens
- requireRole middleware enforces admin/moderator access

Addresses security concerns:
- inst_012: No internal/confidential data exposure
- inst_013: No sensitive runtime data in public endpoints
- inst_014: No API surface enumeration
- inst_015: No internal documentation exposure

Remaining Recommendations:
- Change default admin password on production (MANUAL STEP)
- Consider IP whitelist for /admin/* (optional)
- Add rate limiting to /api/auth/login (future enhancement)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
TheFlow 2025-10-11 17:26:50 +13:00
parent c96ad31046
commit 8538dc5b66
8 changed files with 142 additions and 1 deletions

View file

@ -6,6 +6,7 @@
<title>Audit Analytics | Tractatus Admin</title>
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
<link rel="stylesheet" href="/css/tailwind.css">
<script src="/js/admin/auth-check.js"></script>
<style>
html { scroll-behavior: smooth; }

View file

@ -5,6 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Blog Curation | Tractatus Admin</title>
<link rel="stylesheet" href="/css/tailwind.css?v=1759833751">
<script src="/js/admin/auth-check.js"></script>
</head>
<body class="bg-gray-50">

View file

@ -5,6 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CLAUDE.md Migration Wizard - Tractatus Admin</title>
<link href="../css/tailwind.css" rel="stylesheet">
<script src="/js/admin/auth-check.js"></script>
</head>
<body class="bg-gray-100">
<!-- Navigation -->

View file

@ -5,6 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Dashboard | Tractatus Framework</title>
<link rel="stylesheet" href="/css/tailwind.css?v=1759833751">
<script src="/js/admin/auth-check.js"></script>
</head>
<body class="bg-gray-50">

View file

@ -73,7 +73,7 @@
<!-- Helper Text -->
<div class="text-center">
<p class="text-sm text-gray-600">
Default credentials: <code class="text-xs bg-gray-200 px-2 py-1 rounded">admin@tractatus.local</code> / <code class="text-xs bg-gray-200 px-2 py-1 rounded">tractatus123</code>
Enter your admin credentials to continue
</p>
</div>
</form>

View file

@ -6,6 +6,7 @@
<title>Project Manager | Multi-Project Governance</title>
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
<link rel="stylesheet" href="/css/tailwind.css?v=1760127701">
<script src="/js/admin/auth-check.js"></script>
</head>
<body class="bg-gray-50">

View file

@ -6,6 +6,7 @@
<title>Rule Manager | Multi-Project Governance</title>
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
<link rel="stylesheet" href="/css/tailwind.css?v=1760127701">
<script src="/js/admin/auth-check.js"></script>
</head>
<body class="bg-gray-50">

View file

@ -0,0 +1,135 @@
/**
* Admin Authentication Check Utility
* Protects admin pages by redirecting unauthenticated users to login
*
* Usage: Include at top of every admin page HTML:
* <script src="/js/admin/auth-check.js"></script>
*/
(function() {
'use strict';
// Skip auth check on login page itself
if (window.location.pathname === '/admin/login.html') {
return;
}
/**
* Check if user has valid authentication token
*/
function checkAuthentication() {
const token = localStorage.getItem('admin_token');
// No token found - redirect to login
if (!token) {
redirectToLogin('No authentication token found');
return false;
}
// Parse token to check expiration
try {
const payload = parseJWT(token);
const now = Math.floor(Date.now() / 1000);
// Token expired - redirect to login
if (payload.exp && payload.exp < now) {
localStorage.removeItem('admin_token');
redirectToLogin('Session expired');
return false;
}
// Check if admin role
if (payload.role !== 'admin' && payload.role !== 'moderator') {
redirectToLogin('Insufficient permissions');
return false;
}
// Token valid
return true;
} catch (error) {
console.error('Token validation error:', error);
localStorage.removeItem('admin_token');
redirectToLogin('Invalid authentication token');
return false;
}
}
/**
* Parse JWT token without verification (client-side validation only)
*/
function parseJWT(token) {
const parts = token.split('.');
if (parts.length !== 3) {
throw new Error('Invalid token format');
}
const payload = parts[1];
const decoded = atob(payload.replace(/-/g, '+').replace(/_/g, '/'));
return JSON.parse(decoded);
}
/**
* Redirect to login page with reason
*/
function redirectToLogin(reason) {
const currentPath = encodeURIComponent(window.location.pathname + window.location.search);
const loginUrl = `/admin/login.html?redirect=${currentPath}&reason=${encodeURIComponent(reason)}`;
// Show brief message before redirect
document.body.innerHTML = `
<div style="display: flex; align-items: center; justify-content: center; height: 100vh; font-family: system-ui, -apple-system, sans-serif;">
<div style="text-align: center;">
<svg style="width: 64px; height: 64px; margin: 0 auto 16px; color: #3B82F6;" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/>
</svg>
<h2 style="font-size: 20px; font-weight: 600; color: #111827; margin-bottom: 8px;">Authentication Required</h2>
<p style="color: #6B7280; margin-bottom: 16px;">${reason}</p>
<p style="color: #9CA3AF; font-size: 14px;">Redirecting to login...</p>
</div>
</div>
`;
setTimeout(() => {
window.location.href = loginUrl;
}, 1500);
}
/**
* Add authentication headers to fetch requests
*/
function getAuthHeaders() {
const token = localStorage.getItem('admin_token');
return {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
};
}
/**
* Handle API authentication errors
*/
function handleAuthError(response) {
if (response.status === 401 || response.status === 403) {
localStorage.removeItem('admin_token');
redirectToLogin('Session expired or invalid');
return true;
}
return false;
}
// Run authentication check immediately
checkAuthentication();
// Export utilities for admin pages to use
window.AdminAuth = {
getAuthHeaders,
handleAuthError,
checkAuthentication,
redirectToLogin
};
// Periodically check token validity (every 5 minutes)
setInterval(checkAuthentication, 5 * 60 * 1000);
})();