fix: load configuration from config.json and fix authentication

- Load anime_directory and master_password_hash from config.json on startup
- Sync configuration from config.json to settings object in fastapi_app.py
- Update dependencies.py to load config from JSON if not in environment
- Fix app.js to use makeAuthenticatedRequest() for all authenticated API calls
- Fix API endpoint paths from /api/v1/anime to /api/anime
- Update auth_service.py to load master_password_hash from config.json
- Update auth.py setup endpoint to save master_password_hash to config
- Fix rate limiting code to satisfy type checker
- Update config.json with test master password hash

Fixes:
- 401 Unauthorized errors on /api/anime endpoint
- 503 Service Unavailable errors on /api/anime/process/locks
- Configuration not being loaded from config.json file
- Authentication flow now works end-to-end with JWT tokens
This commit is contained in:
2025-10-24 20:55:10 +02:00
parent 4e08d81bb0
commit a3651e0e47
6 changed files with 148 additions and 36 deletions

View File

@@ -42,24 +42,40 @@ class AniWorldApp {
try {
// First check if we have a token
const token = localStorage.getItem('access_token');
console.log('checkAuthentication: token exists =', !!token);
// Build request with token if available
const headers = {};
if (token) {
headers['Authorization'] = `Bearer ${token}`;
if (!token) {
console.log('checkAuthentication: No token found, redirecting to /login');
window.location.href = '/login';
return;
}
// Build request with token
const headers = {
'Authorization': `Bearer ${token}`
};
const response = await fetch('/api/auth/status', { headers });
console.log('checkAuthentication: response status =', response.status);
if (!response.ok) {
console.log('checkAuthentication: Response not OK, status =', response.status);
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
console.log('checkAuthentication: data =', data);
if (!data.configured) {
// No master password set, redirect to setup
console.log('checkAuthentication: Not configured, redirecting to /setup');
window.location.href = '/setup';
return;
}
if (!data.authenticated) {
// Not authenticated, redirect to login
console.log('checkAuthentication: Not authenticated, redirecting to /login');
localStorage.removeItem('access_token');
localStorage.removeItem('token_expires_at');
window.location.href = '/login';
@@ -67,6 +83,7 @@ class AniWorldApp {
}
// User is authenticated, show logout button
console.log('checkAuthentication: Authenticated successfully');
const logoutBtn = document.getElementById('logout-btn');
if (logoutBtn) {
logoutBtn.style.display = 'block';
@@ -539,22 +556,35 @@ class AniWorldApp {
try {
this.showLoading();
const response = await fetch('/api/v1/anime');
const response = await this.makeAuthenticatedRequest('/api/anime');
if (response.status === 401) {
window.location.href = '/login';
if (!response) {
// makeAuthenticatedRequest returns null and handles redirect on auth failure
return;
}
const data = await response.json();
if (data.status === 'success') {
// Check if response has the expected format
if (Array.isArray(data)) {
// API returns array of AnimeSummary objects directly
this.seriesData = data.map(anime => ({
id: anime.id,
name: anime.title,
title: anime.title,
missing_episodes: anime.missing_episodes || 0,
episodeDict: {} // Will be populated when needed
}));
} else if (data.status === 'success') {
// Legacy format support
this.seriesData = data.series;
this.applyFiltersAndSort();
this.renderSeries();
} else {
this.showToast(`Error loading series: ${data.message}`, 'error');
this.showToast(`Error loading series: ${data.message || 'Unknown error'}`, 'error');
return;
}
this.applyFiltersAndSort();
this.renderSeries();
} catch (error) {
console.error('Error loading series:', error);
this.showToast('Failed to load series', 'error');
@@ -783,7 +813,7 @@ class AniWorldApp {
try {
this.showLoading();
const response = await this.makeAuthenticatedRequest('/api/v1/anime/search', {
const response = await this.makeAuthenticatedRequest('/api/anime/search', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@@ -836,7 +866,7 @@ class AniWorldApp {
async addSeries(link, name) {
try {
const response = await this.makeAuthenticatedRequest('/api/v1/anime/add', {
const response = await this.makeAuthenticatedRequest('/api/anime/add', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@@ -870,7 +900,7 @@ class AniWorldApp {
try {
const folders = Array.from(this.selectedSeries);
const response = await this.makeAuthenticatedRequest('/api/v1/anime/download', {
const response = await this.makeAuthenticatedRequest('/api/anime/download', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@@ -894,7 +924,7 @@ class AniWorldApp {
async rescanSeries() {
try {
const response = await this.makeAuthenticatedRequest('/api/v1/anime/rescan', {
const response = await this.makeAuthenticatedRequest('/api/anime/rescan', {
method: 'POST'
});
@@ -1030,7 +1060,7 @@ class AniWorldApp {
async checkProcessLocks() {
try {
const response = await this.makeAuthenticatedRequest('/api/v1/anime/process/locks');
const response = await this.makeAuthenticatedRequest('/api/anime/process/locks');
if (!response) {
// If no response, set status as idle
this.updateProcessStatus('rescan', false);
@@ -1101,7 +1131,7 @@ class AniWorldApp {
try {
// Load current status
const response = await this.makeAuthenticatedRequest('/api/v1/anime/status');
const response = await this.makeAuthenticatedRequest('/api/anime/status');
if (!response) return;
const data = await response.json();
@@ -1600,7 +1630,7 @@ class AniWorldApp {
async refreshStatus() {
try {
const response = await this.makeAuthenticatedRequest('/api/v1/anime/status');
const response = await this.makeAuthenticatedRequest('/api/anime/status');
if (!response) return;
const data = await response.json();