## 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>
325 lines
16 KiB
HTML
325 lines
16 KiB
HTML
{# app/templates/admin/test-auth-flow.html #}
|
|
{% extends 'admin/base.html' %}
|
|
|
|
{% block title %}Auth Flow Testing{% endblock %}
|
|
|
|
{% 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>
|
|
</div>
|
|
|
|
{# 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;
|
|
|
|
try {
|
|
user = userStr ? JSON.parse(userStr) : null;
|
|
} catch (e) {
|
|
console.error('Failed to parse user data:', e);
|
|
}
|
|
|
|
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';
|
|
},
|
|
|
|
clearAllData() {
|
|
console.log('Clearing all localStorage data...');
|
|
localStorage.clear();
|
|
console.log('All data cleared');
|
|
alert('All localStorage data cleared!');
|
|
this.updateStatus();
|
|
},
|
|
|
|
navigateTo(path) {
|
|
console.log(`Navigating to ${path}...`);
|
|
window.location.href = path;
|
|
},
|
|
|
|
checkAuthStatus() {
|
|
this.updateStatus();
|
|
alert('Check console and status panel for auth details.');
|
|
},
|
|
|
|
setExpiredToken() {
|
|
const expiredToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxIiwiZXhwIjoxNTE2MjM5MDIyfQ.invalid';
|
|
localStorage.setItem('admin_token', expiredToken);
|
|
localStorage.setItem('admin_user', JSON.stringify({
|
|
id: 1,
|
|
username: 'test_expired',
|
|
role: 'admin'
|
|
}));
|
|
alert('Expired token set! Now try navigating to dashboard.');
|
|
this.updateStatus();
|
|
},
|
|
|
|
setMockToken() {
|
|
const mockToken = 'mock_valid_token_' + Date.now();
|
|
localStorage.setItem('admin_token', mockToken);
|
|
localStorage.setItem('admin_user', JSON.stringify({
|
|
id: 1,
|
|
username: 'test_user',
|
|
role: 'admin'
|
|
}));
|
|
alert('Mock token set! Note: This won\'t work with real backend.');
|
|
this.updateStatus();
|
|
},
|
|
|
|
setLogLevel(level) {
|
|
if (typeof window.LOG_LEVEL !== 'undefined') {
|
|
window.LOG_LEVEL = level;
|
|
this.currentLoginLevel = level;
|
|
}
|
|
if (typeof window.API_LOG_LEVEL !== 'undefined') {
|
|
window.API_LOG_LEVEL = level;
|
|
this.currentApiLevel = level;
|
|
}
|
|
alert(`Log level set to ${level}. Reload to apply to all scripts.`);
|
|
}
|
|
};
|
|
}
|
|
</script>
|
|
{% endblock %}
|