From 54e4e66005905e5a10cde0f9406a3099a91bbb3f Mon Sep 17 00:00:00 2001 From: Dimitri Bouniol Date: Thu, 9 Oct 2025 05:49:31 -0700 Subject: [PATCH] Refactored codable implementations into their own extensions for readability --- .../AuthenticatorAssertionResponse.swift | 3 +- .../PublicKeyCredentialRequestOptions.swift | 44 ++++++----- .../AuthenticatorAttestationResponse.swift | 3 +- .../PublicKeyCredentialCreationOptions.swift | 78 +++++++++---------- .../Registration/RegistrationCredential.swift | 2 +- 5 files changed, 64 insertions(+), 66 deletions(-) diff --git a/Sources/WebAuthn/Ceremonies/Authentication/AuthenticatorAssertionResponse.swift b/Sources/WebAuthn/Ceremonies/Authentication/AuthenticatorAssertionResponse.swift index 2daa5df..85956e0 100644 --- a/Sources/WebAuthn/Ceremonies/Authentication/AuthenticatorAssertionResponse.swift +++ b/Sources/WebAuthn/Ceremonies/Authentication/AuthenticatorAssertionResponse.swift @@ -58,7 +58,7 @@ extension AuthenticatorAssertionResponse: Codable { userHandle = try container.decodeBytesFromURLEncodedBase64IfPresent(forKey: .userHandle) attestationObject = try container.decodeBytesFromURLEncodedBase64IfPresent(forKey: .attestationObject) } - + public func encode(to encoder: any Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) @@ -69,7 +69,6 @@ extension AuthenticatorAssertionResponse: Codable { try container.encodeIfPresent(attestationObject?.base64URLEncodedString(), forKey: .attestationObject) } - private enum CodingKeys: String, CodingKey { case clientDataJSON case authenticatorData diff --git a/Sources/WebAuthn/Ceremonies/Authentication/PublicKeyCredentialRequestOptions.swift b/Sources/WebAuthn/Ceremonies/Authentication/PublicKeyCredentialRequestOptions.swift index afe8980..fb09703 100644 --- a/Sources/WebAuthn/Ceremonies/Authentication/PublicKeyCredentialRequestOptions.swift +++ b/Sources/WebAuthn/Ceremonies/Authentication/PublicKeyCredentialRequestOptions.swift @@ -18,7 +18,7 @@ import Foundation /// When encoding using `Encodable`, the byte arrays are encoded as base64url. /// /// - SeeAlso: https://www.w3.org/TR/webauthn-2/#dictionary-assertion-options -public struct PublicKeyCredentialRequestOptions: Codable, Sendable { +public struct PublicKeyCredentialRequestOptions: Sendable { /// A challenge that the authenticator signs, along with other data, when producing an authentication assertion /// /// When encoding using `Encodable` this is encoded as base64url. @@ -45,16 +45,6 @@ public struct PublicKeyCredentialRequestOptions: Codable, Sendable { // let extensions: [String: Any] - public func encode(to encoder: any Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - - try container.encode(challenge.base64URLEncodedString(), forKey: .challenge) - try container.encodeIfPresent(timeout?.milliseconds, forKey: .timeout) - try container.encode(relyingPartyID, forKey: .relyingPartyID) - try container.encodeIfPresent(allowCredentials, forKey: .allowCredentials) - try container.encodeIfPresent(userVerification, forKey: .userVerification) - } - public init( challenge: [UInt8], timeout: Duration?, @@ -68,7 +58,9 @@ public struct PublicKeyCredentialRequestOptions: Codable, Sendable { self.allowCredentials = allowCredentials self.userVerification = userVerification } +} +extension PublicKeyCredentialRequestOptions: Codable { public init(from decoder: any Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) @@ -81,6 +73,16 @@ public struct PublicKeyCredentialRequestOptions: Codable, Sendable { self.allowCredentials = try values.decodeIfPresent([PublicKeyCredentialDescriptor].self, forKey: .allowCredentials) self.userVerification = try values.decodeIfPresent(UserVerificationRequirement.self, forKey: .userVerification) } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(challenge.base64URLEncodedString(), forKey: .challenge) + try container.encodeIfPresent(timeout?.milliseconds, forKey: .timeout) + try container.encode(relyingPartyID, forKey: .relyingPartyID) + try container.encodeIfPresent(allowCredentials, forKey: .allowCredentials) + try container.encodeIfPresent(userVerification, forKey: .userVerification) + } private enum CodingKeys: String, CodingKey { case challenge @@ -94,7 +96,7 @@ public struct PublicKeyCredentialRequestOptions: Codable, Sendable { /// Information about a generated credential. /// /// When encoding using `Encodable`, `id` is encoded as base64url. -public struct PublicKeyCredentialDescriptor: Equatable, Codable, Sendable { +public struct PublicKeyCredentialDescriptor: Equatable, Sendable { /// Defines hints as to how clients might communicate with a particular authenticator in order to obtain an /// assertion for a specific credential public struct AuthenticatorTransport: UnreferencedStringEnumeration, Sendable { @@ -138,15 +140,9 @@ public struct PublicKeyCredentialDescriptor: Equatable, Codable, Sendable { self.id = id self.transports = transports } +} - public func encode(to encoder: any Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - - try container.encode(type, forKey: .type) - try container.encode(id.base64URLEncodedString(), forKey: .id) - try container.encodeIfPresent(transports, forKey: .transports) - } - +extension PublicKeyCredentialDescriptor: Codable { public init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) @@ -155,6 +151,14 @@ public struct PublicKeyCredentialDescriptor: Equatable, Codable, Sendable { self.transports = try container.decodeIfPresent([AuthenticatorTransport].self, forKey: .transports) ?? [] } + public func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(type, forKey: .type) + try container.encode(id.base64URLEncodedString(), forKey: .id) + try container.encodeIfPresent(transports, forKey: .transports) + } + private enum CodingKeys: String, CodingKey { case type case id diff --git a/Sources/WebAuthn/Ceremonies/Registration/AuthenticatorAttestationResponse.swift b/Sources/WebAuthn/Ceremonies/Registration/AuthenticatorAttestationResponse.swift index 343dc31..e6f2bb6 100644 --- a/Sources/WebAuthn/Ceremonies/Registration/AuthenticatorAttestationResponse.swift +++ b/Sources/WebAuthn/Ceremonies/Registration/AuthenticatorAttestationResponse.swift @@ -36,7 +36,7 @@ extension AuthenticatorAttestationResponse: Codable { clientDataJSON = try container.decodeBytesFromURLEncodedBase64(forKey: .clientDataJSON) attestationObject = try container.decodeBytesFromURLEncodedBase64(forKey: .attestationObject) } - + public func encode(to encoder: any Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) @@ -44,7 +44,6 @@ extension AuthenticatorAttestationResponse: Codable { try container.encode(attestationObject.base64URLEncodedString(), forKey: .attestationObject) } - private enum CodingKeys: String, CodingKey { case clientDataJSON case attestationObject diff --git a/Sources/WebAuthn/Ceremonies/Registration/PublicKeyCredentialCreationOptions.swift b/Sources/WebAuthn/Ceremonies/Registration/PublicKeyCredentialCreationOptions.swift index 2d0976b..dfa7242 100644 --- a/Sources/WebAuthn/Ceremonies/Registration/PublicKeyCredentialCreationOptions.swift +++ b/Sources/WebAuthn/Ceremonies/Registration/PublicKeyCredentialCreationOptions.swift @@ -19,7 +19,7 @@ import Foundation /// `Encodable` byte arrays are base64url encoded. /// /// - SeeAlso: https://www.w3.org/TR/webauthn-2/#dictionary-makecredentialoptions -public struct PublicKeyCredentialCreationOptions: Codable, Sendable { +public struct PublicKeyCredentialCreationOptions: Sendable { /// A byte array randomly generated by the Relying Party. Should be at least 16 bytes long to ensure sufficient /// entropy. /// @@ -47,30 +47,6 @@ public struct PublicKeyCredentialCreationOptions: Codable, Sendable { /// supported. public var attestation: AttestationConveyancePreference - public func encode(to encoder: any Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - - try container.encode(challenge.base64URLEncodedString(), forKey: .challenge) - try container.encode(user, forKey: .user) - try container.encode(relyingParty, forKey: .relyingParty) - try container.encode(publicKeyCredentialParameters, forKey: .publicKeyCredentialParameters) - try container.encodeIfPresent(timeout?.milliseconds, forKey: .timeout) - try container.encode(attestation, forKey: .attestation) - } - - public init(from decoder: any Decoder) throws { - let values = try decoder.container(keyedBy: CodingKeys.self) - - self.challenge = try values.decodeBytesFromURLEncodedBase64(forKey: .challenge) - self.user = try values.decode(PublicKeyCredentialUserEntity.self, forKey: .user) - self.relyingParty = try values.decode(PublicKeyCredentialRelyingPartyEntity.self, forKey: .relyingParty) - self.publicKeyCredentialParameters = try values.decode([PublicKeyCredentialParameters].self, forKey: .publicKeyCredentialParameters) - if let timeout = try values.decodeIfPresent(UInt32.self, forKey: .timeout) { - self.timeout = .milliseconds(timeout) - } - self.attestation = try values.decode(AttestationConveyancePreference.self, forKey: .attestation) - } - public init( challenge: [UInt8], user: PublicKeyCredentialUserEntity, @@ -86,6 +62,32 @@ public struct PublicKeyCredentialCreationOptions: Codable, Sendable { self.timeout = timeout self.attestation = attestation } +} + +extension PublicKeyCredentialCreationOptions: Codable { + public init(from decoder: any Decoder) throws { + let values = try decoder.container(keyedBy: CodingKeys.self) + + self.challenge = try values.decodeBytesFromURLEncodedBase64(forKey: .challenge) + self.user = try values.decode(PublicKeyCredentialUserEntity.self, forKey: .user) + self.relyingParty = try values.decode(PublicKeyCredentialRelyingPartyEntity.self, forKey: .relyingParty) + self.publicKeyCredentialParameters = try values.decode([PublicKeyCredentialParameters].self, forKey: .publicKeyCredentialParameters) + if let timeout = try values.decodeIfPresent(UInt32.self, forKey: .timeout) { + self.timeout = .milliseconds(timeout) + } + self.attestation = try values.decode(AttestationConveyancePreference.self, forKey: .attestation) + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(challenge.base64URLEncodedString(), forKey: .challenge) + try container.encode(user, forKey: .user) + try container.encode(relyingParty, forKey: .relyingParty) + try container.encode(publicKeyCredentialParameters, forKey: .publicKeyCredentialParameters) + try container.encodeIfPresent(timeout?.milliseconds, forKey: .timeout) + try container.encode(attestation, forKey: .attestation) + } private enum CodingKeys: String, CodingKey { case challenge @@ -116,13 +118,6 @@ public struct PublicKeyCredentialParameters: Equatable, Codable, Sendable { self.type = type self.alg = alg } - - public init(from decoder: any Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - - self.type = try container.decode(CredentialType.self, forKey: .type) - self.alg = try container.decode(COSEAlgorithmIdentifier.self, forKey: .alg) - } } extension Array where Element == PublicKeyCredentialParameters { @@ -158,7 +153,7 @@ public struct PublicKeyCredentialRelyingPartyEntity: Codable, Sendable { /// creating a new credential. /// /// When encoding using `Encodable`, `id` is base64url encoded. -public struct PublicKeyCredentialUserEntity: Codable, Sendable { +public struct PublicKeyCredentialUserEntity: Sendable { /// Generated by the Relying Party, unique to the user account, and must not contain personally identifying /// information about the user. /// @@ -181,15 +176,9 @@ public struct PublicKeyCredentialUserEntity: Codable, Sendable { self.name = name self.displayName = displayName } +} - public func encode(to encoder: any Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - - try container.encode(id.base64URLEncodedString(), forKey: .id) - try container.encode(name, forKey: .name) - try container.encode(displayName, forKey: .displayName) - } - +extension PublicKeyCredentialUserEntity: Codable { public init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) @@ -198,6 +187,13 @@ public struct PublicKeyCredentialUserEntity: Codable, Sendable { self.displayName = try container.decode(String.self, forKey: .displayName) } + public func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(id.base64URLEncodedString(), forKey: .id) + try container.encode(name, forKey: .name) + try container.encode(displayName, forKey: .displayName) + } private enum CodingKeys: String, CodingKey { case id diff --git a/Sources/WebAuthn/Ceremonies/Registration/RegistrationCredential.swift b/Sources/WebAuthn/Ceremonies/Registration/RegistrationCredential.swift index 02beecd..bf4fe59 100644 --- a/Sources/WebAuthn/Ceremonies/Registration/RegistrationCredential.swift +++ b/Sources/WebAuthn/Ceremonies/Registration/RegistrationCredential.swift @@ -47,7 +47,7 @@ extension RegistrationCredential: Codable { self.rawID = rawID attestationResponse = try container.decode(AuthenticatorAttestationResponse.self, forKey: .attestationResponse) } - + public func encode(to encoder: any Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self)