Skip to content

Commit db38c0e

Browse files
authored
Merge pull request #46 from dimitribouniol/dimitri/timout-ambiguity
2 parents 3ebcd30 + 9bfb5d1 commit db38c0e

File tree

7 files changed

+77
-29
lines changed

7 files changed

+77
-29
lines changed

Sources/WebAuthn/Ceremonies/Authentication/PublicKeyCredentialRequestOptions.swift

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,20 @@ import Foundation
1717
/// The `PublicKeyCredentialRequestOptions` gets passed to the WebAuthn API (`navigator.credentials.get()`)
1818
///
1919
/// When encoding using `Encodable`, the byte arrays are encoded as base64url.
20+
///
21+
/// - SeeAlso: https://www.w3.org/TR/webauthn-2/#dictionary-assertion-options
2022
public struct PublicKeyCredentialRequestOptions: Encodable {
2123
/// A challenge that the authenticator signs, along with other data, when producing an authentication assertion
2224
///
2325
/// When encoding using `Encodable` this is encoded as base64url.
2426
public let challenge: [UInt8]
2527

26-
/// The number of milliseconds that the Relying Party is willing to wait for the call to complete. The value is treated
27-
/// as a hint, and may be overridden by the client.
28+
/// A time, in seconds, that the caller is willing to wait for the call to complete. This is treated as a
29+
/// hint, and may be overridden by the client.
30+
///
31+
/// - Note: When encoded, this value is represented in milleseconds as a ``UInt32``.
2832
/// See https://www.w3.org/TR/webauthn-2/#dictionary-assertion-options
29-
public let timeout: UInt32?
33+
public let timeout: Duration?
3034

3135
/// The Relying Party ID.
3236
public let rpId: String?
@@ -43,7 +47,7 @@ public struct PublicKeyCredentialRequestOptions: Encodable {
4347
var container = encoder.container(keyedBy: CodingKeys.self)
4448

4549
try container.encode(challenge.base64URLEncodedString(), forKey: .challenge)
46-
try container.encodeIfPresent(timeout, forKey: .timeout)
50+
try container.encodeIfPresent(timeout?.milliseconds, forKey: .timeout)
4751
try container.encodeIfPresent(rpId, forKey: .rpId)
4852
try container.encodeIfPresent(allowCredentials, forKey: .allowCredentials)
4953
try container.encodeIfPresent(userVerification, forKey: .userVerification)

Sources/WebAuthn/Ceremonies/Registration/PublicKeyCredentialCreationOptions.swift

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,14 @@
1212
//
1313
//===----------------------------------------------------------------------===//
1414

15+
import Foundation
16+
1517
/// The `PublicKeyCredentialCreationOptions` gets passed to the WebAuthn API (`navigator.credentials.create()`)
1618
///
1719
/// Generally this should not be created manually. Instead use `RelyingParty.beginRegistration()`. When encoding using
1820
/// `Encodable` byte arrays are base64url encoded.
21+
///
22+
/// - SeeAlso: https://www.w3.org/TR/webauthn-2/#dictionary-makecredentialoptions
1923
public struct PublicKeyCredentialCreationOptions: Encodable {
2024
/// A byte array randomly generated by the Relying Party. Should be at least 16 bytes long to ensure sufficient
2125
/// entropy.
@@ -34,9 +38,11 @@ public struct PublicKeyCredentialCreationOptions: Encodable {
3438
/// preferred.
3539
public let publicKeyCredentialParameters: [PublicKeyCredentialParameters]
3640

37-
/// A time, in milliseconds, that the caller is willing to wait for the call to complete. This is treated as a
41+
/// A time, in seconds, that the caller is willing to wait for the call to complete. This is treated as a
3842
/// hint, and may be overridden by the client.
39-
public let timeoutInMilliseconds: UInt32?
43+
///
44+
/// - Note: When encoded, this value is represented in milleseconds as a ``UInt32``.
45+
public let timeout: Duration?
4046

4147
/// Sets the Relying Party's preference for attestation conveyance. At the time of writing only `none` is
4248
/// supported.
@@ -49,7 +55,7 @@ public struct PublicKeyCredentialCreationOptions: Encodable {
4955
try container.encode(user, forKey: .user)
5056
try container.encode(relyingParty, forKey: .relyingParty)
5157
try container.encode(publicKeyCredentialParameters, forKey: .publicKeyCredentialParameters)
52-
try container.encodeIfPresent(timeoutInMilliseconds, forKey: .timeoutInMilliseconds)
58+
try container.encodeIfPresent(timeout?.milliseconds, forKey: .timeout)
5359
try container.encode(attestation, forKey: .attestation)
5460
}
5561

@@ -58,7 +64,7 @@ public struct PublicKeyCredentialCreationOptions: Encodable {
5864
case user
5965
case relyingParty = "rp"
6066
case publicKeyCredentialParameters = "pubKeyCredParams"
61-
case timeoutInMilliseconds = "timeout"
67+
case timeout
6268
case attestation
6369
}
6470
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the WebAuthn Swift open source project
4+
//
5+
// Copyright (c) 2022 the WebAuthn Swift project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of WebAuthn Swift project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
extension Duration {
16+
/// The value of a positive duration in milliseconds, suitable to be encoded in WebAuthn types.
17+
var milliseconds: Int64 {
18+
let (seconds, attoseconds) = self.components
19+
return Int64(seconds * 1000) + Int64(attoseconds/1_000_000_000_000_000)
20+
}
21+
}

Sources/WebAuthn/WebAuthnManager.swift

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -47,31 +47,26 @@ public struct WebAuthnManager {
4747
/// This method will use the Relying Party information from the WebAuthnManager's config to create ``PublicKeyCredentialCreationOptions``
4848
/// - Parameters:
4949
/// - user: The user to register.
50-
/// - timeoutInSeconds: How long the browser should give the user to choose an authenticator. This value
50+
/// - timeout: How long the browser should give the user to choose an authenticator. This value
5151
/// is a *hint* and may be ignored by the browser. Defaults to 60 seconds.
5252
/// - attestation: The Relying Party's preference regarding attestation. Defaults to `.none`.
5353
/// - publicKeyCredentialParameters: A list of public key algorithms the Relying Party chooses to restrict
5454
/// support to. Defaults to all supported algorithms.
5555
/// - Returns: Registration options ready for the browser.
5656
public func beginRegistration(
5757
user: PublicKeyCredentialUserEntity,
58-
timeoutInSeconds: TimeInterval? = 3600,
58+
timeout: Duration? = .seconds(3600),
5959
attestation: AttestationConveyancePreference = .none,
6060
publicKeyCredentialParameters: [PublicKeyCredentialParameters] = .supported
6161
) -> PublicKeyCredentialCreationOptions {
6262
let challenge = challengeGenerator.generate()
6363

64-
var timeoutInMilliseconds: UInt32?
65-
if let timeoutInSeconds {
66-
timeoutInMilliseconds = UInt32(timeoutInSeconds * 1000)
67-
}
68-
6964
return PublicKeyCredentialCreationOptions(
7065
challenge: challenge,
7166
user: user,
7267
relyingParty: .init(id: config.relyingPartyID, name: config.relyingPartyName),
7368
publicKeyCredentialParameters: publicKeyCredentialParameters,
74-
timeoutInMilliseconds: timeoutInMilliseconds,
69+
timeout: timeout,
7570
attestation: attestation
7671
)
7772
}
@@ -139,18 +134,15 @@ public struct WebAuthnManager {
139134
/// "user verified" flag.
140135
/// - Returns: Authentication options ready for the browser.
141136
public func beginAuthentication(
142-
timeout: TimeInterval? = 60,
137+
timeout: Duration? = .seconds(60),
143138
allowCredentials: [PublicKeyCredentialDescriptor]? = nil,
144139
userVerification: UserVerificationRequirement = .preferred
145140
) throws -> PublicKeyCredentialRequestOptions {
146141
let challenge = challengeGenerator.generate()
147-
var timeoutInMilliseconds: UInt32? = nil
148-
if let timeout {
149-
timeoutInMilliseconds = UInt32(timeout * 1000)
150-
}
142+
151143
return PublicKeyCredentialRequestOptions(
152144
challenge: challenge,
153-
timeout: timeoutInMilliseconds,
145+
timeout: timeout,
154146
rpId: config.relyingPartyID,
155147
allowCredentials: allowCredentials,
156148
userVerification: userVerification
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the WebAuthn Swift open source project
4+
//
5+
// Copyright (c) 2022 the WebAuthn Swift project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of WebAuthn Swift project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import XCTest
16+
@testable import WebAuthn
17+
18+
final class DurationTests: XCTestCase {
19+
func testMilliseconds() throws {
20+
XCTAssertEqual(Duration.milliseconds(1234).milliseconds, 1234)
21+
XCTAssertEqual(Duration.milliseconds(-1234).milliseconds, -1234)
22+
XCTAssertEqual(Duration.microseconds(12345).milliseconds, 12)
23+
XCTAssertEqual(Duration.microseconds(-12345).milliseconds, -12)
24+
}
25+
}

Tests/WebAuthnTests/WebAuthnManagerAuthenticationTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,13 @@ final class WebAuthnManagerAuthenticationTests: XCTestCase {
3737
func testBeginAuthentication() async throws {
3838
let allowCredentials: [PublicKeyCredentialDescriptor] = [.init(type: "public-key", id: [1, 0, 2, 30])]
3939
let options = try webAuthnManager.beginAuthentication(
40-
timeout: 1234,
40+
timeout: .seconds(1234),
4141
allowCredentials: allowCredentials,
4242
userVerification: .preferred
4343
)
4444

4545
XCTAssertEqual(options.challenge, challenge)
46-
XCTAssertEqual(options.timeout, 1234000) // timeout converted to milliseconds
46+
XCTAssertEqual(options.timeout, .seconds(1234))
4747
XCTAssertEqual(options.rpId, relyingPartyID)
4848
XCTAssertEqual(options.allowCredentials, allowCredentials)
4949
XCTAssertEqual(options.userVerification, .preferred)

Tests/WebAuthnTests/WebAuthnManagerIntegrationTests.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,13 @@ final class WebAuthnManagerIntegrationTests: XCTestCase {
3131

3232
// Step 1.: Begin Registration
3333
let mockUser = PublicKeyCredentialUserEntity.mock
34-
let timeout: TimeInterval = 1234
34+
let timeout: Duration = .seconds(1234)
3535
let attestationPreference = AttestationConveyancePreference.none
3636
let publicKeyCredentialParameters: [PublicKeyCredentialParameters] = .supported
3737

3838
let registrationOptions = webAuthnManager.beginRegistration(
3939
user: mockUser,
40-
timeoutInSeconds: timeout,
40+
timeout: timeout,
4141
attestation: attestationPreference,
4242
publicKeyCredentialParameters: publicKeyCredentialParameters
4343
)
@@ -49,7 +49,7 @@ final class WebAuthnManagerIntegrationTests: XCTestCase {
4949
XCTAssertEqual(registrationOptions.attestation, attestationPreference)
5050
XCTAssertEqual(registrationOptions.relyingParty.id, config.relyingPartyID)
5151
XCTAssertEqual(registrationOptions.relyingParty.name, config.relyingPartyName)
52-
XCTAssertEqual(registrationOptions.timeoutInMilliseconds, UInt32(timeout * 1000))
52+
XCTAssertEqual(registrationOptions.timeout, timeout)
5353
XCTAssertEqual(registrationOptions.publicKeyCredentialParameters, publicKeyCredentialParameters)
5454

5555
// Now send `registrationOptions` to client, which in turn will send the authenticator's response back to us:
@@ -93,7 +93,7 @@ final class WebAuthnManagerIntegrationTests: XCTestCase {
9393
XCTAssertEqual(credential.publicKey, mockCredentialPublicKey)
9494

9595
// Step 3.: Begin Authentication
96-
let authenticationTimeout: TimeInterval = 4567
96+
let authenticationTimeout: Duration = .seconds(4567)
9797
let userVerification: UserVerificationRequirement = .preferred
9898
let rememberedCredentials = [PublicKeyCredentialDescriptor(
9999
type: "public-key",
@@ -107,7 +107,7 @@ final class WebAuthnManagerIntegrationTests: XCTestCase {
107107
)
108108

109109
XCTAssertEqual(authenticationOptions.rpId, config.relyingPartyID)
110-
XCTAssertEqual(authenticationOptions.timeout, UInt32(authenticationTimeout * 1000)) // timeout is in milliseconds
110+
XCTAssertEqual(authenticationOptions.timeout, authenticationTimeout)
111111
XCTAssertEqual(authenticationOptions.challenge, mockChallenge)
112112
XCTAssertEqual(authenticationOptions.userVerification, userVerification)
113113
XCTAssertEqual(authenticationOptions.allowCredentials, rememberedCredentials)

0 commit comments

Comments
 (0)