mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-04-27 15:17:38 +00:00
add support for password on Hummingbird
This commit is contained in:
parent
0f5f264f0b
commit
fe49e9e19f
1 changed files with 67 additions and 0 deletions
67
VibeTunnel/Core/Services/BasicAuthMiddleware.swift
Normal file
67
VibeTunnel/Core/Services/BasicAuthMiddleware.swift
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
import Foundation
|
||||||
|
import Hummingbird
|
||||||
|
import HummingbirdCore
|
||||||
|
import HTTPTypes
|
||||||
|
import NIOCore
|
||||||
|
|
||||||
|
/// Middleware that implements HTTP Basic Authentication
|
||||||
|
struct BasicAuthMiddleware<Context: RequestContext>: RouterMiddleware {
|
||||||
|
let password: String
|
||||||
|
let realm: String
|
||||||
|
|
||||||
|
init(password: String, realm: String = "VibeTunnel Dashboard") {
|
||||||
|
self.password = password
|
||||||
|
self.realm = realm
|
||||||
|
}
|
||||||
|
|
||||||
|
func handle(_ request: Request, context: Context, next: (Request, Context) async throws -> Response) async throws -> Response {
|
||||||
|
// For BasicRequestContext, we can't easily check if it's localhost
|
||||||
|
// So we'll authenticate all requests when password is set
|
||||||
|
// The server bind address (127.0.0.1 vs 0.0.0.0) already controls network access
|
||||||
|
|
||||||
|
// Extract authorization header
|
||||||
|
guard let authHeader = request.headers[.authorization],
|
||||||
|
authHeader.hasPrefix("Basic ") else {
|
||||||
|
return unauthorizedResponse()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode base64 credentials
|
||||||
|
let base64Credentials = String(authHeader.dropFirst(6))
|
||||||
|
guard let credentialsData = Data(base64Encoded: base64Credentials),
|
||||||
|
let credentials = String(data: credentialsData, encoding: .utf8) else {
|
||||||
|
return unauthorizedResponse()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split username:password
|
||||||
|
let parts = credentials.split(separator: ":", maxSplits: 1)
|
||||||
|
guard parts.count == 2 else {
|
||||||
|
return unauthorizedResponse()
|
||||||
|
}
|
||||||
|
|
||||||
|
// We ignore the username and only check password
|
||||||
|
let providedPassword = String(parts[1])
|
||||||
|
|
||||||
|
// Verify password
|
||||||
|
guard providedPassword == password else {
|
||||||
|
return unauthorizedResponse()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Password correct, continue with request
|
||||||
|
return try await next(request, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func unauthorizedResponse() -> Response {
|
||||||
|
var headers = HTTPFields()
|
||||||
|
headers[.wwwAuthenticate] = "Basic realm=\"\(realm)\""
|
||||||
|
|
||||||
|
let message = "Authentication required"
|
||||||
|
var buffer = ByteBuffer()
|
||||||
|
buffer.writeString(message)
|
||||||
|
|
||||||
|
return Response(
|
||||||
|
status: .unauthorized,
|
||||||
|
headers: headers,
|
||||||
|
body: ResponseBody(byteBuffer: buffer)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue