diff --git a/Sources/JWTProvider/Config+JWT.swift b/Sources/JWTProvider/Config+JWT.swift new file mode 100644 index 0000000..399b0b6 --- /dev/null +++ b/Sources/JWTProvider/Config+JWT.swift @@ -0,0 +1,55 @@ +import JWT +import Vapor + +// MARK: Signer map access + +private let jwtSignersKey = "jwt-provider:signers" + +extension Config { + public internal(set) var signers: SignerMap? { + get { return storage[jwtSignersKey] as? SignerMap } + set { storage[jwtSignersKey] = newValue } + } + + /// Returns the JWT signers + /// or throws an error if not properly configured + public func assertSigners() throws -> SignerMap { + guard let signers = self.signers else { + throw JWTProviderError.noJWTSigner + } + + return signers + } + + /// Returns the JWT signer with the supplied identifier key + public func assertSigner(kid: String) throws -> Signer { + let signers = try assertSigners() + guard let signer = signers[kid] else { + throw JWTProviderError.noJWTSigner + } + return signer + } +} + +// MARK: JSON Web Key Set (JWKS) URL access + +private let jwtJWKSURLKey = "jwt-provider:jwks-url" + +extension Config { + + /// Returns the JWKS URL + public internal(set) var jwksURL: String? { + get { return storage[jwtJWKSURLKey] as? String } + set { storage[jwtJWKSURLKey] = newValue } + } + + /// Returns the JWKS URL + /// or throws an error if not properly configured + public func assertJWKSURL() throws -> String { + guard let jwksURL = jwksURL else { + throw JWTProviderError.noJWTSigner + } + + return jwksURL + } +} diff --git a/Sources/JWTProvider/Deprecated/Droplet+legacySigner.swift b/Sources/JWTProvider/Deprecated/Droplet+legacySigner.swift new file mode 100644 index 0000000..dfd5ccb --- /dev/null +++ b/Sources/JWTProvider/Deprecated/Droplet+legacySigner.swift @@ -0,0 +1,34 @@ +import JWT +import Vapor + +extension Droplet { + + /// Returns the JWT signer + @available(*, deprecated, message: "Use signers instead.") + public internal(set) var signer: Signer? { + get { return signers?.legacySigner } + set { + if let signer = newValue { + if signers != nil { + signers?.legacySigner = signer + } else { + signers = SignerMap(legacySigner: signer) + } + } else { + signers?.legacySigner = nil + } + } + } + + /// Returns the main JWT signer + /// or throws an error if not properly configured + public func assertSigner() throws -> Signer { + // NB. duplicated code from `signer` is necessary + // to prevent deprecation warning + guard let signer = signers?.legacySigner else { + throw JWTProviderError.noJWTSigner + } + + return signer + } +} diff --git a/Sources/JWTProvider/Deprecated/Provider+legacySigner.swift b/Sources/JWTProvider/Deprecated/Provider+legacySigner.swift new file mode 100644 index 0000000..d146496 --- /dev/null +++ b/Sources/JWTProvider/Deprecated/Provider+legacySigner.swift @@ -0,0 +1,20 @@ +import JWT +import Vapor + +extension Provider { + @available(*, deprecated, message: "Use signers instead.") + public var signer: Signer { + if let legacySigner = signers?.legacySigner { + return legacySigner + } else if let signer = signers?.first?.value { + return signer + } else { + fatalError("Trying to access a legacy signer when none has been specified.") + } + } + + @available(*, deprecated, message: "Use init(signers: SignerMap) instead.") + public convenience init(signer: Signer) { + self.init(signers: SignerMap(legacySigner: signer)) + } +} diff --git a/Sources/JWTProvider/Deprecated/SignerMap+legacySigner.swift b/Sources/JWTProvider/Deprecated/SignerMap+legacySigner.swift new file mode 100644 index 0000000..9bac812 --- /dev/null +++ b/Sources/JWTProvider/Deprecated/SignerMap+legacySigner.swift @@ -0,0 +1,18 @@ +import JWT + +private let jwtLegacySignerKey = "jwt-providers:legacy-signer" + +internal extension Dictionary where Key == String, Value == Signer { + internal init(legacySigner: Signer) { + self = [jwtLegacySignerKey: legacySigner] + } + + var legacySigner: Signer? { + get { + return self[jwtLegacySignerKey] + } + set { + self[jwtLegacySignerKey] = newValue + } + } +} diff --git a/Sources/JWTProvider/Droplet+JWT.swift b/Sources/JWTProvider/Droplet+JWT.swift index 55ccc8e..c5d4079 100644 --- a/Sources/JWTProvider/Droplet+JWT.swift +++ b/Sources/JWTProvider/Droplet+JWT.swift @@ -1,81 +1,48 @@ -import Vapor +// `Signer`s used to be stored on the `Droplet`'s storage. +// They have since been moved to `Config` to enable access +// to signers in `Configinitializable` objects +// (eg. `Providers`). +// This file makes sure the `Signer`s can still be accessed +// through from the Droplet. + import JWT +import Vapor -let jwtLegacySignerKey = "jwt-providers:legacy-signer" +// MARK: Signer map access (via Config) extension Droplet { - @available(*, deprecated, message: "Use signers instead.") - public internal(set) var signer: Signer? { - get { return self.signers?[jwtLegacySignerKey] } - set { - if let signer = newValue { - - if self.signers != nil { - self.signers?[jwtLegacySignerKey] = signer - } else { - self.signers = [jwtLegacySignerKey: signer] - } - } else { - self.signers?[jwtLegacySignerKey] = nil - } - } - } - /// Returns the JWT signer - /// or throws an error if not properly configured - @available(*, deprecated, message: "Use assertSigner(kid:) or assertSigners() instead.") - public func assertSigner() throws -> Signer { - guard let signer = self.signer else { - throw JWTProviderError.noJWTSigner - } - - return signer - } -} - -private let jwtSignersKey = "jwt-provider:signers" - -extension Droplet { + /// Returns the JWT signers public internal(set) var signers: SignerMap? { - get { return storage[jwtSignersKey] as? SignerMap } - set { storage[jwtSignersKey] = newValue } + get { return config.signers } + set { config.signers = newValue } } /// Returns the JWT signers /// or throws an error if not properly configured public func assertSigners() throws -> SignerMap { - guard let signers = self.signers else { - throw JWTProviderError.noJWTSigner - } - - return signers + return try config.assertSigners() } /// Returns the JWT signer with the supplied identifier key public func assertSigner(kid: String) throws -> Signer { - let signers = try assertSigners() - guard let signer = signers[kid] else { - throw JWTProviderError.noJWTSigner - } - return signer + return try config.assertSigner(kid: kid) } } -private let jwtJWKSURLKey = "jwt-provider:jwks-url" +// MARK: JSON Web Key Set (JWKS) URL access (via Config) extension Droplet { + + /// Returns the JWKS URL public internal(set) var jwksURL: String? { - get { return storage[jwtJWKSURLKey] as? String } - set { storage[jwtJWKSURLKey] = newValue } + get { return config.jwksURL } + set { config.jwksURL = newValue } } /// Returns the JWKS URL /// or throws an error if not properly configured public func assertJWKSURL() throws -> String { - guard let jwksURL = self.jwksURL else { - throw JWTProviderError.noJWTSigner - } - - return jwksURL + return try config.assertJWKSURL() } } diff --git a/Sources/JWTProvider/PayloadAuthenticationMiddleware.swift b/Sources/JWTProvider/PayloadAuthenticationMiddleware.swift index 19a701e..12a8273 100644 --- a/Sources/JWTProvider/PayloadAuthenticationMiddleware.swift +++ b/Sources/JWTProvider/PayloadAuthenticationMiddleware.swift @@ -21,7 +21,7 @@ public final class PayloadAuthenticationMiddleware: M _ claims: [Claim] = [], _ userType: U.Type = U.self ) { - self.signers = [jwtLegacySignerKey: signer] + self.signers = SignerMap(legacySigner: signer) self.claims = claims self.jwksURL = nil self.clientFactory = nil @@ -81,7 +81,7 @@ public final class PayloadAuthenticationMiddleware: M // based on kid private func signer(for jwt: JWT) throws -> Signer { - if let legacySigner = self.signers[jwtLegacySignerKey] { + if let legacySigner = signers.legacySigner { // Legacy signer, ignore any kid return legacySigner } diff --git a/Sources/JWTProvider/Provider.swift b/Sources/JWTProvider/Provider.swift index 38b4e2f..76a7551 100644 --- a/Sources/JWTProvider/Provider.swift +++ b/Sources/JWTProvider/Provider.swift @@ -4,34 +4,14 @@ import JWT /// Adds required JWT objects to your application /// like token Signers public final class Provider: Vapor.Provider { - public static let repositoryName = "jwt-provider" - @available(*, deprecated, message: "Use signers instead.") - public var signer: Signer { - - if let legacySigner = self.signers?[jwtLegacySignerKey] { - return legacySigner - } else if let signer = self.signers?.first?.value { - return signer - } else { - fatalError("Trying to access a legacy signer when none has been specified.") - } - } - - public let signers: SignerMap? - public let jwksURL: String? - - @available(*, deprecated, message: "Use init(signers: SignerMap) instead.") - public init(signer: Signer) { - self.signers = [jwtLegacySignerKey: signer] - self.jwksURL = nil - } + public let signers: SignerMap? public init(signers: SignerMap) { - self.signers = signers self.jwksURL = nil + self.signers = signers } public init(jwksURL: String) { @@ -55,17 +35,12 @@ public final class Provider: Vapor.Provider { } } - public func boot(_ config: Config) throws { } - - /// Called to prepare the Droplet. - public func boot(_ drop: Droplet) { - drop.signers = self.signers - drop.jwksURL = self.jwksURL + public func boot(_ config: Config) throws { + config.signers = signers + config.jwksURL = jwksURL } - /// Called before the Droplet begins serving - /// which is @noreturn. - public func beforeRun(_ drop: Droplet) { + public func boot(_ drop: Droplet) throws { } - } + public func beforeRun(_ drop: Droplet) { } } diff --git a/Sources/JWTProvider/SignerFactory.swift b/Sources/JWTProvider/SignerFactory.swift index 6c2808c..c867856 100644 --- a/Sources/JWTProvider/SignerFactory.swift +++ b/Sources/JWTProvider/SignerFactory.swift @@ -1,4 +1,3 @@ -import Foundation import JWT public protocol SignerFactory { diff --git a/Sources/JWTProvider/SignerMap.swift b/Sources/JWTProvider/SignerMap.swift index dbd619c..e56792b 100644 --- a/Sources/JWTProvider/SignerMap.swift +++ b/Sources/JWTProvider/SignerMap.swift @@ -13,7 +13,7 @@ public extension Dictionary where Key == String, Value == Signer { throw SignerMapError.missingKey("keys") } - var map = [String: Signer]() + var map = SignerMap() for key in keys { @@ -50,7 +50,7 @@ public extension Dictionary where Key == String, Value == Signer { // Legacy let signer = try JWTConfigSignerFactory(signerConfig: signerConfig).makeSigner() - self = [jwtLegacySignerKey: signer] + self = SignerMap(legacySigner: signer) } else { return nil diff --git a/Tests/JWTProviderTests/ProviderTests.swift b/Tests/JWTProviderTests/ProviderTests.swift index 03d90bd..491682b 100644 --- a/Tests/JWTProviderTests/ProviderTests.swift +++ b/Tests/JWTProviderTests/ProviderTests.swift @@ -34,7 +34,7 @@ class ProviderTests: XCTestCase { let drop = try Droplet(config: config, middleware: []) XCTAssertNotNil(drop.signers) - XCTAssertNotNil(drop.signers?[jwtLegacySignerKey]) + XCTAssertNotNil(drop.signers?.legacySigner) XCTAssertNil(drop.jwksURL) }