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:
parent
c96ad31046
commit
8538dc5b66
8 changed files with 142 additions and 1 deletions
|
|
@ -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; }
|
||||
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
||||
|
|
|
|||
|
|
@ -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 -->
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
||||
|
|
|
|||
135
public/js/admin/auth-check.js
Normal file
135
public/js/admin/auth-check.js
Normal 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);
|
||||
|
||||
})();
|
||||
Loading…
Add table
Reference in a new issue