From 980ebc0d667d7b44857974a10b43e604228461f8 Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 6 Nov 2023 22:39:38 +0100 Subject: [PATCH 01/12] Switch to using JWTKit v5 --- Package.swift | 12 +- Sources/JWT/Application+JWT.swift | 18 +-- Sources/JWT/AsyncJWTAuthenticator.swift | 38 ------- Sources/JWT/JWT+Apple.swift | 35 +++--- Sources/JWT/JWT+Concurrency.swift | 141 ------------------------ Sources/JWT/JWT+Google.swift | 63 ++++------- Sources/JWT/JWT+Microsoft.swift | 35 +++--- Sources/JWT/JWTAuthenticator.swift | 24 ++-- Sources/JWT/Request+JWT.swift | 30 ++--- Tests/JWTTests/JWTTests.swift | 110 +++++++++--------- 10 files changed, 137 insertions(+), 369 deletions(-) delete mode 100644 Sources/JWT/AsyncJWTAuthenticator.swift delete mode 100644 Sources/JWT/JWT+Concurrency.swift diff --git a/Package.swift b/Package.swift index 55d2c69..2f1676d 100644 --- a/Package.swift +++ b/Package.swift @@ -1,19 +1,19 @@ -// 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", branch: "jwtkit-5"), .package(url: "https://github.com/vapor/vapor.git", from: "4.50.0"), ], targets: [ diff --git a/Sources/JWT/Application+JWT.swift b/Sources/JWT/Application+JWT.swift index db37935..512d915 100644 --- a/Sources/JWT/Application+JWT.swift +++ b/Sources/JWT/Application+JWT.swift @@ -1,16 +1,16 @@ -import Vapor import JWTKit +import Vapor -extension Application { - public var jwt: JWT { +public extension Application { + var jwt: JWT { .init(_application: self) } - public struct JWT { + struct JWT { private final class Storage { - var signers: JWTSigners + var keys: JWTKeyCollection init() { - self.signers = .init() + self.keys = .init() } } @@ -20,9 +20,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 { diff --git a/Sources/JWT/AsyncJWTAuthenticator.swift b/Sources/JWT/AsyncJWTAuthenticator.swift deleted file mode 100644 index ef03440..0000000 --- a/Sources/JWT/AsyncJWTAuthenticator.swift +++ /dev/null @@ -1,38 +0,0 @@ -#if compiler(>=5.5) && canImport(_Concurrency) -import NIOCore -import Vapor - -@available(macOS 12, iOS 15, watchOS 8, tvOS 15, *) -extension JWTPayload where Self: Authenticatable { - public static func asyncAuthenticator() -> AsyncAuthenticator { - AsyncJWTPayloadAuthenticator() - } -} - -@available(macOS 12, iOS 15, watchOS 8, tvOS 15, *) -private struct AsyncJWTPayloadAuthenticator: AsyncJWTAuthenticator - where Payload: JWTPayload & Authenticatable -{ - func authenticate(jwt: Payload, for request: Request) async throws { - request.auth.login(jwt) - } -} - -@available(macOS 12, iOS 15, watchOS 8, tvOS 15, *) -public protocol AsyncJWTAuthenticator: AsyncBearerAuthenticator { - associatedtype Payload: JWTPayload - func authenticate(jwt: Payload, for request: Request) async throws -} - -@available(macOS 12, iOS 15, watchOS 8, tvOS 15, *) -extension AsyncJWTAuthenticator { - public func authenticate(bearer: BearerAuthorization, for request: Request) async throws { - try await self.authenticate( - jwt: request.jwt.verify(bearer.token), - for: request - ) - } -} - - -#endif diff --git a/Sources/JWT/JWT+Apple.swift b/Sources/JWT/JWT+Apple.swift index cf28c21..86af81c 100644 --- a/Sources/JWT/JWT+Apple.swift +++ b/Sources/JWT/JWT+Apple.swift @@ -8,30 +8,25 @@ extension Request.JWT { public struct Apple { public let _jwt: Request.JWT - public func verify(applicationIdentifier: String? = nil) -> EventLoopFuture { + 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 { - 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, applicationIdentifier: String? = nil) -> EventLoopFuture - 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, 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 } } } @@ -44,12 +39,8 @@ extension Application.JWT { public struct Apple { public let _jwt: Application.JWT - public func signers(on request: Request) -> EventLoopFuture { - 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 { diff --git a/Sources/JWT/JWT+Concurrency.swift b/Sources/JWT/JWT+Concurrency.swift deleted file mode 100644 index f7c5a42..0000000 --- a/Sources/JWT/JWT+Concurrency.swift +++ /dev/null @@ -1,141 +0,0 @@ -#if compiler(>=5.5) && canImport(_Concurrency) -import NIOCore -import Vapor - -@available(macOS 12, iOS 15, watchOS 8, tvOS 15, *) -extension Request.JWT.Apple { - 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 { - try await self.verify([UInt8](message.utf8), applicationIdentifier: applicationIdentifier) - } - - public func verify(_ message: Message, applicationIdentifier: String? = nil) async throws -> AppleIdentityToken - where Message: DataProtocol - { - let signers = try await self._jwt._request.application.jwt.apple.signers(on: self._jwt._request) - 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 - } -} - -@available(macOS 12, iOS 15, watchOS 8, tvOS 15, *) -extension Application.JWT.Apple { - public func signers(on request: Request) async throws -> JWTSigners { - let jwks = try await self.jwks.get(on: request).get() - let signers = JWTSigners() - try signers.use(jwks: jwks) - return signers - } -} - -@available(macOS 12, iOS 15, watchOS 8, tvOS 15, *) -extension Request.JWT.Google { - public func verify( - applicationIdentifier: String? = nil, - gSuiteDomainName: String? = nil - ) async throws -> GoogleIdentityToken { - 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, - gSuiteDomainName: gSuiteDomainName - ) - } - - public func verify( - _ message: String, - applicationIdentifier: String? = nil, - gSuiteDomainName: String? = nil - ) async throws -> GoogleIdentityToken { - try await self.verify( - [UInt8](message.utf8), - applicationIdentifier: applicationIdentifier, - gSuiteDomainName: gSuiteDomainName - ) - } - - public func verify( - _ message: Message, - applicationIdentifier: String? = nil, - gSuiteDomainName: String? = nil - ) async throws -> GoogleIdentityToken - where Message: DataProtocol - { - let signers = try await self._jwt._request.application.jwt.google.signers(on: self._jwt._request) - let token = try signers.verify(message, as: GoogleIdentityToken.self) - if let applicationIdentifier = applicationIdentifier ?? self._jwt._request.application.jwt.google.applicationIdentifier { - try token.audience.verifyIntendedAudience(includes: applicationIdentifier) - } - - if let gSuiteDomainName = gSuiteDomainName ?? self._jwt._request.application.jwt.google.gSuiteDomainName { - guard let hd = token.hostedDomain, hd.value == gSuiteDomainName else { - throw JWTError.claimVerificationFailure( - name: "hostedDomain", - reason: "Hosted domain claim does not match gSuite domain name" - ) - } - } - return token - } -} - -@available(macOS 12, iOS 15, watchOS 8, tvOS 15, *) -extension Application.JWT.Google { - public func signers(on request: Request) async throws -> JWTSigners { - let jwks = try await self.jwks.get(on: request).get() - let signers = JWTSigners() - try signers.use(jwks: jwks) - return signers - } -} - -@available(macOS 12, iOS 15, watchOS 8, tvOS 15, *) -extension Request.JWT.Microsoft { - 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 { - try await self.verify([UInt8](message.utf8), applicationIdentifier: applicationIdentifier) - } - - public func verify(_ message: Message, applicationIdentifier: String? = nil) async throws -> MicrosoftIdentityToken - where Message: DataProtocol - { - let signers = try await self._jwt._request.application.jwt.microsoft.signers(on: self._jwt._request) - let token = try signers.verify(message, as: MicrosoftIdentityToken.self) - if let applicationIdentifier = applicationIdentifier ?? self._jwt._request.application.jwt.microsoft.applicationIdentifier { - try token.audience.verifyIntendedAudience(includes: applicationIdentifier) - } - return token - } -} - -@available(macOS 12, iOS 15, watchOS 8, tvOS 15, *) -extension Application.JWT.Microsoft { - public func signers(on request: Request) async throws -> JWTSigners { - let jwks = try await self.jwks.get(on: request).get() - let signers = JWTSigners() - try signers.use(jwks: jwks) - return signers - } -} - -#endif diff --git a/Sources/JWT/JWT+Google.swift b/Sources/JWT/JWT+Google.swift index ae3a723..e3c365d 100644 --- a/Sources/JWT/JWT+Google.swift +++ b/Sources/JWT/JWT+Google.swift @@ -11,57 +11,42 @@ extension Request.JWT { public func verify( applicationIdentifier: String? = nil, gSuiteDomainName: String? = nil - ) -> EventLoopFuture { + ) async throws -> GoogleIdentityToken { 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, - gSuiteDomainName: gSuiteDomainName - ) + return try await self.verify(token, applicationIdentifier: applicationIdentifier, gSuiteDomainName: gSuiteDomainName) } public func verify( _ message: String, applicationIdentifier: String? = nil, gSuiteDomainName: String? = nil - ) -> EventLoopFuture { - self.verify( - [UInt8](message.utf8), - applicationIdentifier: applicationIdentifier, - gSuiteDomainName: gSuiteDomainName - ) + ) async throws -> GoogleIdentityToken { + try await self.verify([UInt8](message.utf8), applicationIdentifier: applicationIdentifier, gSuiteDomainName: gSuiteDomainName) } - public func verify( - _ message: Message, + public func verify( + _ message: some DataProtocol, applicationIdentifier: String? = nil, gSuiteDomainName: String? = nil - ) -> EventLoopFuture - where Message: DataProtocol - { - self._jwt._request.application.jwt.google.signers( - on: self._jwt._request - ).flatMapThrowing { signers in - let token = try signers.verify(message, as: GoogleIdentityToken.self) - if let applicationIdentifier = applicationIdentifier ?? self._jwt._request.application.jwt.google.applicationIdentifier { - try token.audience.verifyIntendedAudience(includes: applicationIdentifier) - } - - if let gSuiteDomainName = gSuiteDomainName ?? self._jwt._request.application.jwt.google.gSuiteDomainName { - guard let hd = token.hostedDomain, hd.value == gSuiteDomainName else { - throw JWTError.claimVerificationFailure( - name: "hostedDomain", - reason: "Hosted domain claim does not match gSuite domain name" - ) - } + ) async throws -> GoogleIdentityToken { + let keys = try await self._jwt._request.application.jwt.google.keys(on: self._jwt._request) + let token = try await keys.verify(message, as: GoogleIdentityToken.self) + if let applicationIdentifier = applicationIdentifier ?? self._jwt._request.application.jwt.google.applicationIdentifier { + try token.audience.verifyIntendedAudience(includes: applicationIdentifier) + } + if let gSuiteDomainName = gSuiteDomainName ?? self._jwt._request.application.jwt.google.gSuiteDomainName { + guard let hd = token.hostedDomain, hd.value == gSuiteDomainName else { + throw JWTError.claimVerificationFailure( + name: "hostedDomain", + reason: "Hosted domain claim does not match gSuite domain name" + ) } - return token } + return token } - } } @@ -73,12 +58,8 @@ extension Application.JWT { public struct Google { public let _jwt: Application.JWT - public func signers(on request: Request) -> EventLoopFuture { - 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 { diff --git a/Sources/JWT/JWT+Microsoft.swift b/Sources/JWT/JWT+Microsoft.swift index 549e518..ae4ba6d 100644 --- a/Sources/JWT/JWT+Microsoft.swift +++ b/Sources/JWT/JWT+Microsoft.swift @@ -8,30 +8,25 @@ extension Request.JWT { public struct Microsoft { public let _jwt: Request.JWT - public func verify(applicationIdentifier: String? = nil) -> EventLoopFuture { + 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.") - 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 { - self.verify([UInt8](message.utf8), applicationIdentifier: applicationIdentifier) + public func verify(_ message: String, applicationIdentifier: String? = nil) async throws -> MicrosoftIdentityToken { + try await self.verify([UInt8](message.utf8), applicationIdentifier: applicationIdentifier) } - public func verify(_ message: Message, applicationIdentifier: String? = nil) -> EventLoopFuture - where Message: DataProtocol - { - self._jwt._request.application.jwt.microsoft.signers( - on: self._jwt._request - ).flatMapThrowing { signers in - let token = try signers.verify(message, as: MicrosoftIdentityToken.self) - if let applicationIdentifier = applicationIdentifier ?? self._jwt._request.application.jwt.microsoft.applicationIdentifier { - try token.audience.verifyIntendedAudience(includes: applicationIdentifier) - } - return token + public func verify(_ message: some DataProtocol, 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 { + try token.audience.verifyIntendedAudience(includes: applicationIdentifier) } + return token } } } @@ -44,12 +39,8 @@ extension Application.JWT { public struct Microsoft { public let _jwt: Application.JWT - public func signers(on request: Request) -> EventLoopFuture { - 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 { diff --git a/Sources/JWT/JWTAuthenticator.swift b/Sources/JWT/JWTAuthenticator.swift index 91bf951..9f5bc78 100644 --- a/Sources/JWT/JWTAuthenticator.swift +++ b/Sources/JWT/JWTAuthenticator.swift @@ -1,7 +1,7 @@ import Vapor -extension JWTPayload where Self: Authenticatable { - public static func authenticator() -> Authenticator { +public extension JWTPayload where Self: Authenticatable { + static func authenticator() -> AsyncAuthenticator { JWTPayloadAuthenticator() } } @@ -9,26 +9,18 @@ extension JWTPayload where Self: Authenticatable { private struct JWTPayloadAuthenticator: JWTAuthenticator where Payload: JWTPayload & Authenticatable { - func authenticate(jwt: Payload, for request: Request) -> EventLoopFuture { + func authenticate(jwt: Payload, for request: Request) async throws { request.auth.login(jwt) - return request.eventLoop.makeSucceededFuture(()) } } -public protocol JWTAuthenticator: BearerAuthenticator { +public protocol JWTAuthenticator: AsyncBearerAuthenticator { associatedtype Payload: JWTPayload - func authenticate(jwt: Payload, for request: Request) -> EventLoopFuture + func authenticate(jwt: Payload, for request: Request) async throws } -extension JWTAuthenticator { - public func authenticate(bearer: BearerAuthorization, for request: Request) -> EventLoopFuture { - do { - return try self.authenticate( - jwt: request.jwt.verify(bearer.token), - for: request - ) - } catch { - return request.eventLoop.makeFailedFuture(error) - } +public extension JWTAuthenticator { + func authenticate(bearer: BearerAuthorization, for request: Request) async throws { + try await self.authenticate(jwt: request.jwt.verify(bearer.token), for: request) } } diff --git a/Sources/JWT/Request+JWT.swift b/Sources/JWT/Request+JWT.swift index 964dfb9..914f0e3 100644 --- a/Sources/JWT/Request+JWT.swift +++ b/Sources/JWT/Request+JWT.swift @@ -1,43 +1,43 @@ -import Vapor import JWTKit +import Vapor -extension Request { - public var jwt: JWT { +public extension Request { + var jwt: JWT { .init(_request: self) } - public struct JWT { + struct JWT { public let _request: Request - + @discardableResult - public func verify(as payload: Payload.Type = Payload.self) throws -> Payload + public func verify(as _: Payload.Type = Payload.self) async throws -> Payload where Payload: JWTPayload { guard let token = self._request.headers.bearerAuthorization?.token else { self._request.logger.error("Request is missing JWT bearer header") throw Abort(.unauthorized) } - return try self.verify(token, as: Payload.self) + return try await self.verify(token, as: Payload.self) } - + @discardableResult - public func verify(_ message: String, as payload: Payload.Type = Payload.self) throws -> Payload + public func verify(_ message: String, as _: Payload.Type = Payload.self) async throws -> Payload where Payload: JWTPayload { - try self.verify([UInt8](message.utf8), as: Payload.self) + try await self.verify([UInt8](message.utf8), as: Payload.self) } - + @discardableResult - public func verify(_ message: Message, as payload: Payload.Type = Payload.self) throws -> Payload + public func verify(_ message: Message, as _: Payload.Type = Payload.self) async throws -> Payload where Message: DataProtocol, Payload: JWTPayload { - try self._request.application.jwt.signers.verify(message, as: Payload.self) + try await self._request.application.jwt.keys.verify(message, as: Payload.self) } - public func sign(_ jwt: Payload, kid: JWKIdentifier? = nil) throws -> String + public func sign(_ jwt: Payload, kid: JWKIdentifier? = nil) async throws -> String where Payload: JWTPayload { - try self._request.application.jwt.signers.sign(jwt, kid: kid) + try await self._request.application.jwt.keys.sign(jwt, kid: kid) } } } diff --git a/Tests/JWTTests/JWTTests.swift b/Tests/JWTTests/JWTTests.swift index 8924786..b85c0f0 100644 --- a/Tests/JWTTests/JWTTests.swift +++ b/Tests/JWTTests/JWTTests.swift @@ -3,40 +3,37 @@ import JWTKit import XCTVapor class JWTTests: XCTestCase { - func testDocs() throws { + func testDocs() async throws { // creates a new application for testing let app = Application(.testing) defer { app.shutdown() } // Add HMAC with SHA-256 signer. - app.jwt.signers.use(.hs256(key: "secret")) + await app.jwt.keys.addHS256(key: "secret") - app.jwt.signers.use(.hs256(key: "foo"), kid: "a") - app.jwt.signers.use(.hs256(key: "bar"), kid: "b") + await app.jwt.keys.addHS256(key: "foo", kid: "a") + await app.jwt.keys.addHS256(key: "bar", kid: "b") app.jwt.apple.applicationIdentifier = "..." - app.get("apple") { req -> EventLoopFuture in - req.jwt.apple.verify().map { token in - print(token) // AppleIdentityToken - return .ok - } + app.get("apple") { req async throws -> HTTPStatus in + let token = try await req.jwt.apple.verify() + print(token) // AppleIdentityToken + return .ok } app.jwt.google.applicationIdentifier = "..." app.jwt.google.gSuiteDomainName = "..." - app.get("google") { req -> EventLoopFuture in - req.jwt.google.verify().map { token in - print(token) // GoogleIdentityToken - return .ok - } + app.get("google") { req async throws -> HTTPStatus in + let token = try await req.jwt.google.verify() + print(token) // GoogleIdentityToken + return .ok } app.jwt.microsoft.applicationIdentifier = "..." - app.get("microsoft") { req -> EventLoopFuture in - req.jwt.microsoft.verify().map { token in - print(token) // MicrosoftIdentityToken - return .ok - } + app.get("microsoft") { req async throws -> HTTPStatus in + let token = try await req.jwt.microsoft.verify() + print(token) // MicrosoftIdentityToken + return .ok } // JWT payload structure. @@ -65,20 +62,20 @@ class JWTTests: XCTestCase { // signature verification here. // Since we have an ExpirationClaim, we will // call its verify method. - func verify(using signer: JWTSigner) throws { + func verify(using _: JWTAlgorithm) async throws { try self.expiration.verifyNotExpired() } } // Fetch and verify JWT from incoming request. - app.get("me") { req -> HTTPStatus in - let payload = try req.jwt.verify(as: TestPayload.self) + app.get("me") { req async throws -> HTTPStatus in + let payload = try await req.jwt.verify(as: TestPayload.self) print(payload) return .ok } // Generate and return a new JWT. - app.post("login") { req -> [String: String] in + app.post("login") { req async throws -> [String: String] in // Create a new instance of our JWTPayload let payload = TestPayload( subject: "vapor", @@ -86,8 +83,8 @@ class JWTTests: XCTestCase { isAdmin: true ) // Return the signed JWT - return try [ - "token": req.jwt.sign(payload, kid: "a") + return try await [ + "token": req.jwt.sign(payload, kid: "a"), ] } @@ -121,24 +118,24 @@ class JWTTests: XCTestCase { } // manual authentication using req.jwt.verify - func testManual() throws { + func testManual() async throws { // creates a new application for testing let app = Application(.testing) defer { app.shutdown() } // configures an es512 signer using random key - try app.jwt.signers.use(.es512(key: .generate())) + try await app.jwt.keys.addES512(key: .generate()) // jwt creation using req.jwt.sign - app.post("login") { req -> LoginResponse in + app.post("login") { req async throws -> LoginResponse in let credentials = try req.content.decode(LoginCredentials.self) - return try LoginResponse( + return try await LoginResponse( token: req.jwt.sign(TestUser(name: credentials.name)) ) } - app.get("me") { req -> String in - try req.jwt.verify(as: TestUser.self).name + app.get("me") { req async throws -> String in + try await req.jwt.verify(as: TestUser.self).name } // stores the token created during login @@ -168,7 +165,8 @@ class JWTTests: XCTestCase { } // create a token from a different signer - let fakeToken = try JWTSigner.es256(key: .generate()).sign(TestUser(name: "bob")) + let fakeToken = try await JWTKeyCollection() + .addES512(key: .generate()).sign(TestUser(name: "bob")) try app.testable().test( .GET, "me", headers: ["authorization": "Bearer \(fakeToken)"] ) { res in @@ -177,18 +175,18 @@ class JWTTests: XCTestCase { } // test middleware-based authentication using req.auth.require - func testMiddleware() throws { + func testMiddleware() async throws { // creates a new application for testing let app = Application(.testing) defer { app.shutdown() } // configures an es512 signer using random key - try app.jwt.signers.use(.es512(key: .generate())) + try await app.jwt.keys.addES512(key: .generate()) // jwt creation using req.jwt.sign - app.post("login") { req -> LoginResponse in + app.post("login") { req async throws -> LoginResponse in let credentials = try req.content.decode(LoginCredentials.self) - return try LoginResponse( + return try await LoginResponse( token: req.jwt.sign(TestUser(name: credentials.name)) ) } @@ -235,7 +233,7 @@ class JWTTests: XCTestCase { // token from same signer but for a different user // this tests that the guard middleware catches the failure to auth before it reaches the route handler - let wrongNameToken = try app.jwt.signers.sign(TestUser(name: "bob")) + let wrongNameToken = try await app.jwt.keys.sign(TestUser(name: "bob")) try app.testable().test( .GET, "me", headers: ["authorization": "Bearer \(wrongNameToken)"] ) { res in @@ -243,7 +241,7 @@ class JWTTests: XCTestCase { } // create a token from a different signer - let fakeToken = try JWTSigner.es256(key: .generate()).sign(TestUser(name: "bob")) + let fakeToken = try await JWTKeyCollection().addES512(key: .generate()).sign(TestUser(name: "bob")) try app.testable().test( .GET, "me", headers: ["authorization": "Bearer \(fakeToken)"] ) { res in @@ -251,22 +249,18 @@ class JWTTests: XCTestCase { } } - func testApple() throws { + func testApple() async throws { // creates a new application for testing let app = Application(.testing) defer { app.shutdown() } app.jwt.apple.applicationIdentifier = "com.raywenderlich.TILiOS" - app.get("test") { req in - req.jwt.apple.verify().map { - $0.email ?? "none" - } + app.get("test") { req async throws in + try await req.jwt.apple.verify().email ?? "none" } - app.get("test2") { req in - req.jwt.apple.verify(applicationIdentifier: "com.raywenderlich.TILiOS").map { - $0.email ?? "none" - } + app.get("test2") { req async throws in + try await req.jwt.apple.verify(applicationIdentifier: "com.raywenderlich.TILiOS").email ?? "none" } var headers = HTTPHeaders() @@ -284,12 +278,12 @@ class JWTTests: XCTestCase { } // https://github.com/vapor/jwt-kit/issues/26 - func testSignFailureSegfault() throws { + func testSignFailureSegfault() async throws { struct UserPayload: JWTPayload { var id: UUID var userName: String - func verify(using signer: JWTSigner) throws { } + func verify(using _: JWTAlgorithm) throws {} } // creates a new application for testing @@ -325,17 +319,17 @@ class JWTTests: XCTestCase { waNSUrQp9XZJLA9SgN+N2JwuDi0bxsr0saaLdmWn3S3L6rsg5Cja -----END RSA PRIVATE KEY----- """ - - try app.jwt.signers.use(.rs512(key: .private(pem: [UInt8](privateKeyString.utf8)))) - app.get { req -> String in + try await app.jwt.keys.addRS512(key: .private(pem: [UInt8](privateKeyString.utf8))) + + app.get { req async throws -> String in let authorizationPayload = UserPayload(id: UUID(), userName: "John Smith") - let accessToken = try req.jwt.sign(authorizationPayload) + let accessToken = try await req.jwt.sign(authorizationPayload) return accessToken } - for _ in 0..<1_000 { - try app.test(.GET, "/") { res in + for _ in 0 ..< 1000 { + try app.test(.GET, "/") { res in XCTAssertEqual(res.status, .ok) } } @@ -361,7 +355,6 @@ let isLoggingConfigured: Bool = { return true }() - struct LoginResponse: Content { var token: String } @@ -373,7 +366,7 @@ struct LoginCredentials: Content { struct TestUser: Content, Authenticatable, JWTPayload { var name: String - func verify(using signer: JWTSigner) throws { + func verify(using _: JWTAlgorithm) throws { // nothing to verify } } @@ -381,11 +374,10 @@ struct TestUser: Content, Authenticatable, JWTPayload { struct UserAuthenticator: JWTAuthenticator { typealias Payload = TestUser - func authenticate(jwt: TestUser, for request: Request) -> EventLoopFuture { + func authenticate(jwt: TestUser, for request: Request) async throws { if jwt.name == "foo" { // Requiring this specific username makes the test for the guard middleware in testMiddleware() valid. request.auth.login(jwt) } - return request.eventLoop.makeSucceededFuture(()) } } From 3115953563ec818121950b38edbc3c0c0a404b9f Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 7 Nov 2023 00:27:13 +0100 Subject: [PATCH 02/12] Add Sendable conformance --- Package.swift | 24 ++++++++----- Sources/JWT/Application+JWT.swift | 28 +++++++++++++--- Sources/JWT/JWT+Apple.swift | 52 +++++++++++++++++++++------- Sources/JWT/JWT+Google.swift | 56 ++++++++++++++++++++++++------- Sources/JWT/JWT+Microsoft.swift | 52 +++++++++++++++++++++------- Sources/JWT/Request+JWT.swift | 6 ++-- 6 files changed, 167 insertions(+), 51 deletions(-) diff --git a/Package.swift b/Package.swift index 2f1676d..45549b1 100644 --- a/Package.swift +++ b/Package.swift @@ -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")] + ), ] ) diff --git a/Sources/JWT/Application+JWT.swift b/Sources/JWT/Application+JWT.swift index 512d915..45e16ea 100644 --- a/Sources/JWT/Application+JWT.swift +++ b/Sources/JWT/Application+JWT.swift @@ -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 + + 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) } } diff --git a/Sources/JWT/JWT+Apple.swift b/Sources/JWT/JWT+Apple.swift index 86af81c..e1177ad 100644 --- a/Sources/JWT/JWT+Apple.swift +++ b/Sources/JWT/JWT+Apple.swift @@ -1,14 +1,17 @@ +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) @@ -16,11 +19,17 @@ extension Request.JWT { 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 { @@ -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 { @@ -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 - var applicationIdentifier: String? + private let sendableBox: NIOLockedValueBox + + 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) } } diff --git a/Sources/JWT/JWT+Google.swift b/Sources/JWT/JWT+Google.swift index e3c365d..0a3fd6d 100644 --- a/Sources/JWT/JWT+Google.swift +++ b/Sources/JWT/JWT+Google.swift @@ -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( @@ -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 { @@ -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 { @@ -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 - var applicationIdentifier: String? - var gSuiteDomainName: String? + private let sendableBox: NIOLockedValueBox + + 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) } } diff --git a/Sources/JWT/JWT+Microsoft.swift b/Sources/JWT/JWT+Microsoft.swift index ae4ba6d..06dac33 100644 --- a/Sources/JWT/JWT+Microsoft.swift +++ b/Sources/JWT/JWT+Microsoft.swift @@ -1,14 +1,17 @@ +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) @@ -16,11 +19,17 @@ extension Request.JWT { 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 { @@ -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 { @@ -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 - var applicationIdentifier: String? + private let sendableBox: NIOLockedValueBox + + 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) } } diff --git a/Sources/JWT/Request+JWT.swift b/Sources/JWT/Request+JWT.swift index 914f0e3..047a193 100644 --- a/Sources/JWT/Request+JWT.swift +++ b/Sources/JWT/Request+JWT.swift @@ -6,7 +6,7 @@ public extension Request { .init(_request: self) } - struct JWT { + struct JWT: Sendable { public let _request: Request @discardableResult @@ -28,8 +28,8 @@ public extension Request { } @discardableResult - public func verify(_ message: Message, as _: Payload.Type = Payload.self) async throws -> Payload - where Message: DataProtocol, Payload: JWTPayload + public func verify(_ 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) } From 35b20798b39d984636ce09b3743e00509952a200 Mon Sep 17 00:00:00 2001 From: Gwynne Raskind Date: Sat, 11 Nov 2023 10:37:14 -0600 Subject: [PATCH 03/12] [skip ci] Delete projectboard.yml --- .github/workflows/projectboard.yml | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 .github/workflows/projectboard.yml diff --git a/.github/workflows/projectboard.yml b/.github/workflows/projectboard.yml deleted file mode 100644 index a0e6d98..0000000 --- a/.github/workflows/projectboard.yml +++ /dev/null @@ -1,11 +0,0 @@ -name: issue-to-project-board-workflow -on: - # Trigger when an issue gets labeled or deleted - issues: - types: [reopened, closed, labeled, unlabeled, assigned, unassigned] - -jobs: - update_project_boards: - name: Update project boards - uses: vapor/ci/.github/workflows/update-project-boards-for-issue.yml@reusable-workflows - secrets: inherit From a85e1903c962dd8ef8671941447d1b80fac87539 Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 20 Nov 2023 09:56:52 +0100 Subject: [PATCH 04/12] Adapt to new errors --- Sources/JWT/JWT+Google.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/JWT/JWT+Google.swift b/Sources/JWT/JWT+Google.swift index 0a3fd6d..cf9cfe1 100644 --- a/Sources/JWT/JWT+Google.swift +++ b/Sources/JWT/JWT+Google.swift @@ -41,7 +41,7 @@ public extension Request.JWT { if let gSuiteDomainName = gSuiteDomainName ?? self._jwt._request.application.jwt.google.gSuiteDomainName { guard let hd = token.hostedDomain, hd.value == gSuiteDomainName else { throw JWTError.claimVerificationFailure( - name: "hostedDomain", + failedClaim: token.hostedDomain, reason: "Hosted domain claim does not match gSuite domain name" ) } From 2e6de9b1d8211218a9c056373cc17d877f67b5da Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 20 Nov 2023 10:15:50 +0100 Subject: [PATCH 05/12] Fix failing test --- Tests/JWTTests/JWTTests.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/Tests/JWTTests/JWTTests.swift b/Tests/JWTTests/JWTTests.swift index b85c0f0..3853c81 100644 --- a/Tests/JWTTests/JWTTests.swift +++ b/Tests/JWTTests/JWTTests.swift @@ -270,10 +270,8 @@ class JWTTests: XCTestCase { try app.test(.GET, "test", headers: headers) { res in XCTAssertEqual(res.status, .unauthorized) - XCTAssertContains(res.body.string, "expired") }.test(.GET, "test2", headers: headers) { res in XCTAssertEqual(res.status, .unauthorized) - XCTAssertContains(res.body.string, "expired") } } From 3837d6344f9bd2893ba88230d8a4325b367b9873 Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 19 Feb 2024 17:38:18 +0100 Subject: [PATCH 06/12] Update package for new JWTKit API --- Sources/JWT/Request+JWT.swift | 4 ++-- Tests/JWTTests/JWTTests.swift | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Sources/JWT/Request+JWT.swift b/Sources/JWT/Request+JWT.swift index 047a193..300b943 100644 --- a/Sources/JWT/Request+JWT.swift +++ b/Sources/JWT/Request+JWT.swift @@ -34,10 +34,10 @@ public extension Request { try await self._request.application.jwt.keys.verify(message, as: Payload.self) } - public func sign(_ jwt: Payload, kid: JWKIdentifier? = nil) async throws -> String + public func sign(_ jwt: Payload, header: JWTHeader = .init()) async throws -> String where Payload: JWTPayload { - try await self._request.application.jwt.keys.sign(jwt, kid: kid) + return try await self._request.application.jwt.keys.sign(jwt, header: header) } } } diff --git a/Tests/JWTTests/JWTTests.swift b/Tests/JWTTests/JWTTests.swift index 3853c81..14952f0 100644 --- a/Tests/JWTTests/JWTTests.swift +++ b/Tests/JWTTests/JWTTests.swift @@ -84,7 +84,7 @@ class JWTTests: XCTestCase { ) // Return the signed JWT return try await [ - "token": req.jwt.sign(payload, kid: "a"), + "token": req.jwt.sign(payload, header: ["kid": "a"]), ] } @@ -124,7 +124,7 @@ class JWTTests: XCTestCase { defer { app.shutdown() } // configures an es512 signer using random key - try await app.jwt.keys.addES512(key: .generate()) + await app.jwt.keys.addES512(key: ES512PrivateKey()) // jwt creation using req.jwt.sign app.post("login") { req async throws -> LoginResponse in @@ -166,7 +166,7 @@ class JWTTests: XCTestCase { // create a token from a different signer let fakeToken = try await JWTKeyCollection() - .addES512(key: .generate()).sign(TestUser(name: "bob")) + .addES512(key: ES512PrivateKey()).sign(TestUser(name: "bob")) try app.testable().test( .GET, "me", headers: ["authorization": "Bearer \(fakeToken)"] ) { res in @@ -181,7 +181,7 @@ class JWTTests: XCTestCase { defer { app.shutdown() } // configures an es512 signer using random key - try await app.jwt.keys.addES512(key: .generate()) + await app.jwt.keys.addES512(key: ES512PrivateKey()) // jwt creation using req.jwt.sign app.post("login") { req async throws -> LoginResponse in @@ -241,7 +241,7 @@ class JWTTests: XCTestCase { } // create a token from a different signer - let fakeToken = try await JWTKeyCollection().addES512(key: .generate()).sign(TestUser(name: "bob")) + let fakeToken = try await JWTKeyCollection().addES512(key: ES512PrivateKey()).sign(TestUser(name: "bob")) try app.testable().test( .GET, "me", headers: ["authorization": "Bearer \(fakeToken)"] ) { res in @@ -318,7 +318,7 @@ class JWTTests: XCTestCase { -----END RSA PRIVATE KEY----- """ - try await app.jwt.keys.addRS512(key: .private(pem: [UInt8](privateKeyString.utf8))) + try await app.jwt.keys.addRS256(key: Insecure.RSA.PrivateKey(pem: [UInt8](privateKeyString.utf8))) app.get { req async throws -> String in let authorizationPayload = UserPayload(id: UUID(), userName: "John Smith") From e7c03a72326e9fabea49ba2c560e8306b3359fd7 Mon Sep 17 00:00:00 2001 From: Paul Date: Wed, 21 Feb 2024 19:34:03 +0100 Subject: [PATCH 07/12] Update to use JWTKit 5 beta --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 45549b1..baf21b7 100644 --- a/Package.swift +++ b/Package.swift @@ -13,7 +13,7 @@ let package = Package( .library(name: "JWT", targets: ["JWT"]), ], dependencies: [ - .package(url: "https://github.com/vapor/jwt-kit.git", branch: "jwtkit-5"), + .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"), ], targets: [ From 2e123be6a775fc78aa886c1b0252e7fd5d9b3503 Mon Sep 17 00:00:00 2001 From: Paul Date: Thu, 22 Feb 2024 10:49:05 +0100 Subject: [PATCH 08/12] Bump Vapor dependency --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index baf21b7..ebb1be1 100644 --- a/Package.swift +++ b/Package.swift @@ -14,7 +14,7 @@ let package = Package( ], dependencies: [ .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"), + .package(url: "https://github.com/vapor/vapor.git", from: "4.92.0"), ], targets: [ .target( From de69bde767a0a8243ff1105b559d06d18b754f16 Mon Sep 17 00:00:00 2001 From: Paul Date: Thu, 22 Feb 2024 12:49:30 +0100 Subject: [PATCH 09/12] Fix stupid mistake --- Tests/JWTTests/JWTTests.swift | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/Tests/JWTTests/JWTTests.swift b/Tests/JWTTests/JWTTests.swift index 8cfc550..55ccd6e 100644 --- a/Tests/JWTTests/JWTTests.swift +++ b/Tests/JWTTests/JWTTests.swift @@ -250,10 +250,10 @@ class JWTTests: XCTestCase { } /* - If this test expires you might need to regenerate the JWT. Use https://github.com/0xTim/vapor-jwt-test-siwa and run the project on a real device - Try signing in with Apple and it will print a new JWT to use. - Note that it takes a day for the JWT to expire before the test passes - */ + If this test expires you might need to regenerate the JWT. Use https://github.com/0xTim/vapor-jwt-test-siwa and run the project on a real device + Try signing in with Apple and it will print a new JWT to use. + Note that it takes a day for the JWT to expire before the test passes + */ func testApple() async throws { // creates a new application for testing let app = Application(.testing) @@ -264,11 +264,9 @@ class JWTTests: XCTestCase { app.get("test") { req async throws in try await req.jwt.apple.verify().email ?? "none" } - + app.get("test2") { req async throws in - try await req.jwt.apple.verify(applicationIdentifier: "dev.timc.siwa-demo.TILiOS").map { - $0.email ?? "none" - } + try await req.jwt.apple.verify(applicationIdentifier: "dev.timc.siwa-demo.TILiOS").email ?? "none" } var headers = HTTPHeaders() From c5c811b7b5cfecc57bfacce0f577a6618ccd996d Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 27 Feb 2024 12:21:48 +0100 Subject: [PATCH 10/12] Update CODEOWNERS --- .github/CODEOWNERS | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index c99cc74..c07b281 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1,8 @@ -* @0xTim @gwynne +* @ptoffy +/.github/CONTRIBUTING.md @ptoffy @0xTim @gwynne +/.github/workflows/*.yml @ptoffy @0xTim @gwynne +/.github/workflows/test.yml @ptoffy @gwynne +/.spi.yml @ptoffy @0xTim @gwynne +/.gitignore @ptoffy @0xTim @gwynne +/LICENSE @ptoffy @0xTim @gwynne +/README.md @ptoffy @0xTim @gwynne From 86f03ee267f686a8594e0ae4cacf1d46544aa424 Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 27 Feb 2024 12:43:47 +0100 Subject: [PATCH 11/12] Update README --- README.md | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 0411994..d4eceef 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,31 @@

- JWTKit + + + + JWTKit +

- - Documentation + + Documentation - Team Chat + Team Chat - MIT License + MIT License - - Continuous Integration + + Continuous Integration + + + - Swift 5.2 + Swift 5.9+

- -
+
**Original author** From 8a19d72bd1335fbec0a9871a4940ea16f55b7502 Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 27 Feb 2024 12:48:22 +0100 Subject: [PATCH 12/12] Remove codecov from README --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index 0499a34..78cb45a 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,6 @@ Continuous Integration - - - Swift 5.9+