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

Update to JWTKit v5 #149

Merged
merged 14 commits into from
Feb 27, 2024
36 changes: 22 additions & 14 deletions Package.swift
0xTim marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,29 +1,37 @@
// swift-tools-version:5.4
// swift-tools-version:5.9
import PackageDescription

let package = Package(
name: "jwt",
platforms: [
.macOS(.v10_15),
.iOS(.v13),
.tvOS(.v13),
.watchOS(.v6)
.macOS(.v13),
.iOS(.v16),
.tvOS(.v16),
.watchOS(.v9),
],
products: [
.library(name: "JWT", targets: ["JWT"]),
],
dependencies: [
.package(url: "https://github.com/vapor/jwt-kit.git", from: "4.0.0"),
.package(url: "https://github.com/vapor/jwt-kit.git", from: "5.0.0-beta.1"),
.package(url: "https://github.com/vapor/vapor.git", from: "4.50.0"),
ptoffy marked this conversation as resolved.
Show resolved Hide resolved
],
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")]
),
]
)
40 changes: 30 additions & 10 deletions Sources/JWT/Application+JWT.swift
Original file line number Diff line number Diff line change
@@ -1,16 +1,36 @@
import Vapor
import JWTKit
import Vapor
import NIOConcurrencyHelpers

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

public struct JWT {
private final class Storage {
var signers: JWTSigners
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.signers = .init()
let box = SendableBox(keys: .init())
self.sendableBox = .init(box)
}
}

Expand All @@ -20,9 +40,9 @@ extension Application {

public let _application: Application

public var signers: JWTSigners {
get { self.storage.signers }
set { self.storage.signers = newValue }
public var keys: JWTKeyCollection {
get { self.storage.keys }
set { self.storage.keys = newValue }
}

private var storage: Storage {
Expand Down
38 changes: 0 additions & 38 deletions Sources/JWT/AsyncJWTAuthenticator.swift

This file was deleted.

81 changes: 50 additions & 31 deletions Sources/JWT/JWT+Apple.swift
Original file line number Diff line number Diff line change
@@ -1,55 +1,55 @@
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) -> EventLoopFuture<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.")
return self._jwt._request.eventLoop.makeFailedFuture(Abort(.unauthorized))
throw Abort(.unauthorized)
}
return self.verify(token, applicationIdentifier: applicationIdentifier)
return try await self.verify(token, applicationIdentifier: applicationIdentifier)
}

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

public func verify<Message>(_ message: Message, applicationIdentifier: String? = nil) -> EventLoopFuture<AppleIdentityToken>
where Message: DataProtocol
{
self._jwt._request.application.jwt.apple.signers(
on: self._jwt._request
).flatMapThrowing { signers in
let token = try signers.verify(message, as: AppleIdentityToken.self)
if let applicationIdentifier = applicationIdentifier ?? self._jwt._request.application.jwt.apple.applicationIdentifier {
try token.audience.verifyIntendedAudience(includes: applicationIdentifier)
}
return token
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 {
try token.audience.verifyIntendedAudience(includes: applicationIdentifier)
}
return token
}
}
}

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 signers(on request: Request) -> EventLoopFuture<JWTSigners> {
self.jwks.get(on: request).flatMapThrowing {
let signers = JWTSigners()
try signers.use(jwks: $0)
return signers
}
public func keys(on request: Request) async throws -> JWTKeyCollection {
try await JWTKeyCollection().add(jwks: jwks.get(on: request).get())
}

public var jwks: EndpointCache<JWKS> {
Expand All @@ -69,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
Loading