From 9aeb3604e6498c388df1d30dd0b613ba84160fc0 Mon Sep 17 00:00:00 2001 From: Fallenbagel <98979876+Fallenbagel@users.noreply.github.com> Date: Wed, 12 Jun 2024 18:50:00 +0500 Subject: [PATCH 1/3] fix(auth): validation of ipv6/ipv4 (#812) validation for ipv6 was sort of broken where for example `::1` was being sent as `1`, therefore, logins were broken. This PR fixes it by using nodejs `net.isIPv4()` & `net.isIPv6` for ipv4 and ipv6 validation. possibly related to and fixes #795 --- server/routes/auth.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/server/routes/auth.ts b/server/routes/auth.ts index 82c34b153..52c63ff29 100644 --- a/server/routes/auth.ts +++ b/server/routes/auth.ts @@ -14,6 +14,7 @@ import { ApiError } from '@server/types/error'; import * as EmailValidator from 'email-validator'; import { Router } from 'express'; import gravatarUrl from 'gravatar-url'; +import net from 'net'; const authRoutes = Router(); @@ -271,11 +272,21 @@ authRoutes.post('/jellyfin', async (req, res, next) => { ? jellyfinHost.slice(0, -1) : jellyfinHost; - const ip = req.ip ? req.ip.split(':').reverse()[0] : undefined; + const ip = req.ip; + let clientIp; + + if (ip) { + if (net.isIPv4(ip)) { + clientIp = ip; + } else if (net.isIPv6(ip)) { + clientIp = ip.startsWith('::ffff:') ? ip.substring(7) : ip; + } + } + const account = await jellyfinserver.login( body.username, body.password, - ip + clientIp ); // Next let's see if the user already exists From b5a069901a9545772deaa9c491f2075261da0189 Mon Sep 17 00:00:00 2001 From: Fallenbagel <98979876+Fallenbagel@users.noreply.github.com> Date: Thu, 13 Jun 2024 04:53:12 +0500 Subject: [PATCH 2/3] fix: bypass cache-able lookups when resolving localhost (#813) * fix: bypass cache-able lookups when resolving localhost * fix: bypass cacheable-lookup when resolving localhost --------- Co-authored-by: Gauthier --- server/index.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/server/index.ts b/server/index.ts index b62080778..a9a746562 100644 --- a/server/index.ts +++ b/server/index.ts @@ -27,6 +27,7 @@ import type CacheableLookupType from 'cacheable-lookup'; import { TypeormStore } from 'connect-typeorm/out'; import cookieParser from 'cookie-parser'; import csurf from 'csurf'; +import { lookup } from 'dns'; import type { NextFunction, Request, Response } from 'express'; import express from 'express'; import * as OpenApiValidator from 'express-openapi-validator'; @@ -54,6 +55,19 @@ app const CacheableLookup = (await _importDynamic('cacheable-lookup')) .default as typeof CacheableLookupType; const cacheable = new CacheableLookup(); + + const originalLookup = cacheable.lookup; + + // if hostname is localhost use dns.lookup instead of cacheable-lookup + cacheable.lookup = (...args: any) => { + const [hostname] = args; + if (hostname === 'localhost') { + lookup(...(args as Parameters)); + } else { + originalLookup(...(args as Parameters)); + } + }; + cacheable.install(http.globalAgent); cacheable.install(https.globalAgent); From a9741fa36d06710aa00d28db3dd2c29f2b0973d3 Mon Sep 17 00:00:00 2001 From: Fallenbagel <98979876+Fallenbagel@users.noreply.github.com> Date: Thu, 13 Jun 2024 14:16:07 +0500 Subject: [PATCH 3/3] fix(auth): improve login resilience with headerless fallback authentication (#814) adds fallback to authenticate without headers to ensure and improve resilience across different browsers and client configurations. --- server/api/jellyfin.ts | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/server/api/jellyfin.ts b/server/api/jellyfin.ts index f23e9aceb..81b505f11 100644 --- a/server/api/jellyfin.ts +++ b/server/api/jellyfin.ts @@ -126,25 +126,31 @@ class JellyfinAPI extends ExternalAPI { Password?: string, ClientIP?: string ): Promise { - try { - const headers = ClientIP - ? { - 'X-Forwarded-For': ClientIP, - } - : {}; + const authenticate = async (useHeaders: boolean) => { + const headers = + useHeaders && ClientIP ? { 'X-Forwarded-For': ClientIP } : {}; - const authResponse = await this.post( + return this.post( '/Users/AuthenticateByName', { - Username: Username, + Username, Pw: Password, }, - { - headers: headers, - } + { headers } ); + }; - return authResponse; + try { + return await authenticate(true); + } catch (e) { + logger.debug(`Failed to authenticate with headers: ${e.message}`, { + label: 'Jellyfin API', + ip: ClientIP, + }); + } + + try { + return await authenticate(false); } catch (e) { const status = e.response?.status;