- Create Economist SubmissionTracking package correctly: * mainArticle = full blog post content * coverLetter = 216-word SIR— letter * Links to blog post via blogPostId - Archive 'Letter to The Economist' from blog posts (it's the cover letter) - Fix date display on article cards (use published_at) - Target publication already displaying via blue badge Database changes: - Make blogPostId optional in SubmissionTracking model - Economist package ID: 68fa85ae49d4900e7f2ecd83 - Le Monde package ID: 68fa2abd2e6acd5691932150 Next: Enhanced modal with tabs, validation, export 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
135 lines
3.7 KiB
JavaScript
135 lines
3.7 KiB
JavaScript
/**
|
|
* 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 class="auth-error-container">
|
|
<div class="auth-error-content">
|
|
<svg class="auth-error-icon" 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 class="auth-error-title">Authentication Required</h2>
|
|
<p class="auth-error-message">${reason}</p>
|
|
<p class="auth-error-redirect">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);
|
|
|
|
})();
|