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

Version 4 to Version 5 Migration Deprecations #221

Open
wants to merge 1 commit into
base: v4
Choose a base branch
from

Conversation

dimitribouniol
Copy link

@dimitribouniol dimitribouniol commented Nov 20, 2024

This PR adds migration deprecations for many common APIs between version 4 and version 5 that should help most users (particularly those using ECDSA keys and standard JWTs) migrate from version 4 to version 5 by following warnings. Crucially, it allows libraries that rely on the same requirements (for instance, https://github.com/apple/app-store-server-library-swift and https://github.com/m-barthelemy/AcmeSwift) to allow for a range of versions such as "4.14.0" ..< "6.0.0".

If we agree with the proposed updates here, a few more things should be done:

  • Cut a new minor v4 version, specifically 4.14.0, so users can opt out of the warnings should they wish to.
  • Add an entry to main's README explaining the process to update from v4 to v5.
  • Update v5 with an AsyncJWTPayload typealias back to JWTPayload that is itself deprecated, to fully support libraries that wish to be compatible with v4 and v5.
  • Make a similar but smaller PR to jwt for vapor to move folks away from signers and onto keys (otherwise they are still forced into an all or nothing update since they will still be stuck with JWTSigners).

Note that I didn't make compatibility APIs for every type — if someone feels they are necessary due to a library requiring them, I figured we could add more support in a future PR.

Comment on lines +299 to +303
/// JWTKeyCollection was introduced in v5 and replaces ``JWTSigners``.
///
/// - Note: Please migrate over to ``JWTKeyCollection`` before updating to v5, though if you plan on remaining on v4, ``JWTSigners`` can continue to be used.
public actor JWTKeyCollection {
var signers = JWTSigners()
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a lightweight implementation of the new JWTKeyCollection API falling back entirely on the now deprecated JWTSigners API.

Comment on lines +9 to +17
/// A transitionary protocol with sync and async requirements.
///
/// This protocol should be dropped once you are finished migrating to v5, as it'll have been renamed back to ``JWTPayload``, but with a single async requirement. In order to support both versions v4 and v5 in a library, do not implement the requirements of ``JWTPayload`` as ``JWTSigner`` is no longer available in v5.
public protocol AsyncJWTPayload: Codable {
func verify<Algorithm: JWTAlgorithm>(using signer: Algorithm) throws

/// Verifies that the payload's claims are correct or throws an error.
func verify<Algorithm: JWTAlgorithm>(using algorithm: Algorithm) async throws
}
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because we can't mark protocol methods as deprecated, we define a new one that will be transitionary and support both sync and asynchronous modalities, ensuring compatibility with existing code paths, but also newer signing methods no matter which would be used.

Comment on lines +500 to +506
@available(*, deprecated, message: "Please make sure Payload conforms to AsyncJWTPayload instead of JWTPayload before updating to v5.")
public func sign<Payload: JWTPayload>(
_ payload: Payload,
kid: JWKIdentifier? = nil
) async throws -> String {
try signers.sign(payload, kid: kid)
}
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This variant is marked as deprecated despite appearing on the new JWTKeyCollection to help push users to adopt AsyncJWTPayload wherever they use another payload type.

/// A transitionary protocol with sync and async requirements.
///
/// This protocol should be dropped once you are finished migrating to v5, as it'll have been renamed back to ``JWTPayload``, but with a single async requirement. In order to support both versions v4 and v5 in a library, do not implement the requirements of ``JWTPayload`` as ``JWTSigner`` is no longer available in v5.
public protocol AsyncJWTPayload: Codable {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As for the name, I'm open to alternatives. JWTPayloadV4 could work well since this variant of it only really exists in the v4 version of the codebase.

Comment on lines +33 to +38
func payload<Payload>(as payload: Payload.Type, jsonDecoder: any JWTJSONDecoder) throws -> Payload
where Payload: AsyncJWTPayload
{
try jsonDecoder
.decode(Payload.self, from: .init(self.encodedPayload.base64URLDecodedBytes()))
}
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a duplicate of the above variant to support the new protocol.

where Payload: JWTPayload
where Payload: Encodable
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly, rather than duplicate this internal method, I loosened the protocol requirements to it works for both.

Comment on lines 225 to 252
public enum ECDSA: Sendable {
/// ECDSA.PublicKey was introduced in v5 and replaces ``ECDSAKey``.
///
/// - Note: Please migrate over to ``ECDSA/PublicKey`` before updating to v5, though if you plan on remaining on v4, ``ECDSAKey`` can continue to be used.
public struct PublicKey<Curve: ECDSACurveType> {
let key: ECDSAKey
init(key: ECDSAKey) { self.key = key }
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are wrappers on the existing keys to support the new types a user would be forced into, wile not adding much new functionality. I added bare minimum support for all ECDSA public and private keys.

Comment on lines +8 to +9
class JWTKitMigrationTests: XCTestCase {
func testVerifyingCryptoKey() async throws {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An end to end test to validate everything is hooked up properly. Also tested the migrations in the app-store-server-library repo along with my own codebase.

public typealias ES256PublicKey = ECDSA.PublicKey<P256>
public typealias ES256PrivateKey = ECDSA.PrivateKey<P256>

extension P384: ECDSACurveType, @unchecked @retroactive Sendable {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These will need versions for Swift 5 and Swift 6

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought it wouldn’t, because this version of the library would always be compiled with the Swift 5 language mode. Once v5 is used, then none of this is included.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, nvm, forgot retroactive didn’t exist pre swift 6 compiler, will update

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Though I'm now wondering if its worth adding Sendable conformances at all, since the rest of the library didn't support it yet at all

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants