Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove "HB" prefix #35

Merged
merged 3 commits into from Mar 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -2,7 +2,7 @@

Authentication framework and extensions for Hummingbird server framework.

Includes Authenticator middleware setup, bearer, basic authentication extraction from your Request headers, Bcrypt encryption for passwords.
Includes authentication and session middleware, bearer, basic authentication extraction from your Request headers, Bcrypt encryption for passwords.


## Documentation
Expand Down
12 changes: 6 additions & 6 deletions Sources/HummingbirdAuth/Authenticator/AuthRequestContext.swift
Expand Up @@ -18,19 +18,19 @@ import NIOCore

/// Protocol that all request contexts should conform to if they want to support
/// authentication middleware
public protocol HBAuthRequestContext: HBRequestContext {
public protocol AuthRequestContext: RequestContext {
/// Login cache
var auth: HBLoginCache { get set }
var auth: LoginCache { get set }
}

/// Implementation of a basic request context that supports everything the Hummingbird library needs
public struct HBBasicAuthRequestContext: HBAuthRequestContext {
public struct BasicAuthRequestContext: AuthRequestContext {
/// core context
public var coreContext: HBCoreRequestContext
public var coreContext: CoreRequestContext
/// Login cache
public var auth: HBLoginCache
public var auth: LoginCache

/// Initialize an `HBRequestContext`
/// Initialize an `RequestContext`
/// - Parameters:
/// - applicationContext: Context from Application that instigated the request
/// - channel: Channel that generated this request
Expand Down
22 changes: 11 additions & 11 deletions Sources/HummingbirdAuth/Authenticator/Authenticator.swift
Expand Up @@ -15,8 +15,8 @@
import Hummingbird
import NIOCore

/// Protocol for objects that can be returned by an `HBAuthenticator`.
public protocol HBAuthenticatable: Sendable {}
/// Protocol for objects that can be returned by an `AuthenticatorMiddleware`.
public protocol Authenticatable: Sendable {}

/// Protocol for a middleware that checks if a request is authenticated.
///
Expand All @@ -26,15 +26,15 @@ public protocol HBAuthenticatable: Sendable {}
/// run then throw an error.
///
/// To use an authenticator middleware it is required that your request context conform to
/// ``HBAuthRequestContext`` so the middleware can attach authentication data to
/// ``HBAuthRequestContext/auth``.
/// ``AuthRequestContext`` so the middleware can attach authentication data to
/// ``AuthRequestContext/auth``.
///
/// A simple username, password authenticator could be implemented as follows. If the
/// authenticator is successful it returns a `User` struct, otherwise it returns `nil`.
///
/// ```swift
/// struct BasicAuthenticator: HBAuthenticator {
/// func authenticate<Context: HBAuthRequestContext>(request: HBRequest, context: Context) async throws -> User? {
/// struct BasicAuthenticator: AuthenticatorMiddleware {
/// func authenticate<Context: AuthRequestContext>(request: Request, context: Context) async throws -> User? {
/// // Basic authentication info in the "Authorization" header, is accessible
/// // via request.headers.basic
/// guard let basic = request.headers.basic else { return nil }
Expand All @@ -55,20 +55,20 @@ public protocol HBAuthenticatable: Sendable {}
/// }
/// }
/// ```
public protocol HBAuthenticator: HBMiddlewareProtocol where Context: HBAuthRequestContext {
public protocol AuthenticatorMiddleware: RouterMiddleware where Context: AuthRequestContext {
/// type to be authenticated
associatedtype Value: HBAuthenticatable
associatedtype Value: Authenticatable
/// Called by middleware to see if request can authenticate.
///
/// Should return an authenticatable object if authenticated, return nil is not authenticated
/// but want the request to be passed onto the next middleware or the router, or throw an error
/// if the request should not proceed any further
func authenticate(request: HBRequest, context: Context) async throws -> Value?
func authenticate(request: Request, context: Context) async throws -> Value?
}

extension HBAuthenticator {
extension AuthenticatorMiddleware {
/// Calls `authenticate` and if it returns a valid authenticatable object `login` with this object
public func handle(_ request: HBRequest, context: Context, next: (HBRequest, Context) async throws -> HBResponse) async throws -> HBResponse {
public func handle(_ request: Request, context: Context, next: (Request, Context) async throws -> Response) async throws -> Response {
if let authenticated = try await authenticate(request: request, context: context) {
var context = context
context.auth.login(authenticated)
Expand Down
16 changes: 8 additions & 8 deletions Sources/HummingbirdAuth/Authenticator/LoginCache.swift
Expand Up @@ -14,43 +14,43 @@

import Hummingbird

public struct HBLoginCache: Sendable {
public struct LoginCache: Sendable {
public init() {
self.cache = [:]
}

/// Login with authenticatable object. Add object to cache
/// - Parameter auth: authentication details
public mutating func login<Auth: HBAuthenticatable>(_ auth: Auth) {
public mutating func login<Auth: Authenticatable>(_ auth: Auth) {
self.cache = [ObjectIdentifier(Auth.self): auth]
}

/// Logout authenticatable object. Removes object from cache
/// - Parameter auth: authentication type
public mutating func logout<Auth: HBAuthenticatable>(_: Auth.Type) {
public mutating func logout<Auth: Authenticatable>(_: Auth.Type) {
self.cache[ObjectIdentifier(Auth.self)] = nil
}

/// Return authenticated type
/// - Parameter auth: Type required
public func get<Auth: HBAuthenticatable>(_: Auth.Type) -> Auth? {
public func get<Auth: Authenticatable>(_: Auth.Type) -> Auth? {
return self.cache[ObjectIdentifier(Auth.self)] as? Auth
}

/// Require authenticated type
/// - Parameter auth: Type required
public func require<Auth: HBAuthenticatable>(_: Auth.Type) throws -> Auth {
public func require<Auth: Authenticatable>(_: Auth.Type) throws -> Auth {
guard let auth = get(Auth.self) else {
throw HBHTTPError(.unauthorized)
throw HTTPError(.unauthorized)
}
return auth
}

/// Return if cache is authenticated with type
/// - Parameter auth: Authentication type
public func has<Auth: HBAuthenticatable>(_: Auth.Type) -> Bool {
public func has<Auth: Authenticatable>(_: Auth.Type) -> Bool {
return self.cache[ObjectIdentifier(Auth.self)] != nil
}

var cache: [ObjectIdentifier: HBAuthenticatable]
var cache: [ObjectIdentifier: Authenticatable]
}
35 changes: 35 additions & 0 deletions Sources/HummingbirdAuth/Deprecated.swift
@@ -0,0 +1,35 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Hummingbird server framework project
//
// Copyright (c) 2024 the Hummingbird authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See hummingbird/CONTRIBUTORS.txt for the list of Hummingbird authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

// Below is a list of deprecated symbols with the "HB" prefix. These are available
// temporarily to ease transition from the old symbols that included the "HB"
// prefix to the new ones.
//
// This file will be removed before we do a 2.0 release

@_documentation(visibility: internal) @available(*, deprecated, renamed: "AuthRequestContext")
public typealias HBAuthRequestContext = AuthRequestContext
@_documentation(visibility: internal) @available(*, deprecated, renamed: "BasicAuthRequestContext")
public typealias HBBasicAuthRequestContext = BasicAuthRequestContext
@_documentation(visibility: internal) @available(*, deprecated, renamed: "LoginCache")
public typealias HBLoginCache = LoginCache

@_documentation(visibility: internal) @available(*, deprecated, renamed: "Authenticatable")
public typealias HBAuthenticatable = Authenticatable
@_documentation(visibility: internal) @available(*, deprecated, renamed: "AuthenticatorMiddleware")
public typealias HBAuthenticator = AuthenticatorMiddleware
@_documentation(visibility: internal) @available(*, deprecated, renamed: "SessionMiddleware")
public typealias HBSessionAuthenticator = SessionMiddleware
@_documentation(visibility: internal) @available(*, deprecated, renamed: "SessionStorage")
public typealias HBSessionStorage = SessionStorage
Expand Up @@ -15,10 +15,10 @@
import Hummingbird

/// Middleware returning 404 for unauthenticated requests
public struct IsAuthenticatedMiddleware<Auth: HBAuthenticatable, Context: HBAuthRequestContext>: HBMiddlewareProtocol {
public struct IsAuthenticatedMiddleware<Auth: Authenticatable, Context: AuthRequestContext>: RouterMiddleware {
public init(_: Auth.Type) {}

public func handle(_ request: HBRequest, context: Context, next: (HBRequest, Context) async throws -> HBResponse) async throws -> HBResponse { guard context.auth.has(Auth.self) else { throw HBHTTPError(.unauthorized) }
public func handle(_ request: Request, context: Context, next: (Request, Context) async throws -> Response) async throws -> Response { guard context.auth.has(Auth.self) else { throw HTTPError(.unauthorized) }
return try await next(request, context)
}
}
16 changes: 8 additions & 8 deletions Sources/HummingbirdAuth/OTP/OTP.swift
Expand Up @@ -2,7 +2,7 @@
//
// This source file is part of the Hummingbird server framework project
//
// Copyright (c) 2021-2021 the Hummingbird authors
// Copyright (c) 2021-2024 the Hummingbird authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
Expand Down Expand Up @@ -41,7 +41,7 @@ protocol OTP {
}

extension OTP {
/// Create Authenticator URL for OTP generator
/// Create authenticator URL for OTP generator
///
/// - Parameters:
/// - algorithmName: Name of algorithm
Expand Down Expand Up @@ -104,7 +104,7 @@ public struct HOTP: OTP, Sendable {

/// Initialize HOTP
///
/// If you are using the Google Authenticator you should choose the default values for length and hashFunction
/// If you are using the Google AuthenticatorMiddleware you should choose the default values for length and hashFunction
///
/// - Parameters:
/// - secret: Secret known by client and server
Expand All @@ -124,9 +124,9 @@ public struct HOTP: OTP, Sendable {
self.compute(message: counter.bigEndian.bytes)
}

/// Create Authenticator URL for HOTP generator
/// Create AuthenticatorMiddleware URL for HOTP generator
///
/// OTP is used commonly with authenticator apps on the phone. The Authenticator apps require your
/// OTP is used commonly with authenticator apps on the phone. The AuthenticatorMiddleware apps require your
/// secret to be Base32 encoded when you supply it. You can either supply the base32 encoded secret
/// to be copied into the authenticator app or generate a QR Code to be scanned. This generates the
/// URL you should create your QR Code from.
Expand Down Expand Up @@ -159,7 +159,7 @@ public struct TOTP: OTP, Sendable {

/// Initialize TOTP
///
/// If you are using the Google Authenticator you should choose the default values for length, timeStep and hashFunction
/// If you are using the Google AuthenticatorMiddleware you should choose the default values for length, timeStep and hashFunction
///
/// - Parameters:
/// - secret: Secret known by client and server
Expand All @@ -184,9 +184,9 @@ public struct TOTP: OTP, Sendable {
return self.compute(message: value.bigEndian.bytes)
}

/// Create Authenticator URL for TOTP generator
/// Create AuthenticatorMiddleware URL for TOTP generator
///
/// OTP is used commonly with authenticator apps on the phone. The Authenticator apps require your
/// OTP is used commonly with authenticator apps on the phone. The AuthenticatorMiddleware apps require your
/// secret to be Base32 encoded when you supply it. You can either supply the base32 encoded secret
/// to be copied into the authenticator app or generate a QR Code to be scanned. This generates the
/// URL you should create your QR Code from.
Expand Down
10 changes: 5 additions & 5 deletions Sources/HummingbirdAuth/Sessions/SessionAuthenticator.swift
Expand Up @@ -15,25 +15,25 @@
import Hummingbird

/// Session authenticator
public protocol HBSessionAuthenticator: HBAuthenticator {
public protocol SessionMiddleware: AuthenticatorMiddleware {
/// authenticable value
associatedtype Value = Value
/// session object
associatedtype Session: Codable

/// container for session objects
var sessionStorage: HBSessionStorage { get }
var sessionStorage: SessionStorage { get }

/// Convert Session object into authenticated user
/// - Parameters:
/// - from: session
/// - request: request being processed
/// - Returns: Future holding optional authenticated user
func getValue(from: Session, request: HBRequest, context: Context) async throws -> Value?
func getValue(from: Session, request: Request, context: Context) async throws -> Value?
}

extension HBSessionAuthenticator {
public func authenticate(request: HBRequest, context: Context) async throws -> Value? {
extension SessionMiddleware {
public func authenticate(request: Request, context: Context) async throws -> Value? {
guard let session: Session = try await self.sessionStorage.load(request: request) else { return nil }
return try await getValue(from: session, request: request, context: context)
}
Expand Down
30 changes: 15 additions & 15 deletions Sources/HummingbirdAuth/Sessions/SessionStorage.swift
Expand Up @@ -16,8 +16,8 @@ import ExtrasBase64
import Hummingbird

/// Stores session data
public struct HBSessionStorage: Sendable {
/// HBSessionStorage Errors
public struct SessionStorage: Sendable {
/// SessionStorage Errors
public struct Error: Swift.Error, Equatable {
enum ErrorType {
case sessionDoesNotExist
Expand All @@ -35,7 +35,7 @@ public struct HBSessionStorage: Sendable {
let sessionCookie: String

/// Initialize session storage
public init(_ storage: any HBPersistDriver, sessionCookie: String = "SESSION_ID") {
public init(_ storage: any PersistDriver, sessionCookie: String = "SESSION_ID") {
self.storage = storage
self.sessionCookie = sessionCookie
}
Expand All @@ -44,17 +44,17 @@ public struct HBSessionStorage: Sendable {
///
/// Saving a new session will create a new session id and returns a cookie setting
/// the session id. You need to then return a response including this cookie. You
/// can either create an ``HummingbirdCore/HBResponse`` directly or use ``Hummingbird/HBEditedResponse`` to
/// can either create an ``HummingbirdCore/Response`` directly or use ``Hummingbird/EditedResponse`` to
/// generate the response from another type.
/// ```swift
/// let cookie = try await sessionStorage.save(session: session, expiresIn: .seconds(600))
/// var response = HBEditedResponse(response: responseGenerator)
/// var response = EditedResponse(response: responseGenerator)
/// response.setCookie(cookie)
/// return response
/// ```
/// If you know a session already exists it is preferable to use
/// ``HBSessionStorage/update(session:expiresIn:request:)``.
public func save(session: some Codable, expiresIn: Duration) async throws -> HBCookie {
/// ``SessionStorage/update(session:expiresIn:request:)``.
public func save(session: some Codable, expiresIn: Duration) async throws -> Cookie {
let sessionId = Self.createSessionId()
// prefix with "hbs."
try await self.storage.set(
Expand All @@ -68,7 +68,7 @@ public struct HBSessionStorage: Sendable {
/// update existing session
///
/// If session does not exist then a `sessionDoesNotExist` error will be thrown
public func update(session: some Codable, expiresIn: Duration, request: HBRequest) async throws {
public func update(session: some Codable, expiresIn: Duration, request: Request) async throws {
guard let sessionId = self.getId(request: request) else {
throw Error.sessionDoesNotExist
}
Expand All @@ -81,7 +81,7 @@ public struct HBSessionStorage: Sendable {
}

/// load session
public func load<Session: Codable>(as: Session.Type = Session.self, request: HBRequest) async throws -> Session? {
public func load<Session: Codable>(as: Session.Type = Session.self, request: Request) async throws -> Session? {
guard let sessionId = getId(request: request) else { return nil }
// prefix with "hbs."
return try await self.storage.get(
Expand All @@ -91,7 +91,7 @@ public struct HBSessionStorage: Sendable {
}

/// delete session
public func delete(request: HBRequest) async throws {
public func delete(request: Request) async throws {
guard let sessionId = getId(request: request) else { return }
// prefix with "hbs."
return try await self.storage.remove(
Expand All @@ -100,16 +100,16 @@ public struct HBSessionStorage: Sendable {
}

/// Get session id gets id from request
func getId(request: HBRequest) -> String? {
func getId(request: Request) -> String? {
guard let sessionCookie = request.cookies[self.sessionCookie]?.value else { return nil }
return String(sessionCookie)
}

/// set session id on response
func setId(_ id: String, request: HBRequest) {
func setId(_ id: String, request: Request) {
/* precondition(
request.extensions.get(\.response) != nil,
"Saving a session involves editing the response via HBRequest.response which cannot be done outside of a route without the .editResponse option set"
"Saving a session involves editing the response via Request.response which cannot be done outside of a route without the .editResponse option set"
)
switch self.sessionID {
case .cookie(let cookie):
Expand All @@ -125,8 +125,8 @@ public struct HBSessionStorage: Sendable {
return String(base64Encoding: bytes)
}

// This is wrapped in an unsafe storage wrapper because I cannot conform `HBPersistDriver`
// This is wrapped in an unsafe storage wrapper because I cannot conform `PersistDriver`
// to `Sendable` at this point because Redis and Fluent types do not currently conform to
// `Sendable` when it should be possible for this to be the case.
let storage: any HBPersistDriver
let storage: any PersistDriver
}