Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make the middleware usable and add tests #20

Merged
merged 8 commits into from
Mar 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: test
on:
- pull_request
jobs:
#jwtmiddleware_macos:
# runs-on: macos-latest
# env:
# DEVELOPER_DIR: /Applications/Xcode_11.4_beta.app/Contents/Developer
# steps:
# - uses: actions/checkout@v2
# - run: brew install vapor/tap/vapor-beta
# - run: xcrun swift test --enable-test-discovery --sanitize=thread
jwtmiddleware_xenial:
container:
image: vapor/swift:5.2-xenial
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: swift test --enable-test-discovery --sanitize=thread
jwtmiddleware_bionic:
container:
image: vapor/swift:5.2-bionic
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: swift test --enable-test-discovery --sanitize=thread

2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@
/.build
/Packages
/*.xcodeproj
/.swiftpm
Package.resolved
160 changes: 0 additions & 160 deletions Package.resolved

This file was deleted.

17 changes: 11 additions & 6 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// swift-tools-version:5.1

// swift-tools-version:5.2
import PackageDescription

let package = Package(
Expand All @@ -11,11 +10,17 @@ let package = Package(
.library(name: "JWTMiddleware", targets: ["JWTMiddleware"]),
],
dependencies: [
.package(url: "https://github.com/vapor/vapor.git", from: "4.0.0-beta.3"),
.package(url: "https://github.com/vapor/jwt.git", from: "4.0.0-beta.2"),
.package(url: "https://github.com/vapor/vapor.git", from: "4.0.0-rc"),
.package(url: "https://github.com/vapor/jwt.git", from: "4.0.0-rc"),
],
targets: [
.target(name: "JWTMiddleware", dependencies: ["Vapor", "JWT"]),
.testTarget(name: "JWTMiddlewareTests", dependencies: ["JWTMiddleware"])
.target(name: "JWTMiddleware", dependencies: [
.product(name: "Vapor", package: "vapor"),
.product(name: "JWT", package: "jwt"),
]),
.testTarget(name: "JWTMiddlewareTests", dependencies: [
.byName(name: "JWTMiddleware"),
.product(name: "XCTVapor", package: "vapor"),
])
]
)
22 changes: 8 additions & 14 deletions Sources/JWTMiddleware/JWTMiddleware.swift
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import Vapor
import JWT

final class JWTMiddleware<T: JWTPayload>: Middleware {
init() { }
public final class JWTMiddleware<T: JWTPayload>: Middleware {
public init() {}

func respond(to request: Request, chainingTo next: Responder) -> EventLoopFuture<Response> {
public func respond(to request: Request, chainingTo next: Responder) -> EventLoopFuture<Response> {

guard let token = request.headers.bearerAuthorization?.token.utf8 else {
return request.eventLoop.makeFailedFuture(Abort(.unauthorized, reason: "Missing authorization bearer header"))
Expand All @@ -24,19 +24,13 @@ final class JWTMiddleware<T: JWTPayload>: Middleware {

}

extension AnyHashable {
static let payload: String = "jwt_payload"
}

extension Request {
var loggedIn: Bool {
if (self.userInfo[.payload] as? JWTPayload) != nil {
return true
}
return false
private struct PayloadKey: StorageKey {
typealias Value = JWTPayload
}

var payload: JWTPayload {
get { self.userInfo[.payload] as! JWTPayload }
set { self.userInfo[.payload] = newValue }
get { self.storage[PayloadKey.self]! }
set { self.storage[PayloadKey.self] = newValue }
}
}
29 changes: 0 additions & 29 deletions Sources/JWTMiddleware/Payload.swift

This file was deleted.

84 changes: 80 additions & 4 deletions Tests/JWTMiddlewareTests/JWTMiddlewareTests.swift
Original file line number Diff line number Diff line change
@@ -1,10 +1,86 @@
import XCTest
import JWT
import Vapor
import XCTVapor
import CNIOBoringSSL
@testable import JWTMiddleware
@testable import JWTKit

struct TestPayload: JWTPayload {
let id: UUID
let exp: TimeInterval

init(id: UUID, exp: TimeInterval) {
self.id = id
self.exp = exp
}

func verify(using signer: JWTSigner) throws {
_ = SubjectClaim(value: self.id.uuidString) // Nothing to verify here.
try ExpirationClaim(value: Date(timeIntervalSince1970: self.exp)).verifyNotExpired()
}
}

final class JWTMiddlewareTests: XCTestCase {
func testExample() {}

static var allTests = [
("testExample", testExample),
]
var tester: Application!

override func setUpWithError() throws {
// CryptoKit only generates EC keys and I don't know how to turn the raw representation into JWKS.
var exp: BIGNUM = .init(); CNIOBoringSSL_BN_set_u64(&exp, 0x10001)
var rsa: RSA = .init(); CNIOBoringSSL_RSA_generate_key_ex(&rsa, 4096, &exp, nil)

let dBytes: [UInt8] = .init(unsafeUninitializedCapacity: Int(CNIOBoringSSL_BN_num_bytes(rsa.d))) { $1 = CNIOBoringSSL_BN_bn2bin(rsa.d, $0.baseAddress!) }
let nBytes: [UInt8] = .init(unsafeUninitializedCapacity: Int(CNIOBoringSSL_BN_num_bytes(rsa.n))) { $1 = CNIOBoringSSL_BN_bn2bin(rsa.n, $0.baseAddress!) }
struct LocalJWKS: Codable {
struct LocalJWK: Codable { let kty, d, e, n, use, kid, alg: String }
let keys: [LocalJWK]
}
let keyset = LocalJWKS(keys: [.init(kty: "RSA", d: String(bytes: dBytes.base64URLEncodedBytes(), encoding: .utf8)!, e: "AQAB", n: String(bytes: nBytes.base64URLEncodedBytes(), encoding: .utf8)!, use: "sig", kid: "jwttest", alg: "RS256")])
let json = try JSONEncoder().encode(keyset)

tester = Application(.testing)
try tester.jwt.signers.use(jwksJSON: String(data: json, encoding: .utf8)!)
}

override func tearDownWithError() throws {
tester?.shutdown()
}

func testPayloadValidationUnexpired() throws {
let testPayload = TestPayload(id: UUID(), exp: Date(timeIntervalSinceNow: 10.0).timeIntervalSince1970)

tester.middleware.use(JWTMiddleware<TestPayload>())
tester.get("hello") { _ in "world" }

let token = try tester.jwt.signers.sign(testPayload, kid: "jwttest")

_ = try XCTUnwrap(tester.testable(method: .inMemory).test(.GET, "/hello", headers: ["Authorization": "Bearer \(token)"]) { res in
XCTAssertEqual(res.body.string, "world")
})
}

func testPayloadValidationExpired() throws {
let testPayload = TestPayload(id: UUID(), exp: Date(timeIntervalSinceNow: -10.0).timeIntervalSince1970)

tester.middleware.use(JWTMiddleware<TestPayload>())
tester.get("hello") { _ in "world" }

let token = try tester.jwt.signers.sign(testPayload, kid: "jwttest")

_ = try XCTUnwrap(tester.testable(method: .inMemory).test(.GET, "/hello", headers: ["Authorization": "Bearer \(token)"]) { res in
XCTAssertEqual(res.status, .unauthorized)

struct JWTErrorResponse: Codable {
let error: Bool
let reason: String
}

guard let content = try? XCTUnwrap(res.content.decode(JWTErrorResponse.self)) else {
return
}
XCTAssertEqual(content.error, true)
XCTAssertEqual(content.reason, "exp claim verification failed: expired")
})
}
}