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 Sendable requirements on Authenticatable #3095

Merged
merged 2 commits into from
Nov 2, 2023
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
39 changes: 29 additions & 10 deletions Sources/Vapor/Authentication/AuthenticationCache.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ extension Request {
extension Request.Authentication {
/// Authenticates the supplied instance for this request.
public func login<A>(_ instance: A)
where A: Authenticatable & Sendable
where A: Authenticatable
{
self.cache[A.self] = instance
self.cache[A.self] = UnsafeAuthenticationBox(instance)
}

/// Unauthenticates an authenticatable type.
public func logout<A>(_ type: A.Type = A.self)
where A: Authenticatable & Sendable
where A: Authenticatable
{
self.cache[A.self] = nil
}
Expand All @@ -35,7 +35,7 @@ extension Request.Authentication {
/// instance of that type has been authenticated or if there
/// was a problem.
@discardableResult public func require<A>(_ type: A.Type = A.self) throws -> A
where A: Authenticatable & Sendable
where A: Authenticatable
{
guard let a = self.get(A.self) else {
throw Abort(.unauthorized)
Expand All @@ -46,14 +46,14 @@ extension Request.Authentication {
/// Returns the authenticated instance of the supplied type.
/// - note: `nil` if no type has been authed.
public func get<A>(_ type: A.Type = A.self) -> A?
where A: Authenticatable & Sendable
where A: Authenticatable
{
return self.cache[A.self]
return self.cache[A.self]?.authenticated
}

/// Returns `true` if the type has been authenticated.
public func has<A>(_ type: A.Type = A.self) -> Bool
where A: Authenticatable & Sendable
where A: Authenticatable
{
return self.get(A.self) != nil
}
Expand All @@ -65,11 +65,11 @@ extension Request.Authentication {
self.storage = .init([:])
}

internal subscript<A>(_ type: A.Type) -> A?
where A: Authenticatable & Sendable
internal subscript<A>(_ type: A.Type) -> UnsafeAuthenticationBox<A>?
where A: Authenticatable
{
get {
storage.withLockedValue { $0[ObjectIdentifier(A.self)] as? A }
storage.withLockedValue { $0[ObjectIdentifier(A.self)] as? UnsafeAuthenticationBox<A> }
}
set {
storage.withLockedValue { $0[ObjectIdentifier(A.self)] = newValue }
Expand All @@ -96,3 +96,22 @@ extension Request.Authentication {
}
}
}

// This is to get around the fact that for legacy reasons we can't enforce Sendability on Authenticatable
// types (e.g. Fluent 4 models can never be Sendable because they're reference types with mutable values
// required by protocols and property wrappers). This allows us to store the Authenticatable type in a
// safe-most-of-the-time way. This does introduce an edge case where type could be stored and mutated in
// multiple places. But given how Vapor and its users use Authentication this should almost never
// occur and it was decided the trade-off was acceptable
// As the name implies, the usage of this is unsafe because it disables the sendable checking of the
// compiler and does not add any synchronisation.
@usableFromInline
internal struct UnsafeAuthenticationBox<A>: @unchecked Sendable {
@usableFromInline
let authenticated: A

@inlinable
init(_ authenticated: A) {
self.authenticated = authenticated
}
}
2 changes: 1 addition & 1 deletion Sources/Vapor/Authentication/Authenticator.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import NIOCore

/// Capable of being authenticated.
public protocol Authenticatable: Sendable { }
public protocol Authenticatable { }

/// Helper for creating authentication middleware.
///
Expand Down
2 changes: 1 addition & 1 deletion Sources/Vapor/Authentication/GuardMiddleware.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ extension Authenticatable {


private final class GuardAuthenticationMiddleware<A>: Middleware
where A: Authenticatable & Sendable
where A: Authenticatable
{
/// Error to throw when guard fails.
private let error: Error
Expand Down
2 changes: 1 addition & 1 deletion Sources/Vapor/Authentication/RedirectMiddleware.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ extension Authenticatable {


private final class RedirectMiddleware<A>: Middleware
where A: Authenticatable & Sendable
where A: Authenticatable
{
let makePath: @Sendable (Request) -> String

Expand Down