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:
1 parent
5d06a23198
commit
f3d535d577
4 files changed
+38
-68
No files matched your search
+28
-13
@@ -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)
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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>
|
||||
Reference in new issue
Block a user