// // AuthenticationMiddleware.swift // // Copyright 2026 Brendan Szymanski // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // // SPDX-License-Identifier: GPL-3.0-or-later // import Foundation import HTTPTypes import OpenAPIRuntime /// Stamps Jellyfin's `Authorization` header onto every outgoing request. /// /// Jellyfin does not use a bare bearer token. It expects a `MediaBrowser` scheme carrying the /// client identity alongside the token: /// /// ``` /// Authorization: MediaBrowser Client="Luminate", Device="workstation", /// DeviceId="...", Version="0.1.0", Token="..." /// ``` /// /// The server keys a session by `DeviceId`, so that value must stay stable across launches. Before /// sign-in the token is sent as an empty string, which is what the pre-auth endpoints expect. /// /// This middleware is the only place that header is built; no call site assembles it by hand. package struct AuthenticationMiddleware: ClientMiddleware { /// The application name reported to the server. package let clientName: String /// The human-readable device name shown in the server's active-devices list. package let deviceName: String /// The stable per-machine device identifier the server keys its session by. package let deviceID: String /// The application version reported to the server. package let version: String /// The access token, or `nil` before sign-in. package let token: String? /// Creates a middleware for one client identity. /// /// - Parameters: /// - clientName: The application name reported to the server. /// - deviceName: The human-readable device name. /// - deviceID: The stable per-machine device identifier. /// - version: The application version. /// - token: The access token, or `nil` before sign-in. package init(clientName: String, deviceName: String, deviceID: String, version: String, token: String?) { self.clientName = clientName self.deviceName = deviceName self.deviceID = deviceID self.version = version self.token = token } /// Adds the `Authorization` header and forwards the request down the chain. /// /// - Parameters: /// - request: The request as assembled by the generated client. /// - body: The request body, if any. /// - baseURL: The server URL the request is bound for. /// - operationID: The Jellyfin operation identifier, unused here. /// - next: The next step in the middleware chain. /// - Returns: Whatever `next` returns, unmodified. /// - Throws: Whatever `next` throws. package func intercept( _ request: HTTPRequest, body: HTTPBody?, baseURL: URL, operationID: String, next: @Sendable (HTTPRequest, HTTPBody?, URL) async throws -> (HTTPResponse, HTTPBody?) ) async throws -> (HTTPResponse, HTTPBody?) { var request = request request.headerFields[.authorization] = authorizationValue return try await next(request, body, baseURL) } /// The `MediaBrowser` credential string, with an empty token when unauthenticated. private var authorizationValue: String { """ MediaBrowser Client="\(clientName)", Device="\(deviceName)", DeviceId="\(deviceID)", \ Version="\(version)", Token="\(token ?? "")" """ } }