refactor: migrate vendor APIs to token-based context and consolidate architecture
## Vendor-in-Token Architecture (Complete Migration) - Migrate all vendor API endpoints from require_vendor_context() to token_vendor_id - Update permission dependencies to extract vendor from JWT token - Add vendor exceptions: VendorAccessDeniedException, VendorOwnerOnlyException, InsufficientVendorPermissionsException - Shop endpoints retain require_vendor_context() for URL-based detection - Add AUTH-004 architecture rule enforcing vendor context patterns - Fix marketplace router missing /marketplace prefix ## Exception Pattern Fixes (API-003/API-004) - Services raise domain exceptions, endpoints let them bubble up - Add code_quality and content_page exception modules - Move business logic from endpoints to services (admin, auth, content_page) - Fix exception handling in admin, shop, and vendor endpoints ## Tailwind CSS Consolidation - Consolidate CSS to per-area files (admin, vendor, shop, platform) - Remove shared/cdn-fallback.html and shared/css/tailwind.min.css - Update all templates to use area-specific Tailwind output files - Remove Node.js config (package.json, postcss.config.js, tailwind.config.js) ## Documentation & Cleanup - Update vendor-in-token-architecture.md with completed migration status - Update architecture-rules.md with new rules - Move migration docs to docs/development/migration/ - Remove duplicate/obsolete documentation files - Merge pytest.ini settings into pyproject.toml 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,519 +1,253 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Auth Flow Testing - Admin Panel</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
{# app/templates/admin/test-auth-flow.html #}
|
||||
{% extends 'admin/base.html' %}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||
padding: 20px;
|
||||
background: #f5f5f5;
|
||||
line-height: 1.6;
|
||||
}
|
||||
{% block title %}Auth Flow Testing{% endblock %}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
padding: 30px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #333;
|
||||
margin-bottom: 10px;
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
color: #666;
|
||||
margin-bottom: 30px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.test-section {
|
||||
margin-bottom: 30px;
|
||||
padding: 20px;
|
||||
background: #f9f9f9;
|
||||
border-radius: 6px;
|
||||
border-left: 4px solid #3b82f6;
|
||||
}
|
||||
|
||||
.test-section h2 {
|
||||
color: #333;
|
||||
margin-bottom: 15px;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.test-description {
|
||||
color: #666;
|
||||
margin-bottom: 15px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.test-steps {
|
||||
background: white;
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.test-steps ol {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.test-steps li {
|
||||
margin-bottom: 8px;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
.expected-result {
|
||||
background: #e8f5e9;
|
||||
padding: 12px;
|
||||
border-radius: 4px;
|
||||
border-left: 3px solid #4caf50;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.expected-result strong {
|
||||
color: #2e7d32;
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.expected-result ul {
|
||||
margin-left: 20px;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 12px 24px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
button:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: #3b82f6;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: #2563eb;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: #ef4444;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-danger:hover {
|
||||
background: #dc2626;
|
||||
}
|
||||
|
||||
.btn-warning {
|
||||
background: #f59e0b;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-warning:hover {
|
||||
background: #d97706;
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background: #10b981;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-success:hover {
|
||||
background: #059669;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: #6b7280;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: #4b5563;
|
||||
}
|
||||
|
||||
.status-panel {
|
||||
background: #1e293b;
|
||||
color: #e2e8f0;
|
||||
padding: 20px;
|
||||
border-radius: 6px;
|
||||
margin-top: 30px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.status-panel h3 {
|
||||
color: #38bdf8;
|
||||
margin-bottom: 15px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
}
|
||||
|
||||
.status-item {
|
||||
margin-bottom: 8px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.status-label {
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.status-value {
|
||||
color: #34d399;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-value.false {
|
||||
color: #f87171;
|
||||
}
|
||||
|
||||
.log-level-control {
|
||||
background: #fef3c7;
|
||||
padding: 15px;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 30px;
|
||||
border-left: 4px solid #f59e0b;
|
||||
}
|
||||
|
||||
.log-level-control h3 {
|
||||
color: #92400e;
|
||||
margin-bottom: 10px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.log-level-buttons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.log-level-buttons button {
|
||||
padding: 8px 16px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.warning-box {
|
||||
background: #fef2f2;
|
||||
border: 1px solid #fecaca;
|
||||
border-radius: 6px;
|
||||
padding: 15px;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.warning-box h3 {
|
||||
color: #991b1b;
|
||||
margin-bottom: 10px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.warning-box ul {
|
||||
margin-left: 20px;
|
||||
color: #7f1d1d;
|
||||
}
|
||||
|
||||
.warning-box li {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>🧪 Auth Flow Testing</h1>
|
||||
<p class="subtitle">Comprehensive testing for the Jinja2 migration auth loop fix</p>
|
||||
|
||||
<!-- Log Level Control -->
|
||||
<div class="log-level-control">
|
||||
<h3>📊 Log Level Control</h3>
|
||||
<p style="color: #78350f; font-size: 13px; margin-bottom: 10px;">
|
||||
Change logging verbosity for login.js and api-client.js
|
||||
{% block content %}
|
||||
<div x-data="authFlowTest()" x-init="init()">
|
||||
{# Page Header #}
|
||||
<div class="flex flex-col md:flex-row md:items-center md:justify-between mb-8">
|
||||
<div>
|
||||
<h2 class="text-2xl font-semibold text-gray-700 dark:text-gray-200">
|
||||
Auth Flow Testing
|
||||
</h2>
|
||||
<p class="text-gray-600 dark:text-gray-400 mt-1">
|
||||
Comprehensive testing for Jinja2 migration auth loop fix
|
||||
</p>
|
||||
<div class="log-level-buttons">
|
||||
<button onclick="setLogLevel(0)" class="btn-secondary">0 - None</button>
|
||||
<button onclick="setLogLevel(1)" class="btn-danger">1 - Errors Only</button>
|
||||
<button onclick="setLogLevel(2)" class="btn-warning">2 - Warnings</button>
|
||||
<button onclick="setLogLevel(3)" class="btn-success">3 - Info (Production)</button>
|
||||
<button onclick="setLogLevel(4)" class="btn-primary">4 - Debug (Full)</button>
|
||||
</div>
|
||||
<p style="color: #78350f; font-size: 12px; margin-top: 10px; font-style: italic;">
|
||||
Current levels: LOGIN = <span id="currentLoginLevel">4</span>, API = <span id="currentApiLevel">3</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Test 1: Clean Slate -->
|
||||
<div class="test-section">
|
||||
<h2>Test 1: Clean Slate - Fresh Login Flow</h2>
|
||||
<p class="test-description">
|
||||
Tests the complete login flow from scratch with no existing tokens.
|
||||
</p>
|
||||
|
||||
<div class="test-steps">
|
||||
<strong>Steps:</strong>
|
||||
<ol>
|
||||
<li>Click "Clear All Data" below</li>
|
||||
<li>Click "Navigate to /admin"</li>
|
||||
<li>Observe browser behavior and console logs</li>
|
||||
<li>You should land on login page</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div class="expected-result">
|
||||
<strong>✅ Expected Result:</strong>
|
||||
<ul>
|
||||
<li>Single redirect: /admin → /admin/login</li>
|
||||
<li>Login page loads with NO API calls to /admin/auth/me</li>
|
||||
<li>No loops, no errors in console</li>
|
||||
<li>Form is ready for input</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="button-group">
|
||||
<button onclick="clearAllData()" class="btn-danger">Clear All Data</button>
|
||||
<button onclick="navigateToAdmin()" class="btn-primary">Navigate to /admin</button>
|
||||
<button onclick="navigateToLogin()" class="btn-secondary">Go to Login</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Test 2: Login Success -->
|
||||
<div class="test-section">
|
||||
<h2>Test 2: Successful Login</h2>
|
||||
<p class="test-description">
|
||||
Tests that login works correctly and redirects to dashboard.
|
||||
</p>
|
||||
|
||||
<div class="test-steps">
|
||||
<strong>Steps:</strong>
|
||||
<ol>
|
||||
<li>Ensure you're on /admin/login</li>
|
||||
<li>Enter valid admin credentials</li>
|
||||
<li>Click "Login"</li>
|
||||
<li>Observe redirect and dashboard load</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div class="expected-result">
|
||||
<strong>✅ Expected Result:</strong>
|
||||
<ul>
|
||||
<li>Login API call succeeds (check Network tab)</li>
|
||||
<li>Token stored in localStorage</li>
|
||||
<li>Success message shows briefly</li>
|
||||
<li>Redirect to /admin/dashboard after 500ms</li>
|
||||
<li>Dashboard loads with stats and recent vendors</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="button-group">
|
||||
<button onclick="navigateToLogin()" class="btn-primary">Go to Login Page</button>
|
||||
<button onclick="checkAuthStatus()" class="btn-secondary">Check Auth Status</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Test 3: Dashboard Refresh -->
|
||||
<div class="test-section">
|
||||
<h2>Test 3: Dashboard Refresh (Authenticated)</h2>
|
||||
<p class="test-description">
|
||||
Tests that refreshing the dashboard works without redirect loops.
|
||||
</p>
|
||||
|
||||
<div class="test-steps">
|
||||
<strong>Steps:</strong>
|
||||
<ol>
|
||||
<li>Complete Test 2 (login successfully)</li>
|
||||
<li>On dashboard, press F5 or click "Refresh Page"</li>
|
||||
<li>Observe page reload behavior</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div class="expected-result">
|
||||
<strong>✅ Expected Result:</strong>
|
||||
<ul>
|
||||
<li>Dashboard reloads normally</li>
|
||||
<li>No redirects to login</li>
|
||||
<li>Stats and vendors load correctly</li>
|
||||
<li>No console errors</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="button-group">
|
||||
<button onclick="navigateToDashboard()" class="btn-primary">Go to Dashboard</button>
|
||||
<button onclick="window.location.reload()" class="btn-secondary">Refresh Page</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Test 4: Expired Token -->
|
||||
<div class="test-section">
|
||||
<h2>Test 4: Expired Token Handling</h2>
|
||||
<p class="test-description">
|
||||
Tests that expired tokens are handled gracefully with redirect to login.
|
||||
</p>
|
||||
|
||||
<div class="test-steps">
|
||||
<strong>Steps:</strong>
|
||||
<ol>
|
||||
<li>Click "Set Expired Token"</li>
|
||||
<li>Click "Navigate to Dashboard"</li>
|
||||
<li>Observe authentication failure and redirect</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div class="expected-result">
|
||||
<strong>✅ Expected Result:</strong>
|
||||
<ul>
|
||||
<li>Server detects expired token</li>
|
||||
<li>Returns 401 Unauthorized</li>
|
||||
<li>Browser redirects to /admin/login</li>
|
||||
<li>Token is cleared from localStorage</li>
|
||||
<li>No infinite loops</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="button-group">
|
||||
<button onclick="setExpiredToken()" class="btn-warning">Set Expired Token</button>
|
||||
<button onclick="navigateToDashboard()" class="btn-primary">Navigate to Dashboard</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Test 5: Direct Dashboard Access (No Token) -->
|
||||
<div class="test-section">
|
||||
<h2>Test 5: Direct Dashboard Access (Unauthenticated)</h2>
|
||||
<p class="test-description">
|
||||
Tests that accessing dashboard without token redirects to login.
|
||||
</p>
|
||||
|
||||
<div class="test-steps">
|
||||
<strong>Steps:</strong>
|
||||
<ol>
|
||||
<li>Click "Clear All Data"</li>
|
||||
<li>Click "Navigate to Dashboard"</li>
|
||||
<li>Observe immediate redirect to login</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div class="expected-result">
|
||||
<strong>✅ Expected Result:</strong>
|
||||
<ul>
|
||||
<li>Redirect from /admin/dashboard to /admin/login</li>
|
||||
<li>No API calls attempted</li>
|
||||
<li>Login page loads correctly</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="button-group">
|
||||
<button onclick="clearAllData()" class="btn-danger">Clear All Data</button>
|
||||
<button onclick="navigateToDashboard()" class="btn-primary">Navigate to Dashboard</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Test 6: Login Page with Valid Token -->
|
||||
<div class="test-section">
|
||||
<h2>Test 6: Login Page with Valid Token</h2>
|
||||
<p class="test-description">
|
||||
Tests what happens when user visits login page while already authenticated.
|
||||
</p>
|
||||
|
||||
<div class="test-steps">
|
||||
<strong>Steps:</strong>
|
||||
<ol>
|
||||
<li>Login successfully (Test 2)</li>
|
||||
<li>Click "Go to Login Page" below</li>
|
||||
<li>Observe behavior</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div class="expected-result">
|
||||
<strong>✅ Expected Result:</strong>
|
||||
<ul>
|
||||
<li>Login page loads</li>
|
||||
<li>Existing token is cleared (init() clears it)</li>
|
||||
<li>Form is displayed normally</li>
|
||||
<li>NO redirect loops</li>
|
||||
<li>NO API calls to validate token</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="button-group">
|
||||
<button onclick="setValidToken()" class="btn-success">Set Valid Token (Mock)</button>
|
||||
<button onclick="navigateToLogin()" class="btn-primary">Go to Login Page</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Status Panel -->
|
||||
<div class="status-panel">
|
||||
<h3>🔍 Current Auth Status</h3>
|
||||
<div id="statusDisplay">
|
||||
<div class="status-item">
|
||||
<span class="status-label">Current URL:</span>
|
||||
<span class="status-value" id="currentUrl">-</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-label">Has admin_token:</span>
|
||||
<span class="status-value" id="hasToken">-</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-label">Has admin_user:</span>
|
||||
<span class="status-value" id="hasUser">-</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-label">Token Preview:</span>
|
||||
<span class="status-value" id="tokenPreview">-</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-label">Username:</span>
|
||||
<span class="status-value" id="username">-</span>
|
||||
</div>
|
||||
</div>
|
||||
<button onclick="updateStatus()" style="margin-top: 15px; background: #38bdf8; color: #0f172a; padding: 8px 16px; border-radius: 4px; font-size: 12px; cursor: pointer; border: none;">
|
||||
🔄 Refresh Status
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Warning Box -->
|
||||
<div class="warning-box">
|
||||
<h3>⚠️ Important Notes</h3>
|
||||
<ul>
|
||||
<li>Always check browser console for detailed logs</li>
|
||||
<li>Use Network tab to see actual HTTP requests and redirects</li>
|
||||
<li>Clear browser cache if you see unexpected behavior</li>
|
||||
<li>Make sure FastAPI server is running on localhost:8000</li>
|
||||
<li>Valid admin credentials required for login tests</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Update status display
|
||||
function updateStatus() {
|
||||
{# Log Level Control #}
|
||||
<div class="px-4 py-3 mb-6 bg-yellow-50 dark:bg-yellow-900/20 rounded-lg shadow-md border-l-4 border-yellow-500">
|
||||
<h4 class="mb-2 text-lg font-semibold text-yellow-800 dark:text-yellow-200">Log Level Control</h4>
|
||||
<p class="text-sm text-yellow-700 dark:text-yellow-300 mb-3">
|
||||
Change logging verbosity for login.js and api-client.js
|
||||
</p>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<button @click="setLogLevel(0)" class="px-3 py-1 text-xs font-medium text-white bg-gray-600 rounded hover:bg-gray-700">0 - None</button>
|
||||
<button @click="setLogLevel(1)" class="px-3 py-1 text-xs font-medium text-white bg-red-600 rounded hover:bg-red-700">1 - Errors</button>
|
||||
<button @click="setLogLevel(2)" class="px-3 py-1 text-xs font-medium text-white bg-yellow-600 rounded hover:bg-yellow-700">2 - Warnings</button>
|
||||
<button @click="setLogLevel(3)" class="px-3 py-1 text-xs font-medium text-white bg-green-600 rounded hover:bg-green-700">3 - Info</button>
|
||||
<button @click="setLogLevel(4)" class="px-3 py-1 text-xs font-medium text-white bg-blue-600 rounded hover:bg-blue-700">4 - Debug</button>
|
||||
</div>
|
||||
<p class="text-xs text-yellow-600 dark:text-yellow-400 mt-2 italic">
|
||||
Current: LOGIN = <span x-text="currentLoginLevel">4</span>, API = <span x-text="currentApiLevel">3</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{# Test Sections Grid #}
|
||||
<div class="grid gap-6 mb-8 md:grid-cols-2">
|
||||
{# Test 1: Clean Slate #}
|
||||
<div class="px-4 py-3 bg-white rounded-lg shadow-md dark:bg-gray-800 border-l-4 border-blue-500">
|
||||
<h4 class="mb-2 text-lg font-semibold text-gray-600 dark:text-gray-300">
|
||||
Test 1: Clean Slate - Fresh Login
|
||||
</h4>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-3">
|
||||
Tests complete login flow from scratch with no existing tokens.
|
||||
</p>
|
||||
<div class="p-3 mb-3 bg-gray-50 dark:bg-gray-700 rounded text-sm">
|
||||
<ol class="list-decimal list-inside text-gray-700 dark:text-gray-300 space-y-1">
|
||||
<li>Clear All Data</li>
|
||||
<li>Navigate to /admin</li>
|
||||
<li>Should land on login page</li>
|
||||
</ol>
|
||||
</div>
|
||||
<div class="p-2 mb-3 bg-green-50 dark:bg-green-900/20 rounded border-l-3 border-green-500">
|
||||
<p class="text-xs text-green-700 dark:text-green-400">Expected: Single redirect /admin -> /admin/login, no loops</p>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<button @click="clearAllData()" class="px-3 py-1 text-xs font-medium text-white bg-red-600 rounded hover:bg-red-700">Clear All Data</button>
|
||||
<button @click="navigateTo('/admin')" class="px-3 py-1 text-xs font-medium text-white bg-blue-600 rounded hover:bg-blue-700">Go to /admin</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Test 2: Successful Login #}
|
||||
<div class="px-4 py-3 bg-white rounded-lg shadow-md dark:bg-gray-800 border-l-4 border-green-500">
|
||||
<h4 class="mb-2 text-lg font-semibold text-gray-600 dark:text-gray-300">
|
||||
Test 2: Successful Login
|
||||
</h4>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-3">
|
||||
Tests that login works correctly and redirects to dashboard.
|
||||
</p>
|
||||
<div class="p-3 mb-3 bg-gray-50 dark:bg-gray-700 rounded text-sm">
|
||||
<ol class="list-decimal list-inside text-gray-700 dark:text-gray-300 space-y-1">
|
||||
<li>Go to /admin/login</li>
|
||||
<li>Enter valid admin credentials</li>
|
||||
<li>Click Login</li>
|
||||
</ol>
|
||||
</div>
|
||||
<div class="p-2 mb-3 bg-green-50 dark:bg-green-900/20 rounded border-l-3 border-green-500">
|
||||
<p class="text-xs text-green-700 dark:text-green-400">Expected: Token stored, redirect to /admin/dashboard</p>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<button @click="navigateTo('/admin/login')" class="px-3 py-1 text-xs font-medium text-white bg-blue-600 rounded hover:bg-blue-700">Go to Login</button>
|
||||
<button @click="checkAuthStatus()" class="px-3 py-1 text-xs font-medium text-white bg-gray-600 rounded hover:bg-gray-700">Check Status</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Test 3: Dashboard Refresh #}
|
||||
<div class="px-4 py-3 bg-white rounded-lg shadow-md dark:bg-gray-800 border-l-4 border-purple-500">
|
||||
<h4 class="mb-2 text-lg font-semibold text-gray-600 dark:text-gray-300">
|
||||
Test 3: Dashboard Refresh
|
||||
</h4>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-3">
|
||||
Tests that refreshing dashboard works without redirect loops.
|
||||
</p>
|
||||
<div class="p-3 mb-3 bg-gray-50 dark:bg-gray-700 rounded text-sm">
|
||||
<ol class="list-decimal list-inside text-gray-700 dark:text-gray-300 space-y-1">
|
||||
<li>Complete Test 2 (login)</li>
|
||||
<li>Press F5 or click Refresh</li>
|
||||
<li>Dashboard should reload normally</li>
|
||||
</ol>
|
||||
</div>
|
||||
<div class="p-2 mb-3 bg-green-50 dark:bg-green-900/20 rounded border-l-3 border-green-500">
|
||||
<p class="text-xs text-green-700 dark:text-green-400">Expected: No redirect to login, stats load correctly</p>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<button @click="navigateTo('/admin/dashboard')" class="px-3 py-1 text-xs font-medium text-white bg-blue-600 rounded hover:bg-blue-700">Go to Dashboard</button>
|
||||
<button @click="window.location.reload()" class="px-3 py-1 text-xs font-medium text-white bg-gray-600 rounded hover:bg-gray-700">Refresh Page</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Test 4: Expired Token #}
|
||||
<div class="px-4 py-3 bg-white rounded-lg shadow-md dark:bg-gray-800 border-l-4 border-orange-500">
|
||||
<h4 class="mb-2 text-lg font-semibold text-gray-600 dark:text-gray-300">
|
||||
Test 4: Expired Token Handling
|
||||
</h4>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-3">
|
||||
Tests that expired tokens are handled gracefully.
|
||||
</p>
|
||||
<div class="p-3 mb-3 bg-gray-50 dark:bg-gray-700 rounded text-sm">
|
||||
<ol class="list-decimal list-inside text-gray-700 dark:text-gray-300 space-y-1">
|
||||
<li>Set Expired Token</li>
|
||||
<li>Navigate to Dashboard</li>
|
||||
<li>Should redirect to login</li>
|
||||
</ol>
|
||||
</div>
|
||||
<div class="p-2 mb-3 bg-green-50 dark:bg-green-900/20 rounded border-l-3 border-green-500">
|
||||
<p class="text-xs text-green-700 dark:text-green-400">Expected: 401 response, redirect to login, no loops</p>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<button @click="setExpiredToken()" class="px-3 py-1 text-xs font-medium text-white bg-orange-600 rounded hover:bg-orange-700">Set Expired Token</button>
|
||||
<button @click="navigateTo('/admin/dashboard')" class="px-3 py-1 text-xs font-medium text-white bg-blue-600 rounded hover:bg-blue-700">Go to Dashboard</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Test 5: Direct Access (No Token) #}
|
||||
<div class="px-4 py-3 bg-white rounded-lg shadow-md dark:bg-gray-800 border-l-4 border-red-500">
|
||||
<h4 class="mb-2 text-lg font-semibold text-gray-600 dark:text-gray-300">
|
||||
Test 5: Direct Access (Unauthenticated)
|
||||
</h4>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-3">
|
||||
Tests accessing dashboard without token redirects to login.
|
||||
</p>
|
||||
<div class="p-3 mb-3 bg-gray-50 dark:bg-gray-700 rounded text-sm">
|
||||
<ol class="list-decimal list-inside text-gray-700 dark:text-gray-300 space-y-1">
|
||||
<li>Clear All Data</li>
|
||||
<li>Navigate to Dashboard</li>
|
||||
<li>Should redirect to login</li>
|
||||
</ol>
|
||||
</div>
|
||||
<div class="p-2 mb-3 bg-green-50 dark:bg-green-900/20 rounded border-l-3 border-green-500">
|
||||
<p class="text-xs text-green-700 dark:text-green-400">Expected: Redirect to /admin/login, no API calls</p>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<button @click="clearAllData()" class="px-3 py-1 text-xs font-medium text-white bg-red-600 rounded hover:bg-red-700">Clear All Data</button>
|
||||
<button @click="navigateTo('/admin/dashboard')" class="px-3 py-1 text-xs font-medium text-white bg-blue-600 rounded hover:bg-blue-700">Go to Dashboard</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Test 6: Login with Valid Token #}
|
||||
<div class="px-4 py-3 bg-white rounded-lg shadow-md dark:bg-gray-800 border-l-4 border-teal-500">
|
||||
<h4 class="mb-2 text-lg font-semibold text-gray-600 dark:text-gray-300">
|
||||
Test 6: Login Page with Valid Token
|
||||
</h4>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-3">
|
||||
Tests visiting login page while already authenticated.
|
||||
</p>
|
||||
<div class="p-3 mb-3 bg-gray-50 dark:bg-gray-700 rounded text-sm">
|
||||
<ol class="list-decimal list-inside text-gray-700 dark:text-gray-300 space-y-1">
|
||||
<li>Login successfully (Test 2)</li>
|
||||
<li>Click Go to Login Page</li>
|
||||
<li>Token should be cleared</li>
|
||||
</ol>
|
||||
</div>
|
||||
<div class="p-2 mb-3 bg-green-50 dark:bg-green-900/20 rounded border-l-3 border-green-500">
|
||||
<p class="text-xs text-green-700 dark:text-green-400">Expected: Token cleared, form displayed, no loops</p>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<button @click="setMockToken()" class="px-3 py-1 text-xs font-medium text-white bg-green-600 rounded hover:bg-green-700">Set Mock Token</button>
|
||||
<button @click="navigateTo('/admin/login')" class="px-3 py-1 text-xs font-medium text-white bg-blue-600 rounded hover:bg-blue-700">Go to Login</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Status Panel #}
|
||||
<div class="px-4 py-3 bg-gray-800 rounded-lg shadow-md">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<h4 class="text-lg font-semibold text-gray-200">Current Auth Status</h4>
|
||||
<button @click="updateStatus()" class="px-3 py-1 text-xs text-gray-400 border border-gray-600 rounded hover:bg-gray-700">Refresh</button>
|
||||
</div>
|
||||
<div class="font-mono text-sm space-y-2">
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-500">Current URL:</span>
|
||||
<span class="text-blue-400" x-text="currentUrl">-</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-500">Has admin_token:</span>
|
||||
<span :class="hasToken ? 'text-green-400' : 'text-red-400'" x-text="hasToken ? 'Yes' : 'No'">-</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-500">Has admin_user:</span>
|
||||
<span :class="hasUser ? 'text-green-400' : 'text-red-400'" x-text="hasUser ? 'Yes' : 'No'">-</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-500">Token Preview:</span>
|
||||
<span class="text-green-400 truncate max-w-xs" x-text="tokenPreview">-</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-500">Username:</span>
|
||||
<span class="text-green-400" x-text="username">-</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Warning Box #}
|
||||
<div class="mt-6 px-4 py-3 bg-red-50 dark:bg-red-900/20 rounded-lg border border-red-200 dark:border-red-800">
|
||||
<h4 class="text-lg font-semibold text-red-700 dark:text-red-300 mb-2">Important Notes</h4>
|
||||
<ul class="list-disc list-inside text-sm text-red-600 dark:text-red-400 space-y-1">
|
||||
<li>Always check browser console for detailed logs</li>
|
||||
<li>Use Network tab to see actual HTTP requests and redirects</li>
|
||||
<li>Clear browser cache if you see unexpected behavior</li>
|
||||
<li>Make sure FastAPI server is running on localhost:8000</li>
|
||||
<li>Valid admin credentials required for login tests</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
function authFlowTest() {
|
||||
return {
|
||||
...data(),
|
||||
currentPage: 'auth-testing',
|
||||
|
||||
currentUrl: '-',
|
||||
hasToken: false,
|
||||
hasUser: false,
|
||||
tokenPreview: '-',
|
||||
username: '-',
|
||||
currentLoginLevel: 4,
|
||||
currentApiLevel: 3,
|
||||
|
||||
init() {
|
||||
this.updateStatus();
|
||||
setInterval(() => this.updateStatus(), 2000);
|
||||
console.log('Auth Flow Testing Script Loaded');
|
||||
},
|
||||
|
||||
updateStatus() {
|
||||
const token = localStorage.getItem('admin_token');
|
||||
const userStr = localStorage.getItem('admin_user');
|
||||
let user = null;
|
||||
@@ -524,121 +258,67 @@
|
||||
console.error('Failed to parse user data:', e);
|
||||
}
|
||||
|
||||
document.getElementById('currentUrl').textContent = window.location.href;
|
||||
this.currentUrl = window.location.href;
|
||||
this.hasToken = !!token;
|
||||
this.hasUser = !!user;
|
||||
this.tokenPreview = token ? token.substring(0, 30) + '...' : 'No token';
|
||||
this.username = user?.username || 'Not logged in';
|
||||
},
|
||||
|
||||
const hasTokenEl = document.getElementById('hasToken');
|
||||
hasTokenEl.textContent = token ? 'Yes' : 'No';
|
||||
hasTokenEl.className = token ? 'status-value' : 'status-value false';
|
||||
|
||||
const hasUserEl = document.getElementById('hasUser');
|
||||
hasUserEl.textContent = user ? 'Yes' : 'No';
|
||||
hasUserEl.className = user ? 'status-value' : 'status-value false';
|
||||
|
||||
document.getElementById('tokenPreview').textContent = token
|
||||
? token.substring(0, 30) + '...'
|
||||
: 'No token';
|
||||
|
||||
document.getElementById('username').textContent = user?.username || 'Not logged in';
|
||||
|
||||
console.log('📊 Status Updated:', {
|
||||
hasToken: !!token,
|
||||
hasUser: !!user,
|
||||
user: user
|
||||
});
|
||||
}
|
||||
|
||||
// Test functions
|
||||
function clearAllData() {
|
||||
console.log('🗑️ Clearing all localStorage data...');
|
||||
clearAllData() {
|
||||
console.log('Clearing all localStorage data...');
|
||||
localStorage.clear();
|
||||
console.log('✅ All data cleared');
|
||||
alert('✅ All localStorage data cleared!\n\nCheck console for details.');
|
||||
updateStatus();
|
||||
}
|
||||
console.log('All data cleared');
|
||||
alert('All localStorage data cleared!');
|
||||
this.updateStatus();
|
||||
},
|
||||
|
||||
function navigateToAdmin() {
|
||||
console.log('🚀 Navigating to /admin...');
|
||||
window.location.href = '/admin';
|
||||
}
|
||||
navigateTo(path) {
|
||||
console.log(`Navigating to ${path}...`);
|
||||
window.location.href = path;
|
||||
},
|
||||
|
||||
function navigateToLogin() {
|
||||
console.log('🚀 Navigating to /admin/login...');
|
||||
window.location.href = '/admin/login';
|
||||
}
|
||||
|
||||
function navigateToDashboard() {
|
||||
console.log('🚀 Navigating to /admin/dashboard...');
|
||||
window.location.href = '/admin/dashboard';
|
||||
}
|
||||
|
||||
function checkAuthStatus() {
|
||||
updateStatus();
|
||||
checkAuthStatus() {
|
||||
this.updateStatus();
|
||||
alert('Check console and status panel for auth details.');
|
||||
}
|
||||
},
|
||||
|
||||
function setExpiredToken() {
|
||||
const expiredToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyMzkwMjJ9.invalidexpiredtoken';
|
||||
console.log('⚠️ Setting expired/invalid token...');
|
||||
setExpiredToken() {
|
||||
const expiredToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwiZXhwIjoxNTE2MjM5MDIyfQ.invalid';
|
||||
localStorage.setItem('admin_token', expiredToken);
|
||||
localStorage.setItem('admin_user', JSON.stringify({
|
||||
id: 1,
|
||||
username: 'test_expired',
|
||||
role: 'admin'
|
||||
}));
|
||||
console.log('✅ Expired token set');
|
||||
alert('⚠️ Expired token set!\n\nNow try navigating to dashboard.');
|
||||
updateStatus();
|
||||
}
|
||||
alert('Expired token set! Now try navigating to dashboard.');
|
||||
this.updateStatus();
|
||||
},
|
||||
|
||||
function setValidToken() {
|
||||
// This is a mock token - won't actually work with backend
|
||||
setMockToken() {
|
||||
const mockToken = 'mock_valid_token_' + Date.now();
|
||||
console.log('✅ Setting mock valid token...');
|
||||
localStorage.setItem('admin_token', mockToken);
|
||||
localStorage.setItem('admin_user', JSON.stringify({
|
||||
id: 1,
|
||||
username: 'test_user',
|
||||
role: 'admin'
|
||||
}));
|
||||
console.log('✅ Mock token set (will not work with real backend)');
|
||||
alert('✅ Mock token set!\n\nNote: This is a fake token and won\'t work with the real backend.');
|
||||
updateStatus();
|
||||
}
|
||||
alert('Mock token set! Note: This won\'t work with real backend.');
|
||||
this.updateStatus();
|
||||
},
|
||||
|
||||
// Log level control
|
||||
function setLogLevel(level) {
|
||||
console.log(`📊 Setting log level to ${level}...`);
|
||||
|
||||
// Note: This only works if login.js and api-client.js are loaded
|
||||
// In production, you'd need to reload the page or use a more sophisticated approach
|
||||
if (typeof LOG_LEVEL !== 'undefined') {
|
||||
setLogLevel(level) {
|
||||
if (typeof window.LOG_LEVEL !== 'undefined') {
|
||||
window.LOG_LEVEL = level;
|
||||
document.getElementById('currentLoginLevel').textContent = level;
|
||||
console.log('✅ LOGIN log level set to', level);
|
||||
} else {
|
||||
console.warn('⚠️ LOG_LEVEL not found (login.js not loaded)');
|
||||
this.currentLoginLevel = level;
|
||||
}
|
||||
|
||||
if (typeof API_LOG_LEVEL !== 'undefined') {
|
||||
if (typeof window.API_LOG_LEVEL !== 'undefined') {
|
||||
window.API_LOG_LEVEL = level;
|
||||
document.getElementById('currentApiLevel').textContent = level;
|
||||
console.log('✅ API log level set to', level);
|
||||
} else {
|
||||
console.warn('⚠️ API_LOG_LEVEL not found (api-client.js not loaded)');
|
||||
this.currentApiLevel = level;
|
||||
}
|
||||
|
||||
alert(`Log level set to ${level}\n\n0 = None\n1 = Errors\n2 = Warnings\n3 = Info\n4 = Debug\n\nNote: Changes apply to current page. Reload to apply to all scripts.`);
|
||||
alert(`Log level set to ${level}. Reload to apply to all scripts.`);
|
||||
}
|
||||
|
||||
// Initialize status on load
|
||||
updateStatus();
|
||||
|
||||
// Auto-refresh status every 2 seconds
|
||||
setInterval(updateStatus, 2000);
|
||||
|
||||
console.log('🧪 Auth Flow Testing Script Loaded');
|
||||
console.log('📊 Use the buttons above to run tests');
|
||||
console.log('🔍 Watch browser console and Network tab for details');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
};
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user