Compare commits
9 Commits
Author | SHA1 | Date |
---|---|---|
![]() |
5257875a5b | |
![]() |
d7db87fcd8 | |
![]() |
079814fe35 | |
![]() |
c852499d8a | |
![]() |
63a01c6c5f | |
![]() |
b1ab5bae59 | |
![]() |
22e244a5e6 | |
![]() |
36ca47cd4a | |
![]() |
2d241ee1c7 |
|
@ -2,3 +2,4 @@
|
||||||
/.build
|
/.build
|
||||||
/Packages
|
/Packages
|
||||||
/*.xcodeproj
|
/*.xcodeproj
|
||||||
|
.swiftpm/
|
203
Package.resolved
203
Package.resolved
|
@ -2,129 +2,75 @@
|
||||||
"object": {
|
"object": {
|
||||||
"pins": [
|
"pins": [
|
||||||
{
|
{
|
||||||
"package": "Auth",
|
"package": "async-http-client",
|
||||||
"repositoryURL": "https://github.com/vapor/auth.git",
|
"repositoryURL": "https://github.com/swift-server/async-http-client.git",
|
||||||
"state": {
|
"state": {
|
||||||
"branch": null,
|
"branch": null,
|
||||||
"revision": "e6f61b403727ec124214beb3e57deff579f31d00",
|
"revision": "51dc885a30ca704b02fa803099b0a9b5b38067b6",
|
||||||
"version": "2.0.4"
|
"version": "1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"package": "Console",
|
"package": "async-kit",
|
||||||
"repositoryURL": "https://github.com/vapor/console.git",
|
"repositoryURL": "https://github.com/vapor/async-kit.git",
|
||||||
"state": {
|
"state": {
|
||||||
"branch": null,
|
"branch": null,
|
||||||
"revision": "74cfbea629d4aac34a97cead2447a6870af1950b",
|
"revision": "d9fd2be441af6d1428b62ab694848396e7072a14",
|
||||||
"version": "3.1.1"
|
"version": "1.0.0-beta.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"package": "Core",
|
"package": "console-kit",
|
||||||
"repositoryURL": "https://github.com/vapor/core.git",
|
"repositoryURL": "https://github.com/vapor/console-kit.git",
|
||||||
"state": {
|
"state": {
|
||||||
"branch": null,
|
"branch": null,
|
||||||
"revision": "c64f63cb21631010952f7abfef719d376ab6a441",
|
"revision": "5b91c2dc93781e4b36cb4c667972670eac90e6e7",
|
||||||
"version": "3.9.1"
|
"version": "4.0.0-beta.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"package": "Crypto",
|
"package": "jwt-kit",
|
||||||
"repositoryURL": "https://github.com/vapor/crypto.git",
|
"repositoryURL": "https://github.com/vapor/jwt-kit.git",
|
||||||
"state": {
|
"state": {
|
||||||
"branch": null,
|
"branch": null,
|
||||||
"revision": "df8eb7d8ae51787b3a0628aa3975e67666da936c",
|
"revision": "0c7e52ab75ddfabad539a4a2a2f9fc003e7700b7",
|
||||||
"version": "3.3.3"
|
"version": null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"package": "DatabaseKit",
|
"package": "multipart-kit",
|
||||||
"repositoryURL": "https://github.com/vapor/database-kit.git",
|
"repositoryURL": "https://github.com/vapor/multipart-kit.git",
|
||||||
"state": {
|
"state": {
|
||||||
"branch": null,
|
"branch": null,
|
||||||
"revision": "8f352c8e66dab301ab9bfef912a01ce1361ba1e4",
|
"revision": "a941d7a1d685c83df09077f6190808ff2a7f4dce",
|
||||||
"version": "1.3.3"
|
"version": "4.0.0-beta.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"package": "Fluent",
|
"package": "open-crypto",
|
||||||
"repositoryURL": "https://github.com/vapor/fluent.git",
|
"repositoryURL": "https://github.com/vapor/open-crypto.git",
|
||||||
"state": {
|
"state": {
|
||||||
"branch": null,
|
"branch": null,
|
||||||
"revision": "b915c321c6f9e83743ee5efa35a30895e1b02e51",
|
"revision": "06d26edb8e28295bb7103b4f950d5ea58d634c1b",
|
||||||
"version": "3.2.0"
|
"version": "4.0.0-alpha.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"package": "FluentSQLite",
|
"package": "routing-kit",
|
||||||
"repositoryURL": "https://github.com/vapor/fluent-sqlite.git",
|
"repositoryURL": "https://github.com/vapor/routing-kit.git",
|
||||||
"state": {
|
"state": {
|
||||||
"branch": null,
|
"branch": null,
|
||||||
"revision": "c32f5bda84bf4ea691d19afe183d40044f579e11",
|
"revision": "6c7f4b471f9662d05045d82e64e22d5572a16a82",
|
||||||
"version": "3.0.0"
|
"version": "4.0.0-alpha.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"package": "HTTP",
|
"package": "swift-log",
|
||||||
"repositoryURL": "https://github.com/vapor/http.git",
|
"repositoryURL": "https://github.com/apple/swift-log.git",
|
||||||
"state": {
|
"state": {
|
||||||
"branch": null,
|
"branch": null,
|
||||||
"revision": "3808ed0401379b6e9f4a053f03090ea9d658caa9",
|
"revision": "74d7b91ceebc85daf387ebb206003f78813f71aa",
|
||||||
"version": "3.2.1"
|
"version": "1.2.0"
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"package": "JWT",
|
|
||||||
"repositoryURL": "https://github.com/vapor/jwt.git",
|
|
||||||
"state": {
|
|
||||||
"branch": null,
|
|
||||||
"revision": "2e225c722bf26407c1c4bd11d341e48759f46095",
|
|
||||||
"version": "3.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"package": "Multipart",
|
|
||||||
"repositoryURL": "https://github.com/vapor/multipart.git",
|
|
||||||
"state": {
|
|
||||||
"branch": null,
|
|
||||||
"revision": "f919a01c4d10a281d6236a21b0b1d1759a72b8eb",
|
|
||||||
"version": "3.0.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"package": "Routing",
|
|
||||||
"repositoryURL": "https://github.com/vapor/routing.git",
|
|
||||||
"state": {
|
|
||||||
"branch": null,
|
|
||||||
"revision": "626190ddd2bd9f967743b60ba6adaf90bbd2651c",
|
|
||||||
"version": "3.0.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"package": "Service",
|
|
||||||
"repositoryURL": "https://github.com/vapor/service.git",
|
|
||||||
"state": {
|
|
||||||
"branch": null,
|
|
||||||
"revision": "fa5b5de62bd68bcde9a69933f31319e46c7275fb",
|
|
||||||
"version": "1.0.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"package": "SQL",
|
|
||||||
"repositoryURL": "https://github.com/vapor/sql.git",
|
|
||||||
"state": {
|
|
||||||
"branch": null,
|
|
||||||
"revision": "50eaeb8f52a1ce63f1ff3880e1114dd8757a78a6",
|
|
||||||
"version": "2.3.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"package": "SQLite",
|
|
||||||
"repositoryURL": "https://github.com/vapor/sqlite.git",
|
|
||||||
"state": {
|
|
||||||
"branch": null,
|
|
||||||
"revision": "314d9cd21165bcf14215e336a23ff8214f40e411",
|
|
||||||
"version": "3.2.1"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -132,8 +78,26 @@
|
||||||
"repositoryURL": "https://github.com/apple/swift-nio.git",
|
"repositoryURL": "https://github.com/apple/swift-nio.git",
|
||||||
"state": {
|
"state": {
|
||||||
"branch": null,
|
"branch": null,
|
||||||
"revision": "ba7970fe396e8198b84c6c1b44b38a1d4e2eb6bd",
|
"revision": "ff01888051cd7efceb1bf8319c1dd3986c4bf6fc",
|
||||||
"version": "1.14.1"
|
"version": "2.10.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "swift-nio-extras",
|
||||||
|
"repositoryURL": "https://github.com/apple/swift-nio-extras.git",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "53808818c2015c45247cad74dc05c7a032c96a2f",
|
||||||
|
"version": "1.3.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "swift-nio-http2",
|
||||||
|
"repositoryURL": "https://github.com/apple/swift-nio-http2.git",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "867259332c45d5405efed844f7b3997ebfa94167",
|
||||||
|
"version": "1.7.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -141,71 +105,26 @@
|
||||||
"repositoryURL": "https://github.com/apple/swift-nio-ssl.git",
|
"repositoryURL": "https://github.com/apple/swift-nio-ssl.git",
|
||||||
"state": {
|
"state": {
|
||||||
"branch": null,
|
"branch": null,
|
||||||
"revision": "0f3999f3e3c359cc74480c292644c3419e44a12f",
|
"revision": "ccf96bbe65ecc7c1558ab0dba7ffabdea5c1d31f",
|
||||||
"version": "1.4.0"
|
"version": "2.4.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"package": "swift-nio-ssl-support",
|
"package": "vapor",
|
||||||
"repositoryURL": "https://github.com/apple/swift-nio-ssl-support.git",
|
|
||||||
"state": {
|
|
||||||
"branch": null,
|
|
||||||
"revision": "c02eec4e0e6d351cd092938cf44195a8e669f555",
|
|
||||||
"version": "1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"package": "swift-nio-zlib-support",
|
|
||||||
"repositoryURL": "https://github.com/apple/swift-nio-zlib-support.git",
|
|
||||||
"state": {
|
|
||||||
"branch": null,
|
|
||||||
"revision": "37760e9a52030bb9011972c5213c3350fa9d41fd",
|
|
||||||
"version": "1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"package": "TemplateKit",
|
|
||||||
"repositoryURL": "https://github.com/vapor/template-kit.git",
|
|
||||||
"state": {
|
|
||||||
"branch": null,
|
|
||||||
"revision": "51405c83e95e8adb09565278a5e9b959c605e56c",
|
|
||||||
"version": "1.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"package": "URLEncodedForm",
|
|
||||||
"repositoryURL": "https://github.com/vapor/url-encoded-form.git",
|
|
||||||
"state": {
|
|
||||||
"branch": null,
|
|
||||||
"revision": "82d8d63bdb76b6dd8febe916c639ab8608dbbaed",
|
|
||||||
"version": "1.0.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"package": "Validation",
|
|
||||||
"repositoryURL": "https://github.com/vapor/validation.git",
|
|
||||||
"state": {
|
|
||||||
"branch": null,
|
|
||||||
"revision": "4de213cf319b694e4ce19e5339592601d4dd3ff6",
|
|
||||||
"version": "2.1.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"package": "Vapor",
|
|
||||||
"repositoryURL": "https://github.com/vapor/vapor.git",
|
"repositoryURL": "https://github.com/vapor/vapor.git",
|
||||||
"state": {
|
"state": {
|
||||||
"branch": null,
|
"branch": null,
|
||||||
"revision": "c86ada59b31c69f08a6abd4f776537cba48d5df6",
|
"revision": "3d8bbd5eb2b556debbb51da8ac7357f34b7b0a8e",
|
||||||
"version": "3.3.0"
|
"version": "4.0.0-beta.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"package": "WebSocket",
|
"package": "websocket-kit",
|
||||||
"repositoryURL": "https://github.com/vapor/websocket.git",
|
"repositoryURL": "https://github.com/vapor/websocket-kit.git",
|
||||||
"state": {
|
"state": {
|
||||||
"branch": null,
|
"branch": null,
|
||||||
"revision": "d85e5b6dce4d04065865f77385fc3324f98178f6",
|
"revision": "66c0ea58398f055b5a0d92b0d5f4c32ef0c02eeb",
|
||||||
"version": "1.1.2"
|
"version": "2.0.0-beta.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,17 +1,22 @@
|
||||||
// swift-tools-version:4.0
|
// swift-tools-version:5.0
|
||||||
|
|
||||||
import PackageDescription
|
import PackageDescription
|
||||||
|
|
||||||
let package = Package(
|
let package = Package(
|
||||||
name: "JWTVapor",
|
name: "JWTVapor",
|
||||||
|
platforms: [
|
||||||
|
.macOS(.v10_14)
|
||||||
|
],
|
||||||
products: [
|
products: [
|
||||||
.library(name: "JWTVapor", targets: ["JWTVapor"]),
|
.library(name: "JWTVapor", targets: ["JWTVapor"]),
|
||||||
],
|
],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
.package(url: "https://github.com/vapor/vapor.git", from: "3.3.0"),
|
.package(url: "https://github.com/vapor/vapor.git", from: "4.0.0-beta"),
|
||||||
.package(url: "https://github.com/vapor/jwt.git", from: "3.0.0"),
|
.package(url: "https://github.com/vapor/jwt-kit.git", .revision("0c7e52ab75ddfabad539a4a2a2f9fc003e7700b7")),
|
||||||
.package(url: "https://github.com/vapor/auth.git", from: "2.0.4")
|
// .package(url: "https://github.com/vapor/auth.git", .branch("master"))
|
||||||
],
|
],
|
||||||
targets: [
|
targets: [
|
||||||
.target(name: "JWTVapor", dependencies: ["Vapor", "Authentication", "JWT"]),
|
.target(name: "JWTVapor", dependencies: ["Vapor", "JWTKit"]),
|
||||||
.testTarget(name: "JWTVaporTests", dependencies: ["JWTVapor"]),
|
.testTarget(name: "JWTVaporTests", dependencies: ["JWTVapor"]),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
//
|
||||||
|
// File.swift
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by Ralph Küpper on 12/10/19.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Vapor
|
||||||
|
@_exported import JWTKit
|
||||||
|
|
||||||
|
class JWTConfig {
|
||||||
|
var algorithm: JWTAlgorithm
|
||||||
|
var header: JWTHeader?
|
||||||
|
var secretVar: String
|
||||||
|
var publicVar: String
|
||||||
|
|
||||||
|
public init(algorithm: JWTAlgorithm, header: JWTHeader? = nil, secretVar: String? = nil, publicVar: String? = nil) {
|
||||||
|
self.algorithm = algorithm
|
||||||
|
self.header = header
|
||||||
|
self.secretVar = secretVar ?? "JWT_SECRET"
|
||||||
|
self.publicVar = publicVar ?? "JWT_PUBLIC"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Application {
|
||||||
|
|
||||||
|
private struct JWTServiceKey: StorageKey {
|
||||||
|
typealias Value = JWTService
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct JWTConfigKey: StorageKey {
|
||||||
|
typealias Value = JWTConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
var jwtConfig: JWTConfig {
|
||||||
|
if let existing = self.storage[JWTConfigKey.self] {
|
||||||
|
return existing
|
||||||
|
} else {
|
||||||
|
let config = JWTConfig(algorithm: .rsa(.sha256))
|
||||||
|
self.storage[JWTConfigKey.self] = config
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var jwtService: JWTService {
|
||||||
|
if let existing = self.storage[JWTServiceKey.self] {
|
||||||
|
return existing
|
||||||
|
} else {
|
||||||
|
var new:JWTService
|
||||||
|
guard let publicKey = Environment.get(self.jwtConfig.publicVar) else {
|
||||||
|
print("No value was found at the given public key environmen '\(self.jwtConfig.publicVar)'")
|
||||||
|
exit(0)
|
||||||
|
}
|
||||||
|
switch self.jwtConfig.algorithm {
|
||||||
|
case let .rsa(alorithm): new = RSAService(n: publicKey, e: "AQAB", d: Environment.get(self.jwtConfig.secretVar), header: self.jwtConfig.header, algorithm: alorithm)
|
||||||
|
case let .hmac(alorithm): new = HMACService(key: publicKey, header: self.jwtConfig.header, algorithm: alorithm)
|
||||||
|
case let .cert(alorithm): new = CertService(certificate: Data(publicKey.utf8), header: self.jwtConfig.header, algorithm: alorithm)
|
||||||
|
case let .custom(closure): new = closure(self.jwtConfig.header, publicKey, Environment.get(self.jwtConfig.secretVar))
|
||||||
|
}
|
||||||
|
|
||||||
|
self.storage[JWTServiceKey.self] = new
|
||||||
|
return new
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,19 +1,23 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import Crypto
|
import JWTKit
|
||||||
import JWT
|
|
||||||
|
|
||||||
public final class CertService: JWTService {
|
public final class CertService: JWTService {
|
||||||
public var signer: JWTSigner
|
public var signer: JWTSigner
|
||||||
public var header: JWTHeader?
|
public var header: JWTHeader?
|
||||||
|
|
||||||
public init(certificate: String, header: JWTHeader? = nil, algorithm: DigestAlgorithm = .sha256)throws {
|
public init(certificate: Data, header: JWTHeader? = nil, algorithm: DigestAlgorithm = .sha256) {
|
||||||
let key = try RSAKey.public(certificate: certificate)
|
do {
|
||||||
|
let key = try RSAKey.public(pem: certificate)
|
||||||
switch algorithm {
|
|
||||||
case .sha256: self.signer = JWTSigner.rs256(key: key)
|
switch algorithm {
|
||||||
case .sha384: self.signer = JWTSigner.rs384(key: key)
|
case .sha256: self.signer = JWTSigner.rs256(key: key)
|
||||||
case .sha512: self.signer = JWTSigner.rs512(key: key)
|
case .sha384: self.signer = JWTSigner.rs384(key: key)
|
||||||
default: throw JWTProviderError(identifier: "badRSAAlgorithm", reason: "RSA signing requires SHA256, SHA384, or SHA512 algorithm", status: .internalServerError)
|
case .sha512: self.signer = JWTSigner.rs512(key: key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
print("We cannot run this service without a key.")
|
||||||
|
exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.header = header
|
self.header = header
|
||||||
|
|
|
@ -1,17 +1,15 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import Crypto
|
import JWTKit
|
||||||
import JWT
|
|
||||||
|
|
||||||
public final class HMACService: JWTService {
|
public final class HMACService: JWTService {
|
||||||
public let signer: JWTSigner
|
public let signer: JWTSigner
|
||||||
public let header: JWTHeader?
|
public let header: JWTHeader?
|
||||||
|
|
||||||
public init(key: String, header: JWTHeader? = nil, algorithm: DigestAlgorithm = .sha256)throws {
|
public init(key: String, header: JWTHeader? = nil, algorithm: DigestAlgorithm = .sha256) {
|
||||||
switch algorithm {
|
switch algorithm {
|
||||||
case .sha256: self.signer = JWTSigner.hs256(key: Data(key.utf8))
|
case .sha256: self.signer = JWTSigner.hs256(key: Data(key.utf8))
|
||||||
case .sha384: self.signer = JWTSigner.hs384(key: Data(key.utf8))
|
case .sha384: self.signer = JWTSigner.hs384(key: Data(key.utf8))
|
||||||
case .sha512: self.signer = JWTSigner.hs512(key: Data(key.utf8))
|
case .sha512: self.signer = JWTSigner.hs512(key: Data(key.utf8))
|
||||||
default: throw JWTProviderError(identifier: "badHMACAlgorithm", reason: "HMAC signing requires SHA256, SHA384, or SHA512 algorithm", status: .internalServerError)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.header = header
|
self.header = header
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import Foundation
|
|
||||||
import Vapor
|
import Vapor
|
||||||
|
|
||||||
/// Configuration required by the `JWKSService`.
|
/// Configuration required by the `JWKSService`.
|
||||||
|
@ -13,12 +12,12 @@ import Vapor
|
||||||
|
|
||||||
/// `JWKSConfig` is a `Service` which gets created by the required container and is made available
|
/// `JWKSConfig` is a `Service` which gets created by the required container and is made available
|
||||||
/// to `JWKSService` in its `makeService` method.
|
/// to `JWKSService` in its `makeService` method.
|
||||||
public struct JWKSConfig: Service {
|
public struct JWKSConfig {
|
||||||
public let jwks: String
|
public let jwks: URI
|
||||||
public let following: String
|
public let following: String
|
||||||
public let password: String?
|
public let password: String?
|
||||||
|
|
||||||
public init(jwks: String, following: String, password: String? = nil) {
|
public init(jwks: URI, following: String, password: String? = nil) {
|
||||||
self.jwks = jwks
|
self.jwks = jwks
|
||||||
self.following = following
|
self.following = following
|
||||||
self.password = password
|
self.password = password
|
||||||
|
|
|
@ -4,19 +4,28 @@ import Vapor
|
||||||
/// Content (Codeable) model to read the response obtained after hitting the url specified in the
|
/// Content (Codeable) model to read the response obtained after hitting the url specified in the
|
||||||
/// `jwks` property of `JWKSConfig`.
|
/// `jwks` property of `JWKSConfig`.
|
||||||
public struct JWKSDocumentRequest: Content {
|
public struct JWKSDocumentRequest: Content {
|
||||||
let jwksUrl: String
|
let jwksUrl: URI
|
||||||
|
|
||||||
public init(from decoder: Decoder) throws {
|
public init(from decoder: Decoder) throws {
|
||||||
// Get the container
|
|
||||||
let container = try decoder.container(keyedBy: JWKSDocumentCodingKey.self)
|
let container = try decoder.container(keyedBy: JWKSDocumentCodingKey.self)
|
||||||
|
|
||||||
// Read the coding key passed to the decoder as part of userinfo
|
// Read the coding key passed to the decoder as part of userinfo
|
||||||
guard let jwksUrlKey = decoder.userInfo[JWKSService.codingUserInfoKey] as? JWKSDocumentCodingKey else {
|
guard let jwksUrlKey = decoder.userInfo[JWKSService.codingUserInfoKey] as? JWKSDocumentCodingKey else {
|
||||||
throw DecodingError.dataCorrupted(.init(codingPath: [], debugDescription: "Failed"))
|
throw DecodingError.dataCorrupted(.init(codingPath: [], debugDescription: "Failed"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the jwks url from the json response.
|
self.jwksUrl = try URI(string: container.decode(String.self, forKey: jwksUrlKey))
|
||||||
self.jwksUrl = try container.decode(String.self, forKey: jwksUrlKey)
|
}
|
||||||
|
|
||||||
|
public func encode(to encoder: Encoder) throws {
|
||||||
|
var container = encoder.container(keyedBy: JWKSDocumentCodingKey.self)
|
||||||
|
|
||||||
|
// Read the coding key passed to the decoder as part of userinfo
|
||||||
|
guard let jwksUrlKey = encoder.userInfo[JWKSService.codingUserInfoKey] as? JWKSDocumentCodingKey else {
|
||||||
|
throw DecodingError.dataCorrupted(.init(codingPath: [], debugDescription: "Failed"))
|
||||||
|
}
|
||||||
|
|
||||||
|
try container.encode(self.jwksUrl.string, forKey: jwksUrlKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,10 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import Vapor
|
import Vapor
|
||||||
|
|
||||||
public struct JWKSService: ServiceType {
|
public struct JWKSService {
|
||||||
|
static let factory: (Application) throws -> JWKSService = { _ in fatalError() }
|
||||||
/// config: `JWKSConfig` instance which contains all the configurations required by `JWKSService`.
|
static let requestFactory: (Request) throws -> JWKSService = { _ in fatalError() }
|
||||||
private let config: JWKSConfig
|
|
||||||
/// The service container in which the Services are to be loaded.
|
|
||||||
private let container: Container
|
|
||||||
/// The client we need to pull the jwks.
|
|
||||||
private let client: Client
|
|
||||||
|
|
||||||
/// Create a key that gets passed as a key in the `JSONDecoder.userInfo` dictionary and its value is
|
/// Create a key that gets passed as a key in the `JSONDecoder.userInfo` dictionary and its value is
|
||||||
/// value that the user passes in `JWKSConfig`'s `following`. The value passed in the `following`
|
/// value that the user passes in `JWKSConfig`'s `following`. The value passed in the `following`
|
||||||
/// property is converted into a `CodingKey` type before passing in `JSONDecoder`'s userInfo.
|
/// property is converted into a `CodingKey` type before passing in `JSONDecoder`'s userInfo.
|
||||||
|
@ -18,53 +13,53 @@ public struct JWKSService: ServiceType {
|
||||||
///
|
///
|
||||||
/// [codingUserInfoKey: JWKSDocumentCodingKey(stringValue: self.config.following)]
|
/// [codingUserInfoKey: JWKSDocumentCodingKey(stringValue: self.config.following)]
|
||||||
static let codingUserInfoKey = CodingUserInfoKey(rawValue: "jwksUrlKey")!
|
static let codingUserInfoKey = CodingUserInfoKey(rawValue: "jwksUrlKey")!
|
||||||
|
|
||||||
public init(config: JWKSConfig, container: Container) {
|
|
||||||
|
/// config: `JWKSConfig` instance which contains all the configurations required by `JWKSService`.
|
||||||
|
private let config: JWKSConfig
|
||||||
|
|
||||||
|
/// The client we need to pull the jwks.
|
||||||
|
private let client: Client
|
||||||
|
|
||||||
|
public init(config: JWKSConfig, client: Client) {
|
||||||
self.config = config
|
self.config = config
|
||||||
self.container = container
|
self.client = client
|
||||||
self.client = try! container.make(Client.self)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finds and returns a RSAService that matches the provided `tid` from the list of JWKS.
|
/// Finds and returns a RSAService that matches the provided `tid` from the list of JWKS.
|
||||||
/// The `tid` is obtained from the JWT Header of the incoming request, if the signing mechanism used is RSA.
|
/// The `tid` is obtained from the JWT Header of the incoming request, if the signing mechanism used is RSA.
|
||||||
public func rsaService(forKey tid: String) throws -> Future<RSAService> {
|
public func rsaService(forKey tid: String) throws -> EventLoopFuture<RSAService> {
|
||||||
return client.get(self.config.jwks).flatMap { response throws -> Future<JWKSDocumentRequest> in
|
return self.client.get(self.config.jwks).flatMap { response -> EventLoopFuture<ClientResponse> in
|
||||||
|
|
||||||
/// Create a JSONDecoder in which the codingKey could be passed as part of userInfo.
|
|
||||||
let jsonDecoder = JSONDecoder()
|
let jsonDecoder = JSONDecoder()
|
||||||
let jwksUrlKey = JWKSDocumentCodingKey(stringValue: self.config.following)!
|
let jwksUrlKey = JWKSDocumentCodingKey(stringValue: self.config.following)!
|
||||||
jsonDecoder.userInfo = [JWKSService.codingUserInfoKey: jwksUrlKey]
|
jsonDecoder.userInfo = [JWKSService.codingUserInfoKey: jwksUrlKey]
|
||||||
/// return the JSON response.
|
|
||||||
return try response.content.decode(json: JWKSDocumentRequest.self, using: jsonDecoder)
|
do {
|
||||||
|
let request = try response.content.decode(JWKSDocumentRequest.self, using: jsonDecoder)
|
||||||
}.flatMap { jwksDocumentRequest throws -> Future<Response> in
|
return self.client.get(request.jwksUrl)
|
||||||
/// Make a request and return the JWKS file.
|
} catch let error {
|
||||||
return self.client.get(jwksDocumentRequest.jwksUrl)
|
let eventLoop = self.client.eventLoopGroup.next()
|
||||||
|
return eventLoop.future(error: error)
|
||||||
}.flatMap { response throws -> Future<JWKSKeys> in
|
}
|
||||||
|
}.flatMapThrowing { response throws -> RSAService in
|
||||||
/// Read the entire list of JWKS Keys.
|
/// Read the entire list of JWKS Keys.
|
||||||
return try response.content.decode(JWKSKeys.self)
|
let jwksKeys = try response.content.decode(JWKSKeys.self)
|
||||||
|
|
||||||
}.map { jwksKeys throws -> JWKSKey in
|
|
||||||
/// Search for JWKSKey that corresponds to the provided `tid`
|
/// Search for JWKSKey that corresponds to the provided `tid`
|
||||||
guard let matchingJWKSKey = jwksKeys.keys.filter({ $0.kid == tid }).first else {
|
guard let matchingJWKSKey = jwksKeys.keys.filter({ $0.kid == tid }).first else {
|
||||||
throw JWTProviderError(identifier: "invalidJWKSKeys", reason: "No matching key found in JWKS file", status: .internalServerError)
|
throw JWTProviderError(identifier: "invalidJWKSKeys", reason: "No matching key found in JWKS file", status: .internalServerError)
|
||||||
}
|
}
|
||||||
return matchingJWKSKey
|
|
||||||
|
return try RSAService(n: matchingJWKSKey.n, e: matchingJWKSKey.e, d: matchingJWKSKey.d)
|
||||||
}.map { jwksKey throws -> RSAService in
|
|
||||||
/// Create the RSAService using the JWKSKey.
|
|
||||||
return try RSAService(n: jwksKey.n, e: jwksKey.e, d: jwksKey.d)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func makeService(for worker: Container) throws -> JWKSService {
|
// public static func makeService(for worker: Container) throws -> JWKSService {
|
||||||
// Load the required services.
|
// // Load the required services.
|
||||||
let config = try worker.make(JWKSConfig.self)
|
// let config = try worker.make(JWKSConfig.self)
|
||||||
return JWKSService(config: config, container: worker)
|
// return JWKSService(config: config, container: worker)
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
//
|
||||||
|
// File.swift
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by Ralph Küpper on 12/10/19.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Vapor
|
||||||
|
|
||||||
|
|
||||||
|
public enum JWTAlgorithm {
|
||||||
|
case rsa(DigestAlgorithm = .sha256)
|
||||||
|
case hmac(DigestAlgorithm = .sha256)
|
||||||
|
case cert(DigestAlgorithm = .sha256)
|
||||||
|
case custom((JWTHeader?, String, String?) -> JWTService)
|
||||||
|
}
|
|
@ -1,42 +1,47 @@
|
||||||
import Command
|
import ConsoleKit
|
||||||
import Console
|
import Foundation
|
||||||
|
|
||||||
public final class NewJWTCommand<Payload>: Command where Payload: JWTPayload {
|
public final class NewJWTCommand<Payload>: Command where Payload: JWTPayload {
|
||||||
public var arguments: [CommandArgument] = []
|
public struct Signature: CommandSignature {
|
||||||
|
static var jsonHelp: String { """
|
||||||
|
The payload for the generated token.
|
||||||
|
If this flag is not use, the payload passed into the command's initializer is used.
|
||||||
|
""" }
|
||||||
|
|
||||||
|
@Option(name: "json", help: Signature.jsonHelp) var json: Data?
|
||||||
|
|
||||||
|
public init() { }
|
||||||
|
}
|
||||||
|
|
||||||
public var options: [CommandOption] = [
|
public let help: String = "Creates a JWT token for debugging purposes."
|
||||||
CommandOption.value(name: "json", help: [
|
|
||||||
"The payload for the generated token",
|
|
||||||
"If this flag is not use, the payload passed into the command's initializer is used"
|
|
||||||
])
|
|
||||||
]
|
|
||||||
|
|
||||||
public var help: [String] = ["Creates a JWT token for debugging purposes."]
|
|
||||||
|
|
||||||
internal let payloadCreator: () -> Payload
|
internal let payloadCreator: () -> Payload
|
||||||
|
internal let signer: JWTService
|
||||||
|
|
||||||
public init(payload: Payload) {
|
public init(payload: Payload, signer: JWTService) {
|
||||||
self.payloadCreator = { payload }
|
self.payloadCreator = { payload }
|
||||||
|
self.signer = signer
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(_ handler: @escaping () -> Payload) {
|
public init(signer: JWTService, _ handler: @escaping () -> Payload) {
|
||||||
self.payloadCreator = handler
|
self.payloadCreator = handler
|
||||||
|
self.signer = signer
|
||||||
}
|
}
|
||||||
|
|
||||||
public func run(using context: CommandContext) throws -> EventLoopFuture<Void> {
|
public func run(using context: CommandContext, signature: Signature) throws {
|
||||||
let signer = try context.container.make(JWTService.self)
|
let payload: Payload =
|
||||||
let payload: Payload
|
try signature.json.map { try JSONDecoder().decode(Payload.self, from: $0) } ??
|
||||||
if let json = context.options["json"]?.data(using: .utf8) {
|
self.payloadCreator()
|
||||||
payload = try JSONDecoder().decode(Payload.self, from: json)
|
|
||||||
} else {
|
|
||||||
payload = self.payloadCreator()
|
|
||||||
}
|
|
||||||
|
|
||||||
let token = try signer.sign(payload)
|
let token = try signer.sign(payload)
|
||||||
|
|
||||||
context.console.output("JWT Token: ", style: .info, newLine: false)
|
context.console.output("JWT Token: ", style: .info, newLine: false)
|
||||||
context.console.output(token, style: .plain, newLine: true)
|
context.console.output(token, style: .plain, newLine: true)
|
||||||
|
}
|
||||||
return context.container.future()
|
}
|
||||||
|
|
||||||
|
extension Data: LosslessStringConvertible {
|
||||||
|
public init?(_ description: String) {
|
||||||
|
self.init(description.utf8)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,60 +0,0 @@
|
||||||
import Vapor
|
|
||||||
import Service
|
|
||||||
@_exported import JWT
|
|
||||||
|
|
||||||
public class JWTProvider: Vapor.Provider {
|
|
||||||
public static var repositoryName: String = "JWTProvider"
|
|
||||||
|
|
||||||
public let serviceBuilder: (String, String?)throws -> JWTService
|
|
||||||
|
|
||||||
public init (serviceBuilder: @escaping (String, String?)throws -> JWTService) {
|
|
||||||
self.serviceBuilder = serviceBuilder
|
|
||||||
}
|
|
||||||
|
|
||||||
public init(_ serviceType: JWTService.Type)throws {
|
|
||||||
self.serviceBuilder = { key, d in
|
|
||||||
switch serviceType {
|
|
||||||
case is RSAService.Type:
|
|
||||||
return try RSAService(n: key, e: "AQAB", d: d)
|
|
||||||
case is HMACService.Type: return try HMACService(key: key)
|
|
||||||
case is CertService.Type: return try CertService(certificate: key)
|
|
||||||
default:
|
|
||||||
throw JWTProviderError(
|
|
||||||
identifier: "unsupportedJWTService",
|
|
||||||
reason: "The registered JWT service is not supported. Maybe you created a custom service?",
|
|
||||||
status: .internalServerError
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func register(_ services: inout Services) throws {
|
|
||||||
|
|
||||||
/// Registering a JWKSService.
|
|
||||||
services.register(JWKSService.self)
|
|
||||||
|
|
||||||
let d = Environment.get("JWT_SECRET")
|
|
||||||
guard let key = Environment.get("JWT_PUBLIC") else {
|
|
||||||
throw JWTProviderError(identifier: "noPublicFound", reason: "No 'JWT_PUBLIC' environment variable was found", status: .internalServerError)
|
|
||||||
}
|
|
||||||
|
|
||||||
let jwtService = try serviceBuilder(key, d)
|
|
||||||
if let rsaService = jwtService as? RSAService {
|
|
||||||
services.register(rsaService, as: JWTService.self)
|
|
||||||
} else if let hmacService = jwtService as? HMACService {
|
|
||||||
services.register(hmacService, as: JWTService.self)
|
|
||||||
} else if let certService = jwtService as? CertService {
|
|
||||||
services.register(certService, as: JWTService.self)
|
|
||||||
} else {
|
|
||||||
throw JWTProviderError(
|
|
||||||
identifier: "unsupportedJWTService",
|
|
||||||
reason: "The registered JWT service is not supported. Maybe you created a custom service?",
|
|
||||||
status: .internalServerError
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func boot(_ worker: Container) throws {}
|
|
||||||
|
|
||||||
public func didBoot(_ worker: Container) throws -> EventLoopFuture<Void> { return Future.map(on: worker, { () }) }
|
|
||||||
}
|
|
|
@ -1,8 +1,11 @@
|
||||||
import Debugging
|
|
||||||
import Vapor
|
import Vapor
|
||||||
|
|
||||||
public struct JWTProviderError: Debuggable, AbortError, Error {
|
public struct JWTProviderError: AbortError, Error {
|
||||||
public let identifier: String
|
public let identifier: String
|
||||||
public let reason: String
|
public let reason: String
|
||||||
public let status: HTTPResponseStatus
|
public let status: HTTPResponseStatus
|
||||||
|
|
||||||
|
public var description: String {
|
||||||
|
return "JWTProviderError.\(self.identifier): \(self.status) \(self.reason)"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import Service
|
import JWTKit
|
||||||
import Crypto
|
|
||||||
import JWT
|
|
||||||
|
|
||||||
public protocol JWTService: Service {
|
public protocol JWTService {
|
||||||
var signer: JWTSigner { get }
|
var signer: JWTSigner { get }
|
||||||
var header: JWTHeader? { get }
|
var header: JWTHeader? { get }
|
||||||
|
|
||||||
|
@ -11,33 +9,48 @@ public protocol JWTService: Service {
|
||||||
func verify(_ token: Data)throws -> Bool
|
func verify(_ token: Data)throws -> Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum DigestAlgorithm {
|
||||||
|
case sha256, sha384, sha512
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
extension UInt8 {
|
||||||
|
static let period: UInt8 = 46
|
||||||
|
}
|
||||||
|
|
||||||
|
extension JWTSigner {
|
||||||
|
public func verify<S, H, P>(_ signature: S, header: H, payload: P) throws -> Bool
|
||||||
|
where S: DataProtocol, H: DataProtocol, P: DataProtocol
|
||||||
|
{
|
||||||
|
let message: Data = Data(header) + Data([.period]) + Data(payload)
|
||||||
|
guard let signature = Data(base64Encoded: Data(signature)) else {
|
||||||
|
throw JWTError.malformedToken
|
||||||
|
}
|
||||||
|
return try algorithm.verify(signature, signs: message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension JWTService {
|
extension JWTService {
|
||||||
public func sign<T>(_ payload: T) throws -> String where T : JWTPayload {
|
public func sign<T>(_ payload: T) throws -> String where T : JWTPayload {
|
||||||
guard let header = self.header else {
|
guard let header = self.header else {
|
||||||
throw JWTProviderError(identifier: "noHeader", reason: "Cannot sign token with a header", status: .internalServerError)
|
throw JWTProviderError(identifier: "noHeader", reason: "Cannot sign token with a header", status: .internalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
let jwt = JWT(header: header, payload: payload)
|
let jwt = JWT(header: header, payload: payload)
|
||||||
let data = try signer.sign(jwt)
|
let bytes = try jwt.sign(using: self.signer)
|
||||||
guard let token = String(data: data, encoding: .utf8) else {
|
return String(decoding: bytes, as: UTF8.self)
|
||||||
throw JWTProviderError(
|
|
||||||
identifier: "tokenEncodingFailed",
|
|
||||||
reason: "Converting access token data to a string failed with UTF-8 encoding",
|
|
||||||
status: .internalServerError
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return token
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func verify(_ token: Data)throws -> Bool {
|
public func verify(_ token: Data)throws -> Bool {
|
||||||
let parts = token.split(separator: .period)
|
let parts = token.split(separator: .period)
|
||||||
guard parts.count == 3 else {
|
guard parts.count == 3 else {
|
||||||
throw JWTError(identifier: "invalidJWT", reason: "Malformed JWT")
|
throw JWTError.malformedToken
|
||||||
}
|
}
|
||||||
|
|
||||||
let header = Data(parts[0])
|
let header = Data(parts[0])
|
||||||
let payload = Data(parts[1])
|
let payload = Data(parts[1])
|
||||||
let signature = Data(parts[2])
|
let signature = Data(parts[2])
|
||||||
|
|
||||||
return try self.signer.verify(signature, header: header, payload: payload)
|
return try self.signer.verify(signature, header: header, payload: payload)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,32 +1,41 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import Crypto
|
import CJWTKitCrypto
|
||||||
import JWT
|
import JWTKit
|
||||||
|
|
||||||
public final class RSAService: JWTService {
|
public final class RSAService: JWTService {
|
||||||
|
public enum KeyType {
|
||||||
|
case `public`, `private`
|
||||||
|
}
|
||||||
|
|
||||||
public let signer: JWTSigner
|
public let signer: JWTSigner
|
||||||
public let header: JWTHeader?
|
public let header: JWTHeader?
|
||||||
|
|
||||||
public init(pem: String, header: JWTHeader? = nil, type: RSAKeyType = .private, algorithm: DigestAlgorithm = .sha256)throws {
|
public init(pem: Data, header: JWTHeader? = nil, type: KeyType = .private, algorithm: DigestAlgorithm = .sha256) {
|
||||||
let key: RSAKey
|
let key: RSAKey
|
||||||
switch type {
|
do {
|
||||||
case .public: key = try RSAKey.public(pem: pem)
|
switch type {
|
||||||
case .private: key = try RSAKey.private(pem: pem)
|
case .public: key = try RSAKey.public(pem: pem)
|
||||||
|
case .private: key = try RSAKey.private(pem: pem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
print("We need a key to run this service.")
|
||||||
|
exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch algorithm {
|
switch algorithm {
|
||||||
case .sha256: self.signer = JWTSigner.rs256(key: key)
|
case .sha256: self.signer = JWTSigner.rs256(key: key)
|
||||||
case .sha384: self.signer = JWTSigner.rs384(key: key)
|
case .sha384: self.signer = JWTSigner.rs384(key: key)
|
||||||
case .sha512: self.signer = JWTSigner.rs512(key: key)
|
case .sha512: self.signer = JWTSigner.rs512(key: key)
|
||||||
default: throw JWTProviderError(identifier: "badRSAAlgorithm", reason: "RSA signing requires SHA256, SHA384, or SHA512 algorithm", status: .internalServerError)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.header = header
|
self.header = header
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
secret: String,
|
secret: Data,
|
||||||
header: JWTHeader? = nil,
|
header: JWTHeader? = nil,
|
||||||
keyBuilder: (LosslessDataConvertible)throws -> RSAKey,
|
keyBuilder: (Data) throws -> RSAKey,
|
||||||
signerBuilder: (RSAKey) -> JWTSigner = JWTSigner.rs256
|
signerBuilder: (RSAKey) -> JWTSigner = JWTSigner.rs256
|
||||||
)throws {
|
)throws {
|
||||||
let key = try keyBuilder(secret)
|
let key = try keyBuilder(secret)
|
||||||
|
@ -34,17 +43,18 @@ public final class RSAService: JWTService {
|
||||||
self.header = header
|
self.header = header
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(n: String, e: String, d: String? = nil, header: JWTHeader? = nil, algorithm: DigestAlgorithm = .sha256)throws {
|
public init(n: String, e: String, d: String? = nil, header: JWTHeader? = nil, algorithm: DigestAlgorithm = .sha256) {
|
||||||
let key = try RSAKey.components(n: n, e: e, d: d)
|
guard let key = RSAKey(modulus: n, exponent: e, privateExponent: d) else {
|
||||||
|
print("RSA key initialization failed")
|
||||||
|
exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
switch algorithm {
|
switch algorithm {
|
||||||
case .sha256: self.signer = JWTSigner.rs256(key: key)
|
case .sha256: self.signer = JWTSigner.rs256(key: key)
|
||||||
case .sha384: self.signer = JWTSigner.rs384(key: key)
|
case .sha384: self.signer = JWTSigner.rs384(key: key)
|
||||||
case .sha512: self.signer = JWTSigner.rs512(key: key)
|
case .sha512: self.signer = JWTSigner.rs512(key: key)
|
||||||
default: throw JWTProviderError(identifier: "badRSAAlgorithm", reason: "RSA signing requires SHA256, SHA384, or SHA512 algorithm", status: .internalServerError)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.header = header
|
self.header = header
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,21 @@
|
||||||
import Authentication
|
|
||||||
import Vapor
|
import Vapor
|
||||||
import JWT
|
import JWTKit
|
||||||
|
|
||||||
|
extension Request {
|
||||||
|
var jwtService: JWTService {
|
||||||
|
return self.application.jwtService
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension Request {
|
extension Request {
|
||||||
public func payload<Payload>(`as` type: Payload.Type = Payload.self)throws -> Payload where Payload: JWTPayload {
|
public func payload<Payload>(`as` type: Payload.Type = Payload.self)throws -> Payload where Payload: JWTPayload {
|
||||||
guard let token = self.http.headers.bearerAuthorization?.token else {
|
guard let token = self.headers.bearerAuthorization?.token else {
|
||||||
throw JWTProviderError(identifier: "missingAuthorizationHeader", reason: "'Authorization' header with bearer token is missing", status: .badRequest)
|
throw JWTProviderError(identifier: "missingAuthorizationHeader", reason: "'Authorization' header with bearer token is missing", status: .badRequest)
|
||||||
}
|
}
|
||||||
let jwt = try self.make(JWTService.self)
|
|
||||||
let data: Data = Data(token.utf8)
|
let data: Data = Data(token.utf8)
|
||||||
|
|
||||||
if try jwt.verify(data) {
|
if try self.jwtService.verify(data) {
|
||||||
return try JWT<Payload>.init(from: data, verifiedUsing: jwt.signer).payload
|
return try JWT<Payload>.init(from: data, verifiedBy: self.jwtService.signer).payload
|
||||||
} else {
|
} else {
|
||||||
throw JWTProviderError(identifier: "verificationFailed", reason: "Verification failed for JWT token", status: .forbidden)
|
throw JWTProviderError(identifier: "verificationFailed", reason: "Verification failed for JWT token", status: .forbidden)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,36 +1,36 @@
|
||||||
import Foundation
|
//import Foundation
|
||||||
import Vapor
|
//import Vapor
|
||||||
|
//
|
||||||
struct JWKSMockClient: Client, ServiceType {
|
//struct JWKSMockClient: Client, ServiceType {
|
||||||
var container: Container
|
// var container: Container
|
||||||
var requestString: String!
|
// var requestString: String!
|
||||||
|
//
|
||||||
init(container: Container) {
|
// init(container: Container) {
|
||||||
self.container = container
|
// self.container = container
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
func send(_ req: Request) -> EventLoopFuture<Response> {
|
// func send(_ req: Request) -> EventLoopFuture<Response> {
|
||||||
var response: String = ""
|
// var response: String = ""
|
||||||
|
//
|
||||||
switch (req.http.url.absoluteString) {
|
// switch (req.http.url.absoluteString) {
|
||||||
case "https://mockurl.com":
|
// case "https://mockurl.com":
|
||||||
response = JWKSResponseFixture.openIDDocumentResponse
|
// response = JWKSResponseFixture.openIDDocumentResponse
|
||||||
case "https//:mock_jwks_uri.com":
|
// case "https//:mock_jwks_uri.com":
|
||||||
response = JWKSResponseFixture.jwksResponse
|
// response = JWKSResponseFixture.jwksResponse
|
||||||
default:
|
// default:
|
||||||
response = "{}"
|
// response = "{}"
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
return Future.map(on: req) {
|
// return Future.map(on: req) {
|
||||||
req.makeResponse(response, as: .json)
|
// req.makeResponse(response, as: .json)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
public static var serviceSupports: [Any.Type] {
|
// public static var serviceSupports: [Any.Type] {
|
||||||
return [Client.self]
|
// return [Client.self]
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
static func makeService(for worker: Container) throws -> JWKSMockClient {
|
// static func makeService(for worker: Container) throws -> JWKSMockClient {
|
||||||
return JWKSMockClient(container: worker)
|
// return JWKSMockClient(container: worker)
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
|
|
@ -1,74 +1,73 @@
|
||||||
import XCTest
|
//import XCTest
|
||||||
import Vapor
|
//import Vapor
|
||||||
import JWTVapor
|
//import JWTVapor
|
||||||
import Core
|
//
|
||||||
|
//final class JWKSServiceTests: XCTestCase {
|
||||||
final class JWKSServiceTests: XCTestCase {
|
//
|
||||||
|
// static let allTests = [
|
||||||
static let allTests = [
|
// ("testItThrowsErrorIfJWKSConfigIsNotRegisteredAsAService", testItThrowsErrorIfJWKSConfigIsNotRegisteredAsAService),
|
||||||
("testItThrowsErrorIfJWKSConfigIsNotRegisteredAsAService", testItThrowsErrorIfJWKSConfigIsNotRegisteredAsAService),
|
// ("testItDoesNotThrowErrorIfJWKSConfigIsRegisteredAsAService", testItDoesNotThrowErrorIfJWKSConfigIsRegisteredAsAService),
|
||||||
("testItDoesNotThrowErrorIfJWKSConfigIsRegisteredAsAService", testItDoesNotThrowErrorIfJWKSConfigIsRegisteredAsAService),
|
// ("testItReturnsAnRSAServiceForMentionedTid", testItReturnsAnRSAServiceForMentionedTid),
|
||||||
("testItReturnsAnRSAServiceForMentionedTid", testItReturnsAnRSAServiceForMentionedTid),
|
// ("testItThrowsAnErrorIfNoRSAServiceIsFoundForMentionedTid", testItThrowsAnErrorIfNoRSAServiceIsFoundForMentionedTid)
|
||||||
("testItThrowsAnErrorIfNoRSAServiceIsFoundForMentionedTid", testItThrowsAnErrorIfNoRSAServiceIsFoundForMentionedTid)
|
// ]
|
||||||
]
|
//
|
||||||
|
// func testItThrowsErrorIfJWKSConfigIsNotRegisteredAsAService() {
|
||||||
func testItThrowsErrorIfJWKSConfigIsNotRegisteredAsAService() {
|
// var services = Services()
|
||||||
var services = Services()
|
// try? services.register(JWTProvider(RSAService.self))
|
||||||
try? services.register(JWTProvider(RSAService.self))
|
// let app = try? Application(services: services)
|
||||||
let app = try? Application(services: services)
|
//
|
||||||
|
// XCTAssertThrowsError(try app?.make(JWKSService.self))
|
||||||
XCTAssertThrowsError(try app?.make(JWKSService.self))
|
// }
|
||||||
}
|
//
|
||||||
|
// func testItDoesNotThrowErrorIfJWKSConfigIsRegisteredAsAService() {
|
||||||
func testItDoesNotThrowErrorIfJWKSConfigIsRegisteredAsAService() {
|
// var services = Services()
|
||||||
var services = Services()
|
// let jwksMockConfig = JWKSConfig(jwks: "https://mockurl.com", following: "jwks_mock_uri_key")
|
||||||
let jwksMockConfig = JWKSConfig(jwks: "https://mockurl.com", following: "jwks_mock_uri_key")
|
// services.register(jwksMockConfig)
|
||||||
services.register(jwksMockConfig)
|
// try? services.register(JWTProvider(RSAService.self))
|
||||||
try? services.register(JWTProvider(RSAService.self))
|
// let app = try? Application(services: services)
|
||||||
let app = try? Application(services: services)
|
//
|
||||||
|
// XCTAssertNoThrow(try app?.make(JWKSService.self))
|
||||||
XCTAssertNoThrow(try app?.make(JWKSService.self))
|
// }
|
||||||
}
|
//
|
||||||
|
// func testItReturnsAnRSAServiceForMentionedTid() {
|
||||||
func testItReturnsAnRSAServiceForMentionedTid() {
|
// var services = Services.default()
|
||||||
var services = Services.default()
|
// let jwksMockConfig = JWKSConfig(jwks: "https://mockurl.com", following: "jwks_mock_uri_key")
|
||||||
let jwksMockConfig = JWKSConfig(jwks: "https://mockurl.com", following: "jwks_mock_uri_key")
|
// services.register(jwksMockConfig)
|
||||||
services.register(jwksMockConfig)
|
// services.register(JWKSMockClient.self)
|
||||||
services.register(JWKSMockClient.self)
|
// try? services.register(JWTProvider(RSAService.self))
|
||||||
try? services.register(JWTProvider(RSAService.self))
|
//
|
||||||
|
// var config = Config.default()
|
||||||
var config = Config.default()
|
// config.prefer(JWKSMockClient.self, for: Client.self)
|
||||||
config.prefer(JWKSMockClient.self, for: Client.self)
|
//
|
||||||
|
// let app = try! Application(config: config, services: services)
|
||||||
let app = try! Application(config: config, services: services)
|
//
|
||||||
|
// do {
|
||||||
do {
|
// let rsaService = try app.make(JWKSService.self)
|
||||||
let rsaService = try app.make(JWKSService.self)
|
// .rsaService(forKey: "7_Zuf1tvkwLxYaHS3q6lUjUYIGw")
|
||||||
.rsaService(forKey: "7_Zuf1tvkwLxYaHS3q6lUjUYIGw")
|
// .wait()
|
||||||
.wait()
|
//
|
||||||
|
// XCTAssertEqual(rsaService.signer.algorithm.jwtAlgorithmName, "RS256")
|
||||||
XCTAssertEqual(rsaService.signer.algorithm.jwtAlgorithmName, "RS256")
|
//
|
||||||
|
// } catch {
|
||||||
} catch {
|
//
|
||||||
|
// XCTFail("Failed with exception \(error)")
|
||||||
XCTFail("Failed with exception \(error)")
|
// }
|
||||||
}
|
// }
|
||||||
}
|
//
|
||||||
|
// func testItThrowsAnErrorIfNoRSAServiceIsFoundForMentionedTid() {
|
||||||
func testItThrowsAnErrorIfNoRSAServiceIsFoundForMentionedTid() {
|
// var services = Services.default()
|
||||||
var services = Services.default()
|
// let jwksMockConfig = JWKSConfig(jwks: "https://mockurl.com", following: "jwks_mock_uri_key")
|
||||||
let jwksMockConfig = JWKSConfig(jwks: "https://mockurl.com", following: "jwks_mock_uri_key")
|
// services.register(jwksMockConfig)
|
||||||
services.register(jwksMockConfig)
|
// services.register(JWKSMockClient.self)
|
||||||
services.register(JWKSMockClient.self)
|
// try? services.register(JWTProvider(RSAService.self))
|
||||||
try? services.register(JWTProvider(RSAService.self))
|
//
|
||||||
|
// var config = Config.default()
|
||||||
var config = Config.default()
|
// config.prefer(JWKSMockClient.self, for: Client.self)
|
||||||
config.prefer(JWKSMockClient.self, for: Client.self)
|
//
|
||||||
|
// let app = try! Application(config: config, services: services)
|
||||||
let app = try! Application(config: config, services: services)
|
//
|
||||||
|
// XCTAssertThrowsError(try app.make(JWKSService.self)
|
||||||
XCTAssertThrowsError(try app.make(JWKSService.self)
|
// .rsaService(forKey: "some_unknown_tid")
|
||||||
.rsaService(forKey: "some_unknown_tid")
|
// .wait())
|
||||||
.wait())
|
// }
|
||||||
}
|
//}
|
||||||
}
|
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
import XCTest
|
//import XCTest
|
||||||
import Vapor
|
//import Vapor
|
||||||
import JWTVapor
|
//import JWTVapor
|
||||||
|
//
|
||||||
final class JWTProviderTests: XCTestCase {
|
//final class JWTProviderTests: XCTestCase {
|
||||||
static let allTests = [("testItRegistersJWKSService", testItRegistersJWKSService)]
|
// static let allTests = [("testItRegistersJWKSService", testItRegistersJWKSService)]
|
||||||
|
//
|
||||||
func testItRegistersJWKSService() {
|
// func testItRegistersJWKSService() {
|
||||||
var services = Services()
|
// var services = Services()
|
||||||
|
//
|
||||||
let mockJWKSConfig = JWKSConfig(jwks: "https://mockUrl.com", following: "jwks_mock_uri")
|
// let mockJWKSConfig = JWKSConfig(jwks: "https://mockUrl.com", following: "jwks_mock_uri")
|
||||||
services.register(mockJWKSConfig)
|
// services.register(mockJWKSConfig)
|
||||||
try? services.register(JWTProvider(RSAService.self))
|
// try? services.register(JWTProvider(RSAService.self))
|
||||||
|
//
|
||||||
let app = try? Application(services: services)
|
// let app = try? Application(services: services)
|
||||||
let jwksService = try? app?.make(JWKSService.self)
|
// let jwksService = try? app?.make(JWKSService.self)
|
||||||
|
//
|
||||||
XCTAssertNotNil(jwksService as Any)
|
// XCTAssertNotNil(jwksService as Any)
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import XCTest
|
import XCTest
|
||||||
|
|
||||||
import JWTVaporTests
|
import JWTKitVaporTests
|
||||||
|
|
||||||
var tests = [XCTestCaseEntry]()
|
var tests = [XCTestCaseEntry]()
|
||||||
tests += JWTVaporTests.allTests()
|
tests += JWTVaporTests.allTests()
|
||||||
|
|
Loading…
Reference in New Issue