fix(serie): 403 pour les non admin

fix(settings): la clé api n'apparait que dans un menu pour les admin

feat(client): ajout de alldebrid dans le tableau de comparatif

fix(settings): bug ou les parametre se sauvegarder en rentrant sur lla page avancé
This commit is contained in:
MasutaYunikon committed 2026-05-22 20:31:15 +02:00
1 parent 5d06a23198
commit f3d535d577
4 files changed
+38 -68

No files matched your search

+28 -13
View File
@@ -72,20 +72,35 @@ app.post('/api/auth/regenerate-token', requireAuth, authRegenerateToken)
// ── API publique v1 (auth par token) ──────────────────────────
app.use('/api', publicApiRouter)
// ── Routes (admin uniquement) ──────────────────────────────────
app.use('/api', requireAdmin, usersRouter)
app.use('/api', requireAdmin, jellyfinRouter)
app.use('/api', requireAdmin, settingsRouter)
app.use('/api', requireAdmin, torrentClientsRouter)
app.use('/api', requireAdmin, downloadsRouter)
app.use('/api', requireAdmin, organizeRouter)
app.use('/api', requireAdmin, importRouter)
app.use('/api', requireAdmin, systemRouter)
app.use('/api', requireAdmin, nfoUpdatesRouter)
app.use('/api', requireAdmin, plexRouter)
app.use('/api', requireAdmin, rssSyncRouter)
// ── Garde admin : protège les préfixes réservés aux admins ────
// NOTE : app.use('/api', requireAdmin, router) s'applique à TOUTES
// les requêtes /api/* avant même que le router vérifie ses routes,
// ce qui bloquerait les utilisateurs standard. On utilise donc un
// middleware ciblé sur les chemins réellement admin-only.
const ADMIN_PREFIXES = [
'/users', '/jellyfin', '/settings', '/torrent-clients',
'/downloads', '/download', '/organize', '/import',
'/system', '/nfo-updates', '/plex', '/rss-sync',
]
app.use('/api', (req, res, next) => {
if (ADMIN_PREFIXES.some(p => req.path === p || req.path.startsWith(p + '/'))) {
return requireAdmin(req, res, next)
}
next()
})
// ── Routes accessibles à tous les utilisateurs connectés ───────
// ── Routers (admin + utilisateurs connectés) ───────────────────
app.use('/api', usersRouter)
app.use('/api', jellyfinRouter)
app.use('/api', settingsRouter)
app.use('/api', torrentClientsRouter)
app.use('/api', downloadsRouter)
app.use('/api', organizeRouter)
app.use('/api', importRouter)
app.use('/api', systemRouter)
app.use('/api', nfoUpdatesRouter)
app.use('/api', plexRouter)
app.use('/api', rssSyncRouter)
app.use('/api', requireAuth, seriesRouter)
app.use('/api', requireAuth, requestsRouter)
// Invites : mix public + admin (le router gère ses propres middlewares)
+3
View File
@@ -26,6 +26,7 @@ import SettingsToggle from '@/components/settings/SettingsToggle.vue'
const devMode = ref(false)
const saved = ref(false)
let saveTimer: ReturnType<typeof setTimeout> | null = null
let loaded = false
onMounted(async () => {
const res = await fetch('/api/settings', { credentials: 'include' })
@@ -33,9 +34,11 @@ onMounted(async () => {
const s = await res.json()
devMode.value = s.devMode ?? false
}
loaded = true
})
watch(devMode, async (val) => {
if (!loaded) return
await fetch('/api/settings', {
method : 'POST',
headers: { 'Content-Type': 'application/json' },
@@ -255,6 +255,10 @@ const clientMatrix = [
id: 'real-debrid', label: 'Real-Debrid', version: 'API v1',
auth: 'Clé API', fileSelect: true, fileProgress: true, torrentUpload: 'proxy', categories: false, deleteFiles: true,
},
{
id: 'alldebrid', label: 'AllDebrid', version: 'API v4',
auth: 'Clé API', fileSelect: true, fileProgress: true, torrentUpload: 'proxy', categories: false, deleteFiles: true,
},
]
const modal = ref({
+3 -55
View File
@@ -71,38 +71,6 @@
</div>
</section>
<!-- ── Token API utilisateur ──────────────────────────────── -->
<section class="flex flex-col gap-4">
<div>
<h2 class="text-base font-semibold text-primary">Token API personnel</h2>
<p class="text-sm text-muted mt-1">
Utilisez ce token pour vous authentifier sur l'API publique FanKarr
(ex : configuration manuelle du plugin Jellyfin).
</p>
</div>
<div class="settings-card flex flex-col gap-3">
<div class="flex items-center gap-2">
<code class="flex-1 text-xs bg-shell px-3 py-2 rounded-lg border border-border text-muted font-mono truncate">
{{ showToken ? myToken : '••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••' }}
</code>
<button @click="showToken = !showToken" class="btn-ghost text-xs shrink-0">
{{ showToken ? 'Masquer' : 'Afficher' }}
</button>
<button @click="copyToken" class="btn-ghost text-xs shrink-0">
{{ tokenCopied ? 'Copié !' : 'Copier' }}
</button>
</div>
<button @click="regenerateToken" class="btn-ghost text-xs text-red-400 hover:text-red-300 w-fit">
Regénérer le token
</button>
<p class="text-xs text-muted">
Endpoint auth plugin :
<code class="bg-shell px-1.5 py-0.5 rounded text-xs">POST {{ origin }}/api/v1/auth/jellyfin</code>
</p>
</div>
</section>
<!-- ── Référence API ──────────────────────────────────────── -->
<section class="flex flex-col gap-3">
<h2 class="text-base font-semibold text-primary">Référence API v1</h2>
@@ -147,10 +115,7 @@ const syncing = ref(false)
const syncResult = ref<{ created: number; skipped: number; users: string[] } | null>(null)
const syncError = ref<string | null>(null)
const myToken = ref('')
const showToken = ref(false)
const tokenCopied = ref(false)
const origin = window.location.origin
const origin = window.location.origin
const apiEndpoints = [
{ method: 'POST', path: '/api/v1/auth/jellyfin', desc: 'Échanger un token Jellyfin contre un token FanKarr' },
@@ -173,15 +138,7 @@ async function loadSettings() {
}
}
async function loadMyToken() {
const res = await fetch('/api/auth/me', { credentials: 'include' })
if (res.ok) {
const d = await res.json()
myToken.value = d.apiToken ?? ''
}
}
onMounted(() => { loadSettings(); loadMyToken() })
onMounted(() => { loadSettings() })
async function save() {
saving.value = true
@@ -227,14 +184,5 @@ async function copyToken() {
setTimeout(() => { tokenCopied.value = false }, 2000)
}
async function regenerateToken() {
if (!confirm('Regénérer le token ? L\'ancien token sera immédiatement invalidé.')) return
const res = await fetch('/api/auth/regenerate-token', {
method: 'POST', credentials: 'include',
})
if (res.ok) {
const d = await res.json()
myToken.value = d.apiToken
}
}
</script>