Skip to content

Commit

Permalink
Add Sendable conformance
Browse files Browse the repository at this point in the history
  • Loading branch information
ptoffy committed Nov 6, 2023
1 parent 980ebc0 commit 3115953
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 51 deletions.
24 changes: 16 additions & 8 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,21 @@ let package = Package(
.package(url: "https://github.com/vapor/vapor.git", from: "4.50.0"),
],
targets: [
.target(name: "JWT", dependencies: [
.product(name: "JWTKit", package: "jwt-kit"),
.product(name: "Vapor", package: "vapor"),
]),
.testTarget(name: "JWTTests", dependencies: [
.target(name: "JWT"),
.product(name: "XCTVapor", package: "vapor"),
]),
.target(
name: "JWT",
dependencies: [
.product(name: "JWTKit", package: "jwt-kit"),
.product(name: "Vapor", package: "vapor"),
],
swiftSettings: [.enableExperimentalFeature("StrictConcurrency")]
),
.testTarget(
name: "JWTTests",
dependencies: [
.target(name: "JWT"),
.product(name: "XCTVapor", package: "vapor"),
],
swiftSettings: [.enableExperimentalFeature("StrictConcurrency")]
),
]
)
28 changes: 24 additions & 4 deletions Sources/JWT/Application+JWT.swift
Original file line number Diff line number Diff line change
@@ -1,16 +1,36 @@
import JWTKit
import Vapor
import NIOConcurrencyHelpers

public extension Application {
var jwt: JWT {
.init(_application: self)
}

struct JWT {
private final class Storage {
var keys: JWTKeyCollection
struct JWT: Sendable {
private final class Storage: Sendable {
private struct SendableBox: Sendable {
var keys: JWTKeyCollection
}

private let sendableBox: NIOLockedValueBox<SendableBox>

var keys: JWTKeyCollection {
get {
self.sendableBox.withLockedValue { box in
box.keys
}
}
set {
self.sendableBox.withLockedValue { box in
box.keys = newValue
}
}
}

init() {
self.keys = .init()
let box = SendableBox(keys: .init())
self.sendableBox = .init(box)
}
}

Expand Down
52 changes: 40 additions & 12 deletions Sources/JWT/JWT+Apple.swift
Original file line number Diff line number Diff line change
@@ -1,26 +1,35 @@
import NIOConcurrencyHelpers
import Vapor

extension Request.JWT {
public var apple: Apple {
public extension Request.JWT {
var apple: Apple {
.init(_jwt: self)
}

public struct Apple {
struct Apple: Sendable {
public let _jwt: Request.JWT

public func verify(applicationIdentifier: String? = nil) async throws -> AppleIdentityToken {
public func verify(
applicationIdentifier: String? = nil
) async throws -> AppleIdentityToken {
guard let token = self._jwt._request.headers.bearerAuthorization?.token else {
self._jwt._request.logger.error("Request is missing JWT bearer header.")
throw Abort(.unauthorized)
}
return try await self.verify(token, applicationIdentifier: applicationIdentifier)
}

public func verify(_ message: String, applicationIdentifier: String? = nil) async throws -> AppleIdentityToken {
public func verify(
_ message: String,
applicationIdentifier: String? = nil
) async throws -> AppleIdentityToken {
try await self.verify([UInt8](message.utf8), applicationIdentifier: applicationIdentifier)
}

public func verify(_ message: some DataProtocol, applicationIdentifier: String? = nil) async throws -> AppleIdentityToken {
public func verify(
_ message: some DataProtocol & Sendable,
applicationIdentifier: String? = nil
) async throws -> AppleIdentityToken {
let keys = try await self._jwt._request.application.jwt.apple.keys(on: self._jwt._request)
let token = try await keys.verify(message, as: AppleIdentityToken.self)
if let applicationIdentifier = applicationIdentifier ?? self._jwt._request.application.jwt.apple.applicationIdentifier {
Expand All @@ -31,12 +40,12 @@ extension Request.JWT {
}
}

extension Application.JWT {
public var apple: Apple {
public extension Application.JWT {
var apple: Apple {
.init(_jwt: self)
}

public struct Apple {
struct Apple: Sendable {
public let _jwt: Application.JWT

public func keys(on request: Request) async throws -> JWTKeyCollection {
Expand All @@ -60,12 +69,31 @@ extension Application.JWT {
typealias Value = Storage
}

private final class Storage {
private final class Storage: Sendable {
private struct SendableBox: Sendable {
var applicationIdentifier: String?
}

let jwks: EndpointCache<JWKS>
var applicationIdentifier: String?
private let sendableBox: NIOLockedValueBox<SendableBox>

var applicationIdentifier: String? {
get {
self.sendableBox.withLockedValue { box in
box.applicationIdentifier
}
}
set {
self.sendableBox.withLockedValue { box in
box.applicationIdentifier = newValue
}
}
}

init() {
self.jwks = .init(uri: "https://appleid.apple.com/auth/keys")
self.applicationIdentifier = nil
let box = SendableBox(applicationIdentifier: nil)
self.sendableBox = .init(box)
}
}

Expand Down
56 changes: 44 additions & 12 deletions Sources/JWT/JWT+Google.swift
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import NIOConcurrencyHelpers
import Vapor

extension Request.JWT {
public var google: Google {
public extension Request.JWT {
var google: Google {
.init(_jwt: self)
}

public struct Google {
struct Google: Sendable {
public let _jwt: Request.JWT

public func verify(
Expand All @@ -28,7 +29,7 @@ extension Request.JWT {
}

public func verify(
_ message: some DataProtocol,
_ message: some DataProtocol & Sendable,
applicationIdentifier: String? = nil,
gSuiteDomainName: String? = nil
) async throws -> GoogleIdentityToken {
Expand All @@ -50,12 +51,12 @@ extension Request.JWT {
}
}

extension Application.JWT {
public var google: Google {
public extension Application.JWT {
var google: Google {
.init(_jwt: self)
}

public struct Google {
struct Google: Sendable {
public let _jwt: Application.JWT

public func keys(on request: Request) async throws -> JWTKeyCollection {
Expand Down Expand Up @@ -88,14 +89,45 @@ extension Application.JWT {
typealias Value = Storage
}

private final class Storage {
private final class Storage: Sendable {
private struct SendableBox: Sendable {
var applicationIdentifier: String?
var gSuiteDomainName: String?
}

let jwks: EndpointCache<JWKS>
var applicationIdentifier: String?
var gSuiteDomainName: String?
private let sendableBox: NIOLockedValueBox<SendableBox>

var applicationIdentifier: String? {
get {
self.sendableBox.withLockedValue { box in
box.applicationIdentifier
}
}
set {
self.sendableBox.withLockedValue { box in
box.applicationIdentifier = newValue
}
}
}

var gSuiteDomainName: String? {
get {
self.sendableBox.withLockedValue { box in
box.gSuiteDomainName
}
}
set {
self.sendableBox.withLockedValue { box in
box.gSuiteDomainName = newValue
}
}
}

init() {
self.jwks = .init(uri: "https://www.googleapis.com/oauth2/v3/certs")
self.applicationIdentifier = nil
self.gSuiteDomainName = nil
let box = SendableBox(applicationIdentifier: nil, gSuiteDomainName: nil)
self.sendableBox = .init(box)
}
}

Expand Down
52 changes: 40 additions & 12 deletions Sources/JWT/JWT+Microsoft.swift
Original file line number Diff line number Diff line change
@@ -1,26 +1,35 @@
import NIOConcurrencyHelpers
import Vapor

extension Request.JWT {
public var microsoft: Microsoft {
public extension Request.JWT {
var microsoft: Microsoft {
.init(_jwt: self)
}

public struct Microsoft {
struct Microsoft {
public let _jwt: Request.JWT

public func verify(applicationIdentifier: String? = nil) async throws -> MicrosoftIdentityToken {
public func verify(
applicationIdentifier: String? = nil
) async throws -> MicrosoftIdentityToken {
guard let token = self._jwt._request.headers.bearerAuthorization?.token else {
self._jwt._request.logger.error("Request is missing JWT bearer header.")
throw Abort(.unauthorized)
}
return try await self.verify(token, applicationIdentifier: applicationIdentifier)
}

public func verify(_ message: String, applicationIdentifier: String? = nil) async throws -> MicrosoftIdentityToken {
public func verify(
_ message: String,
applicationIdentifier: String? = nil
) async throws -> MicrosoftIdentityToken {
try await self.verify([UInt8](message.utf8), applicationIdentifier: applicationIdentifier)
}

public func verify(_ message: some DataProtocol, applicationIdentifier: String? = nil) async throws -> MicrosoftIdentityToken {
public func verify(
_ message: some DataProtocol & Sendable,
applicationIdentifier: String? = nil
) async throws -> MicrosoftIdentityToken {
let keys = try await self._jwt._request.application.jwt.microsoft.keys(on: self._jwt._request)
let token = try await keys.verify(message, as: MicrosoftIdentityToken.self)
if let applicationIdentifier = applicationIdentifier ?? self._jwt._request.application.jwt.microsoft.applicationIdentifier {
Expand All @@ -31,12 +40,12 @@ extension Request.JWT {
}
}

extension Application.JWT {
public var microsoft: Microsoft {
public extension Application.JWT {
var microsoft: Microsoft {
.init(_jwt: self)
}

public struct Microsoft {
struct Microsoft {
public let _jwt: Application.JWT

public func keys(on request: Request) async throws -> JWTKeyCollection {
Expand All @@ -60,12 +69,31 @@ extension Application.JWT {
typealias Value = Storage
}

private final class Storage {
private final class Storage: Sendable {
private struct SendableBox: Sendable {
var applicationIdentifier: String?
}

let jwks: EndpointCache<JWKS>
var applicationIdentifier: String?
private let sendableBox: NIOLockedValueBox<SendableBox>

var applicationIdentifier: String? {
get {
self.sendableBox.withLockedValue { box in
box.applicationIdentifier
}
}
set {
self.sendableBox.withLockedValue { box in
box.applicationIdentifier = newValue
}
}
}

init() {
self.jwks = .init(uri: "https://login.microsoftonline.com/common/discovery/keys")
self.applicationIdentifier = nil
let box = SendableBox(applicationIdentifier: nil)
self.sendableBox = .init(box)
}
}

Expand Down
6 changes: 3 additions & 3 deletions Sources/JWT/Request+JWT.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ public extension Request {
.init(_request: self)
}

struct JWT {
struct JWT: Sendable {
public let _request: Request

@discardableResult
Expand All @@ -28,8 +28,8 @@ public extension Request {
}

@discardableResult
public func verify<Message, Payload>(_ message: Message, as _: Payload.Type = Payload.self) async throws -> Payload
where Message: DataProtocol, Payload: JWTPayload
public func verify<Payload>(_ message: some DataProtocol & Sendable, as _: Payload.Type = Payload.self) async throws -> Payload
where Payload: JWTPayload
{
try await self._request.application.jwt.keys.verify(message, as: Payload.self)
}
Expand Down

0 comments on commit 3115953

Please sign in to comment.