From df2a5359975b6bf6f2503125d660a877e9b815f0 Mon Sep 17 00:00:00 2001 From: Simon Whitty Date: Tue, 17 Sep 2024 14:20:55 +1000 Subject: [PATCH] BasicAuthRoutedHTTPHandler --- FlyingFox/Sources/HTTPHeader.swift | 1 + .../Handlers/BasicAuthRoutedHTTPHandler.swift | 100 ++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 FlyingFox/Sources/Handlers/BasicAuthRoutedHTTPHandler.swift diff --git a/FlyingFox/Sources/HTTPHeader.swift b/FlyingFox/Sources/HTTPHeader.swift index 1df3e23..201751f 100644 --- a/FlyingFox/Sources/HTTPHeader.swift +++ b/FlyingFox/Sources/HTTPHeader.swift @@ -62,6 +62,7 @@ public extension HTTPHeader { static let webSocketVersion = HTTPHeader("Sec-WebSocket-Version") static let transferEncoding = HTTPHeader("Transfer-Encoding") static let upgrade = HTTPHeader("Upgrade") + static let wwwAuthenticate = HTTPHeader("WWW-Authenticate") static let xForwardedFor = HTTPHeader("X-Forwarded-For") } diff --git a/FlyingFox/Sources/Handlers/BasicAuthRoutedHTTPHandler.swift b/FlyingFox/Sources/Handlers/BasicAuthRoutedHTTPHandler.swift new file mode 100644 index 0000000..6c0f86b --- /dev/null +++ b/FlyingFox/Sources/Handlers/BasicAuthRoutedHTTPHandler.swift @@ -0,0 +1,100 @@ +// +// BasicAuthRoutedHTTPHandler.swift +// FlyingFox +// +// Created by Simon Whitty on 27/09/2024. +// Copyright © 2024 Simon Whitty. All rights reserved. +// +// Distributed under the permissive MIT license +// Get the latest version from here: +// +// https://github.com/swhitty/FlyingFox +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +import Foundation + +public struct BasicAuthRoutedHTTPHandler: HTTPHandler, Sendable { + + private var handlers = RoutedHTTPHandler() + private var realm: String + private var username: String + private var password: String + + public init(realm: String, username: String, password: String) { + self.realm = realm + self.username = username + self.password = password + } + + public mutating func appendRoute(_ route: HTTPRoute, to handler: some HTTPHandler) { + handlers.appendRoute(route, to: handler) + } + + public mutating func appendRoute(_ route: HTTPRoute, + handler: @Sendable @escaping (HTTPRequest) async throws -> HTTPResponse) { + handlers.appendRoute(route, handler: handler) + } + + public mutating func appendRoute( + _ route: HTTPRoute, + handler: @Sendable @escaping (HTTPRequest, repeat each P) async throws -> HTTPResponse + ) { + handlers.appendRoute(route, handler: handler) + } + + public mutating func appendRoute( + _ route: HTTPRoute, + handler: @Sendable @escaping (repeat each P) async throws -> HTTPResponse + ) { + handlers.appendRoute(route, handler: handler) + } + + public func handleRequest(_ request: HTTPRequest) async throws -> HTTPResponse { + guard let credentials = request.basicAuthorization else { + return HTTPResponse(statusCode: .unauthorized, headers: [.wwwAuthenticate: "Basic realm=\"\(realm)\""]) + } + guard credentials.username == username, credentials.password == password else { + return HTTPResponse(statusCode: .forbidden) + } + return try await handlers.handleRequest(request) + } +} + +extension HTTPRequest { + + var basicAuthorization: (username: String, password: String)? { + let auth = headers[.authorization] ?? "" + let comps = auth.components(separatedBy: " ") + guard comps.count == 2, comps[0] == "Basic" else { + return nil + } + guard let data = Data(base64Encoded: comps[1]), + let text = String(data: data, encoding: .utf8) else { + return nil + } + let parts = text.components(separatedBy: ":") + guard parts.count == 2 else { + return nil + } + return (username: parts[0], password: parts[1]) + } + +}