mirror of
https://github.com/kikootwo/ReadMeABook.git
synced 2026-06-03 12:50:09 +00:00
Improve user auth handling and download monitoring
Adds detection of local users for authentication validation and login, prevents role changes for OIDC users, and clarifies user management UI. Enhances active downloads API to include speed and ETA from qBittorrent, and improves file path handling in download monitoring. Also updates torrent tagging and user info returned by APIs.
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { requireAuth, requireAdmin, AuthenticatedRequest } from '@/lib/middleware/auth';
|
||||
import { prisma } from '@/lib/db';
|
||||
import { getQBittorrentService } from '@/lib/integrations/qbittorrent.service';
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
return requireAuth(request, async (req: AuthenticatedRequest) => {
|
||||
@@ -17,7 +18,11 @@ export async function GET(request: NextRequest) {
|
||||
status: 'downloading',
|
||||
deletedAt: null,
|
||||
},
|
||||
include: {
|
||||
select: {
|
||||
id: true,
|
||||
status: true,
|
||||
progress: true,
|
||||
updatedAt: true,
|
||||
audiobook: {
|
||||
select: {
|
||||
id: true,
|
||||
@@ -42,6 +47,7 @@ export async function GET(request: NextRequest) {
|
||||
select: {
|
||||
downloadStatus: true,
|
||||
torrentName: true,
|
||||
torrentHash: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -51,20 +57,67 @@ export async function GET(request: NextRequest) {
|
||||
take: 20,
|
||||
});
|
||||
|
||||
// Format response
|
||||
const formatted = activeDownloads.map((download) => ({
|
||||
requestId: download.id,
|
||||
title: download.audiobook.title,
|
||||
author: download.audiobook.author,
|
||||
status: download.status,
|
||||
progress: download.progress,
|
||||
torrentName: download.downloadHistory[0]?.torrentName || null,
|
||||
downloadStatus: download.downloadHistory[0]?.downloadStatus || null,
|
||||
user: download.user.plexUsername,
|
||||
startedAt: download.updatedAt,
|
||||
}));
|
||||
// Get qBittorrent service
|
||||
let qbService;
|
||||
try {
|
||||
qbService = await getQBittorrentService();
|
||||
} catch (error) {
|
||||
console.error('[Admin] Failed to initialize qBittorrent service:', error);
|
||||
// Return downloads without speed/eta if qBittorrent is unavailable
|
||||
const formatted = activeDownloads.map((download) => ({
|
||||
requestId: download.id,
|
||||
title: download.audiobook.title,
|
||||
author: download.audiobook.author,
|
||||
status: download.status,
|
||||
progress: download.progress,
|
||||
speed: 0,
|
||||
eta: null,
|
||||
torrentName: download.downloadHistory[0]?.torrentName || null,
|
||||
downloadStatus: download.downloadHistory[0]?.downloadStatus || null,
|
||||
user: download.user.plexUsername,
|
||||
startedAt: download.updatedAt,
|
||||
}));
|
||||
return NextResponse.json({ downloads: formatted });
|
||||
}
|
||||
|
||||
return NextResponse.json({ downloads: formatted });
|
||||
// Format response with speed and ETA from qBittorrent
|
||||
const formatted = await Promise.all(
|
||||
activeDownloads.map(async (download) => {
|
||||
let speed = 0;
|
||||
let eta: number | null = null;
|
||||
|
||||
// Get torrent hash from download history
|
||||
const torrentHash = download.downloadHistory[0]?.torrentHash;
|
||||
|
||||
// Fetch torrent info from qBittorrent if we have a hash
|
||||
if (torrentHash) {
|
||||
try {
|
||||
const torrentInfo = await qbService.getTorrent(torrentHash);
|
||||
speed = torrentInfo.dlspeed;
|
||||
eta = torrentInfo.eta > 0 ? torrentInfo.eta : null;
|
||||
} catch (error) {
|
||||
// Torrent not found or other error - use defaults
|
||||
console.error(`[Admin] Failed to get torrent info for ${torrentHash}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
requestId: download.id,
|
||||
title: download.audiobook.title,
|
||||
author: download.audiobook.author,
|
||||
status: download.status,
|
||||
progress: download.progress,
|
||||
speed,
|
||||
eta,
|
||||
torrentName: download.downloadHistory[0]?.torrentName || null,
|
||||
downloadStatus: download.downloadHistory[0]?.downloadStatus || null,
|
||||
user: download.user.plexUsername,
|
||||
startedAt: download.updatedAt,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
return NextResponse.json({ downloads: formatted });
|
||||
} catch (error) {
|
||||
console.error('[Admin] Failed to fetch active downloads:', error);
|
||||
return NextResponse.json(
|
||||
|
||||
@@ -15,6 +15,11 @@ export async function GET(request: NextRequest) {
|
||||
const configs = await prisma.configuration.findMany();
|
||||
const configMap = new Map(configs.map((c) => [c.key, c.value]));
|
||||
|
||||
// Check if any local users exist (for validation)
|
||||
const hasLocalUsers = (await prisma.user.count({
|
||||
where: { authProvider: 'local' }
|
||||
})) > 0;
|
||||
|
||||
// Mask sensitive values
|
||||
const maskValue = (key: string, value: string | null | undefined) => {
|
||||
const sensitiveKeys = ['token', 'api_key', 'password', 'secret'];
|
||||
@@ -27,6 +32,7 @@ export async function GET(request: NextRequest) {
|
||||
// Build response object
|
||||
const settings = {
|
||||
backendMode: configMap.get('system.backend_mode') || 'plex',
|
||||
hasLocalUsers,
|
||||
plex: {
|
||||
url: configMap.get('plex_url') || '',
|
||||
token: maskValue('token', configMap.get('plex_token')),
|
||||
|
||||
@@ -34,11 +34,12 @@ export async function PUT(
|
||||
);
|
||||
}
|
||||
|
||||
// Check if user is the setup admin
|
||||
// Check if user is the setup admin or OIDC user
|
||||
const targetUser = await prisma.user.findUnique({
|
||||
where: { id },
|
||||
select: {
|
||||
isSetupAdmin: true,
|
||||
authProvider: true,
|
||||
plexUsername: true,
|
||||
},
|
||||
});
|
||||
@@ -58,6 +59,14 @@ export async function PUT(
|
||||
);
|
||||
}
|
||||
|
||||
// Prevent changing OIDC user roles (managed by identity provider)
|
||||
if (targetUser.authProvider === 'oidc') {
|
||||
return NextResponse.json(
|
||||
{ error: 'Cannot change OIDC user roles. Use admin role mapping in OIDC settings instead.' },
|
||||
{ status: 403 }
|
||||
);
|
||||
}
|
||||
|
||||
// Update user role
|
||||
const updatedUser = await prisma.user.update({
|
||||
where: { id },
|
||||
|
||||
@@ -19,6 +19,7 @@ export async function GET(request: NextRequest) {
|
||||
plexEmail: true,
|
||||
role: true,
|
||||
isSetupAdmin: true,
|
||||
authProvider: true,
|
||||
avatarUrl: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import { NextResponse } from 'next/server';
|
||||
import { ConfigurationService } from '@/lib/services/config.service';
|
||||
import { prisma } from '@/lib/db';
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
@@ -17,22 +18,36 @@ export async function GET() {
|
||||
const registrationEnabled = (await configService.get('auth.registration_enabled')) === 'true';
|
||||
const oidcProviderName = await configService.get('oidc.provider_name') || 'SSO';
|
||||
|
||||
// Check if any local users exist in database (for login form visibility)
|
||||
const hasLocalUsers = (await prisma.user.count({
|
||||
where: { authProvider: 'local' }
|
||||
})) > 0;
|
||||
|
||||
const providers: string[] = [];
|
||||
if (oidcEnabled) providers.push('oidc');
|
||||
if (registrationEnabled) providers.push('local');
|
||||
if (hasLocalUsers) providers.push('local');
|
||||
|
||||
return NextResponse.json({
|
||||
backendMode: 'audiobookshelf',
|
||||
providers,
|
||||
registrationEnabled,
|
||||
hasLocalUsers,
|
||||
oidcProviderName: oidcEnabled ? oidcProviderName : null,
|
||||
});
|
||||
} else {
|
||||
// Plex mode
|
||||
// Plex mode - check if local admin exists (setup admin)
|
||||
const hasLocalUsers = (await prisma.user.count({
|
||||
where: {
|
||||
plexId: { startsWith: 'local-' },
|
||||
isSetupAdmin: true
|
||||
}
|
||||
})) > 0;
|
||||
|
||||
return NextResponse.json({
|
||||
backendMode: 'plex',
|
||||
providers: ['plex'],
|
||||
registrationEnabled: false,
|
||||
hasLocalUsers,
|
||||
oidcProviderName: null,
|
||||
});
|
||||
}
|
||||
@@ -43,6 +58,7 @@ export async function GET() {
|
||||
backendMode: 'plex',
|
||||
providers: ['plex'],
|
||||
registrationEnabled: false,
|
||||
hasLocalUsers: false,
|
||||
oidcProviderName: null,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user