Skip to content

Commit

Permalink
Update for JWTKit v5 (#157)
Browse files Browse the repository at this point in the history
* Update for JWTKit v5

* Add lint job

* Update v5

* Update to swift 6

* Update to swift 6 again

* Update to swift-testing

* Make `withApp` return `Void`

* Fix warnings

* Format

* Try use reusable workflow for linting

* Use main branch test workflow
  • Loading branch information
ptoffy authored Oct 5, 2024
1 parent f240c5f commit f208afc
Show file tree
Hide file tree
Showing 18 changed files with 349 additions and 390 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ on:
push:
branches:
- main

jobs:
unit-tests:
uses: vapor/ci/.github/workflows/run-unit-tests.yml@main
with:
with_coverage: false
with_tsan: true
warnings_as_errors: true
with_linting: true
12 changes: 5 additions & 7 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version:5.10
// swift-tools-version:6.0
import PackageDescription

let package = Package(
Expand All @@ -10,10 +10,10 @@ let package = Package(
.watchOS(.v9),
],
products: [
.library(name: "JWT", targets: ["JWT"]),
.library(name: "JWT", targets: ["JWT"])
],
dependencies: [
.package(url: "https://github.com/vapor/jwt-kit.git", from: "5.0.0-rc.1"),
.package(url: "https://github.com/vapor/jwt-kit.git", from: "5.0.0"),
.package(url: "https://github.com/vapor/vapor.git", from: "4.101.0"),
],
targets: [
Expand All @@ -22,16 +22,14 @@ let package = Package(
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")]
]
),
]
)
15 changes: 13 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,18 @@
<br>

Support for JWT (JSON Web Tokens) in Vapor.
This package integrates [JWTKit](https://github.com/vapor/jwt-kit.git) with Vapor.

**Original author**
### Installation

- Siemen Sikkema, [@siemensikkema](http://github.com/siemensikkema)
Use the SPM string to easily include the package in your `Package.swift` file.

```swift
.package(url: "https://github.com/vapor/jwt.git", from: "5.0.0")
```

and add it to your target's dependencies:

```swift
.product(name: "JWT", package: "jwt")
```
14 changes: 7 additions & 7 deletions Sources/JWT/Application+JWT.swift
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import JWTKit
import Vapor
import NIOConcurrencyHelpers
import Vapor

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

struct JWT: Sendable {
public struct JWT: Sendable {
private final class Storage: Sendable {
private struct SendableBox: Sendable {
var keys: JWTKeyCollection
}

private let sendableBox: NIOLockedValueBox<SendableBox>

var keys: JWTKeyCollection {
get {
self.sendableBox.withLockedValue { box in
Expand All @@ -27,7 +27,7 @@ public extension Application {
}
}
}

init() {
let box = SendableBox(keys: .init())
self.sendableBox = .init(box)
Expand Down
12 changes: 6 additions & 6 deletions Sources/JWT/JWT+Apple.swift
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import NIOConcurrencyHelpers
import Vapor

public extension Request.JWT {
var apple: Apple {
extension Request.JWT {
public var apple: Apple {
.init(_jwt: self)
}

struct Apple: Sendable {
public struct Apple: Sendable {
public let _jwt: Request.JWT

public func verify(
Expand Down Expand Up @@ -40,12 +40,12 @@ public extension Request.JWT {
}
}

public extension Application.JWT {
var apple: Apple {
extension Application.JWT {
public var apple: Apple {
.init(_jwt: self)
}

struct Apple: Sendable {
public struct Apple: Sendable {
public let _jwt: Application.JWT

public func keys(on request: Request) async throws -> JWTKeyCollection {
Expand Down
12 changes: 6 additions & 6 deletions Sources/JWT/JWT+Google.swift
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import NIOConcurrencyHelpers
import Vapor

public extension Request.JWT {
var google: Google {
extension Request.JWT {
public var google: Google {
.init(_jwt: self)
}

struct Google: Sendable {
public struct Google: Sendable {
public let _jwt: Request.JWT

public func verify(
Expand Down Expand Up @@ -51,12 +51,12 @@ public extension Request.JWT {
}
}

public extension Application.JWT {
var google: Google {
extension Application.JWT {
public var google: Google {
.init(_jwt: self)
}

struct Google: Sendable {
public struct Google: Sendable {
public let _jwt: Application.JWT

public func keys(on request: Request) async throws -> JWTKeyCollection {
Expand Down
18 changes: 9 additions & 9 deletions Sources/JWT/JWT+Microsoft.swift
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import NIOConcurrencyHelpers
import Vapor

public extension Request.JWT {
var microsoft: Microsoft {
extension Request.JWT {
public var microsoft: Microsoft {
.init(_jwt: self)
}

struct Microsoft {
public struct Microsoft {
public let _jwt: Request.JWT

public func verify(
Expand Down Expand Up @@ -40,12 +40,12 @@ public extension Request.JWT {
}
}

public extension Application.JWT {
var microsoft: Microsoft {
extension Application.JWT {
public var microsoft: Microsoft {
.init(_jwt: self)
}

struct Microsoft {
public struct Microsoft {
public let _jwt: Application.JWT

public func keys(on request: Request) async throws -> JWTKeyCollection {
Expand All @@ -55,7 +55,7 @@ public extension Application.JWT {
public var jwks: EndpointCache<JWKS> {
self.storage.jwks
}

public var jwksEndpoint: URI {
get {
self.storage.jwksEndpoint
Expand Down Expand Up @@ -87,7 +87,7 @@ public extension Application.JWT {
}

private let sendableBox: NIOLockedValueBox<SendableBox>

var jwks: EndpointCache<JWKS> {
get {
self.sendableBox.withLockedValue { box in
Expand All @@ -113,7 +113,7 @@ public extension Application.JWT {
}
}
}

var jwksEndpoint: URI {
get {
self.sendableBox.withLockedValue { box in
Expand Down
11 changes: 5 additions & 6 deletions Sources/JWT/JWTAuthenticator.swift
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import Vapor

public extension JWTPayload where Self: Authenticatable {
static func authenticator() -> AsyncAuthenticator {
extension JWTPayload where Self: Authenticatable {
public static func authenticator() -> AsyncAuthenticator {
JWTPayloadAuthenticator<Self>()
}
}

private struct JWTPayloadAuthenticator<Payload>: JWTAuthenticator
where Payload: JWTPayload & Authenticatable
{
where Payload: JWTPayload & Authenticatable {
func authenticate(jwt: Payload, for request: Request) async throws {
request.auth.login(jwt)
}
Expand All @@ -19,8 +18,8 @@ public protocol JWTAuthenticator: AsyncBearerAuthenticator {
func authenticate(jwt: Payload, for request: Request) async throws
}

public extension JWTAuthenticator {
func authenticate(bearer: BearerAuthorization, for request: Request) async throws {
extension JWTAuthenticator {
public func authenticate(bearer: BearerAuthorization, for request: Request) async throws {
try await self.authenticate(jwt: request.jwt.verify(bearer.token), for: request)
}
}
2 changes: 1 addition & 1 deletion Sources/JWT/JWTError+Vapor.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Vapor

extension JWTError: AbortError {
extension JWTError: @retroactive AbortError {
public var status: HTTPResponseStatus {
.unauthorized
}
Expand Down
18 changes: 7 additions & 11 deletions Sources/JWT/Request+JWT.swift
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import JWTKit
import Vapor

public extension Request {
var jwt: JWT {
extension Request {
public var jwt: JWT {
.init(_request: self)
}

struct JWT: Sendable {
public struct JWT: Sendable {
public let _request: Request

@discardableResult
public func verify<Payload>(as _: Payload.Type = Payload.self) async throws -> Payload
where Payload: JWTPayload
{
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)
Expand All @@ -22,21 +21,18 @@ public extension Request {

@discardableResult
public func verify<Payload>(_ message: String, as _: Payload.Type = Payload.self) async throws -> Payload
where Payload: JWTPayload
{
where Payload: JWTPayload {
try await self.verify([UInt8](message.utf8), as: Payload.self)
}

@discardableResult
public func verify<Payload>(_ message: some DataProtocol & Sendable, as _: Payload.Type = Payload.self) async throws -> Payload
where Payload: JWTPayload
{
where Payload: JWTPayload {
try await self._request.application.jwt.keys.verify(message, as: Payload.self)
}

public func sign<Payload>(_ jwt: Payload, kid: JWKIdentifier? = nil, header: JWTHeader = .init()) async throws -> String
where Payload: JWTPayload
{
where Payload: JWTPayload {
return try await self._request.application.jwt.keys.sign(jwt, kid: kid, header: header)
}
}
Expand Down
7 changes: 7 additions & 0 deletions Tests/JWTTests/Helpers/ByteBuffer+string.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Vapor

extension ByteBuffer {
var string: String {
.init(decoding: self.readableBytesView, as: UTF8.self)
}
}
10 changes: 10 additions & 0 deletions Tests/JWTTests/Helpers/isLoggingConfigured.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import Vapor

let isLoggingConfigured: Bool = {
LoggingSystem.bootstrap { label in
var handler = StreamLogHandler.standardOutput(label: label)
handler.logLevel = .debug
return handler
}
return true
}()
9 changes: 9 additions & 0 deletions Tests/JWTTests/Helpers/withApp.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import Testing
import Vapor

func withApp(_ body: (Application) async throws -> Void) async throws {
let app = try await Application.make(.testing)
try #require(isLoggingConfigured == true)
try await body(app)
try await app.asyncShutdown()
}
Loading

0 comments on commit f208afc

Please sign in to comment.