1
0

fix: classify invalid subsonic URL as unreachable, type settings handlers, guard token mask and body types

This commit is contained in:
2026-08-29 16:01:07 +02:00
parent e94ffb535b
commit a54f56834d
2 changed files with 16 additions and 6 deletions

View File

@@ -1,6 +1,7 @@
import { FastifyInstance } from 'fastify'
import { SubsonicClient } from '../subsonic.js'
import { requireAuth } from './authRoutes.js'
import type { UserRow } from '../auth.js'
import type { DB } from '../db.js'
export interface SettingsRow {
@@ -23,7 +24,11 @@ export function getSettings(db: DB, userId: number): SettingsRow {
export function settingsView(s: SettingsRow) {
return {
hasDiscogsToken: !!s.discogs_token,
discogsTokenMasked: s.discogs_token ? `****${s.discogs_token.slice(-5)}` : null,
discogsTokenMasked: s.discogs_token
? s.discogs_token.length > 5
? `****${s.discogs_token.slice(-5)}`
: '****'
: null,
subsonicUrl: s.subsonic_url,
subsonicUsername: s.subsonic_username,
hasSubsonicPassword: !!s.subsonic_password,
@@ -36,15 +41,20 @@ export function subsonicConfigComplete(s: SettingsRow): boolean {
export async function registerSettingsRoutes(app: FastifyInstance): Promise<void> {
app.get('/api/settings', { preHandler: [requireAuth] }, async (request) => {
const s = getSettings(request.server.db, (request.user as any).id)
const s = getSettings(request.server.db, (request.user as UserRow).id)
return settingsView(s)
})
app.put('/api/settings', { preHandler: [requireAuth] }, async (request, reply) => {
const db = request.server.db
const userId = (request.user as any).id
const userId = (request.user as UserRow).id
const s = getSettings(db, userId)
const body = (request.body ?? {}) as Record<string, string | undefined>
for (const value of Object.values(body)) {
if (value !== undefined && typeof value !== 'string') {
return reply.code(400).send({ error: 'invalid_input', detail: 'settings fields must be strings' })
}
}
const next = {
discogs_token: body.discogsToken !== undefined ? body.discogsToken || null : s.discogs_token,

View File

@@ -45,11 +45,11 @@ export class SubsonicClient {
}
private async request(endpoint: string, params: Record<string, string> = {}): Promise<any> {
let res: Response
try {
const url = new URL(`${this.base}/rest/${endpoint}`)
const search = { ...this.authParams(), ...params }
for (const [k, v] of Object.entries(search)) url.searchParams.set(k, v)
let res: Response
try {
res = await this.fetchImpl(url.toString())
} catch {
throw new SubsonicError('unreachable', `could not reach ${this.base}`)