From 72b50d6600a4274c07450d7d60e15939f1177405 Mon Sep 17 00:00:00 2001 From: Dimitri Bouniol Date: Sat, 14 Dec 2024 02:51:46 -0800 Subject: [PATCH] Fixed an issue where VAPID.Configuration would encode the same key twice --- README.md | 4 +- .../WebPush/VAPID/VAPIDConfiguration.swift | 73 +++++++++++++------ 2 files changed, 54 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 213c4d1..50172d2 100644 --- a/README.md +++ b/README.md @@ -111,12 +111,12 @@ To uninstall the generator: Once installed, a new configuration can be generated as needed: ``` % ~/.swiftpm/bin/vapid-key-generator https://example.com -VAPID.Configuration: {"contactInformation":"https://example.com","expirationDuration":79200,"keys":["g7PXKzeMR/B+ndQWa92Dl9u22CibXJnm6vN9L6Gri1E="],"primaryKey":"g7PXKzeMR/B+ndQWa92Dl9u22CibXJnm6vN9L6Gri1E=","validityDuration":72000} +VAPID.Configuration: {"contactInformation":"https://example.com","expirationDuration":79200,"primaryKey":"6PSSAJiMj7uOvtE4ymNo5GWcZbT226c5KlV6c+8fx5g=","validityDuration":72000} Example Usage: // TODO: Load this data from .env or from file system - let configurationData = Data(#" {"contactInformation":"https://example.com","expirationDuration":79200,"keys":["g7PXKzeMR/B+ndQWa92Dl9u22CibXJnm6vN9L6Gri1E="],"primaryKey":"g7PXKzeMR/B+ndQWa92Dl9u22CibXJnm6vN9L6Gri1E=","validityDuration":72000} "#.utf8) + let configurationData = Data(#" {"contactInformation":"https://example.com","expirationDuration":79200,"primaryKey":"6PSSAJiMj7uOvtE4ymNo5GWcZbT226c5KlV6c+8fx5g=","validityDuration":72000} "#.utf8) let vapidConfiguration = try JSONDecoder().decode(VAPID.Configuration.self, from: configurationData) ``` diff --git a/Sources/WebPush/VAPID/VAPIDConfiguration.swift b/Sources/WebPush/VAPID/VAPIDConfiguration.swift index 5d871ce..1446c6f 100644 --- a/Sources/WebPush/VAPID/VAPIDConfiguration.swift +++ b/Sources/WebPush/VAPID/VAPIDConfiguration.swift @@ -9,7 +9,7 @@ import Foundation extension VoluntaryApplicationServerIdentification { - public struct Configuration: Hashable, Codable, Sendable { + public struct Configuration: Hashable, Sendable { /// The VAPID key that identifies the push service to subscribers. /// /// This key should be shared by all instances of your push service, and should be kept secure. Rotating this key is not recommended as you'll lose access to subscribers that registered against it. @@ -64,26 +64,6 @@ extension VoluntaryApplicationServerIdentification { self.validityDuration = validityDuration } - public init(from decoder: any Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - - let primaryKey = try container.decodeIfPresent(Key.self, forKey: CodingKeys.primaryKey) - let keys = try container.decode(Set.self, forKey: CodingKeys.keys) - let deprecatedKeys = try container.decodeIfPresent(Set.self, forKey: CodingKeys.deprecatedKeys) - let contactInformation = try container.decode(ContactInformation.self, forKey: CodingKeys.contactInformation) - let expirationDuration = try container.decode(Duration.self, forKey: CodingKeys.expirationDuration) - let validityDuration = try container.decode(Duration.self, forKey: CodingKeys.validityDuration) - - try self.init( - primaryKey: primaryKey, - keys: keys, - deprecatedKeys: deprecatedKeys, - contactInformation: contactInformation, - expirationDuration: expirationDuration, - validityDuration: validityDuration - ) - } - mutating func updateKeys( primaryKey: Key?, keys: Set, @@ -105,6 +85,57 @@ extension VoluntaryApplicationServerIdentification { } } +extension VAPID.Configuration: Codable { + public enum CodingKeys: CodingKey { + case primaryKey + case keys + case deprecatedKeys + case contactInformation + case expirationDuration + case validityDuration + } + + public init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + let primaryKey = try container.decodeIfPresent(VAPID.Key.self, forKey: CodingKeys.primaryKey) + let keys = try container.decodeIfPresent(Set.self, forKey: CodingKeys.keys) ?? [] + let deprecatedKeys = try container.decodeIfPresent(Set.self, forKey: CodingKeys.deprecatedKeys) + let contactInformation = try container.decode(ContactInformation.self, forKey: CodingKeys.contactInformation) + let expirationDuration = try container.decode(Duration.self, forKey: CodingKeys.expirationDuration) + let validityDuration = try container.decode(Duration.self, forKey: CodingKeys.validityDuration) + + try self.init( + primaryKey: primaryKey, + keys: keys, + deprecatedKeys: deprecatedKeys, + contactInformation: contactInformation, + expirationDuration: expirationDuration, + validityDuration: validityDuration + ) + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + /// Remove the primary key from the list so it's not listed twice + var keys: Set? = self.keys + if let primaryKey { + keys?.remove(primaryKey) + } + if keys?.isEmpty == true { + keys = nil + } + + try container.encodeIfPresent(primaryKey, forKey: .primaryKey) + try container.encodeIfPresent(keys, forKey: .keys) + try container.encodeIfPresent(deprecatedKeys, forKey: .deprecatedKeys) + try container.encode(contactInformation, forKey: .contactInformation) + try container.encode(expirationDuration, forKey: .expirationDuration) + try container.encode(validityDuration, forKey: .validityDuration) + } +} + extension VAPID.Configuration { /// The contact information for the push service. ///