From f21676d3c49fa28452331d0807e9c2fbb305aee6 Mon Sep 17 00:00:00 2001 From: Vamsi Madduluri Date: Wed, 10 Jan 2024 14:52:53 +0530 Subject: [PATCH 01/12] Refactor: Remove comments and update doc comments for CodeManager --- .../EmptyCodeManager.swift | 13 +++--- .../VaporOAuth/Protocols/CodeManager.swift | 40 +++++++++++++++++-- .../Fakes/FakeCodeManager.swift | 9 ++--- .../Fakes/StubCodeManager.swift | 1 - 4 files changed, 46 insertions(+), 17 deletions(-) diff --git a/Sources/VaporOAuth/DefaultImplementations/EmptyCodeManager.swift b/Sources/VaporOAuth/DefaultImplementations/EmptyCodeManager.swift index 6a4b3f8..39a4479 100644 --- a/Sources/VaporOAuth/DefaultImplementations/EmptyCodeManager.swift +++ b/Sources/VaporOAuth/DefaultImplementations/EmptyCodeManager.swift @@ -1,11 +1,10 @@ public struct EmptyCodeManager: CodeManager { public init() {} - + public func getCode(_ code: String) -> OAuthCode? { return nil } - - // Updated to include PKCE parameters + public func generateCode( userID: String, clientID: String, @@ -16,16 +15,16 @@ public struct EmptyCodeManager: CodeManager { ) async throws -> String { return "" } - + public func codeUsed(_ code: OAuthCode) {} - + public func getDeviceCode(_ deviceCode: String) -> OAuthDeviceCode? { return nil } - + public func generateDeviceCode(userID: String, clientID: String, scopes: [String]?) async throws -> String { return "" } - + public func deviceCodeUsed(_ deviceCode: OAuthDeviceCode) {} } diff --git a/Sources/VaporOAuth/Protocols/CodeManager.swift b/Sources/VaporOAuth/Protocols/CodeManager.swift index 6054bd0..99e8aa3 100644 --- a/Sources/VaporOAuth/Protocols/CodeManager.swift +++ b/Sources/VaporOAuth/Protocols/CodeManager.swift @@ -1,13 +1,45 @@ /// Responsible for generating and managing OAuth Codes public protocol CodeManager: Sendable { - // Updated to include PKCE parameters + /// Generates an OAuth code for the specified user, client, redirect URI, scopes, code challenge, and code challenge method. + /// - Parameters: + /// - userID: The ID of the user. + /// - clientID: The ID of the client. + /// - redirectURI: The redirect URI. + /// - scopes: The requested scopes. + /// - codeChallenge: The code challenge. + /// - codeChallengeMethod: The code challenge method. + /// - Returns: The generated OAuth code. + /// - Throws: An error if the code generation fails. func generateCode(userID: String, clientID: String, redirectURI: String, scopes: [String]?, codeChallenge: String?, codeChallengeMethod: String?) async throws -> String + + /// Retrieves the OAuth code associated with the specified code. + /// - Parameter code: The OAuth code. + /// - Returns: The associated OAuth code, or `nil` if not found. + /// - Throws: An error if the retrieval fails. func getCode(_ code: String) async throws -> OAuthCode? - - // This is explicit to ensure that the code is marked as used or deleted (it could be implied that this is done when you call - // `getCode` but it is called explicitly to remind developers to ensure that codes can't be reused) + + /// Marks the specified OAuth code as used or deleted. + /// - Parameter code: The OAuth code to mark as used or deleted. + /// - Throws: An error if the operation fails. func codeUsed(_ code: OAuthCode) async throws + + /// Generates a device code for the specified user, client, and scopes. + /// - Parameters: + /// - userID: The ID of the user. + /// - clientID: The ID of the client. + /// - scopes: The requested scopes. + /// - Returns: The generated device code. + /// - Throws: An error if the code generation fails. func generateDeviceCode(userID: String, clientID: String, scopes: [String]?) async throws -> String + + /// Retrieves the device code associated with the specified device code. + /// - Parameter deviceCode: The device code. + /// - Returns: The associated device code, or `nil` if not found. + /// - Throws: An error if the retrieval fails. func getDeviceCode(_ deviceCode: String) async throws -> OAuthDeviceCode? + + /// Marks the specified device code as used or deleted. + /// - Parameter deviceCode: The device code to mark as used or deleted. + /// - Throws: An error if the operation fails. func deviceCodeUsed(_ deviceCode: OAuthDeviceCode) async throws } diff --git a/Tests/VaporOAuthTests/Fakes/FakeCodeManager.swift b/Tests/VaporOAuthTests/Fakes/FakeCodeManager.swift index c40e1e7..11063d6 100644 --- a/Tests/VaporOAuthTests/Fakes/FakeCodeManager.swift +++ b/Tests/VaporOAuthTests/Fakes/FakeCodeManager.swift @@ -2,7 +2,7 @@ import VaporOAuth import Foundation class FakeCodeManager: CodeManager { - + private(set) var usedCodes: [String] = [] var codes: [String: OAuthCode] = [:] var deviceCodes: [String: OAuthDeviceCode] = [:] @@ -11,18 +11,17 @@ class FakeCodeManager: CodeManager { func getCode(_ code: String) -> OAuthCode? { return codes[code] } - + func getDeviceCode(_ deviceCode: String) -> OAuthDeviceCode? { return deviceCodes[deviceCode] } - // Updated to include PKCE parameters func generateCode(userID: String, clientID: String, redirectURI: String, scopes: [String]?, codeChallenge: String?, codeChallengeMethod: String?) throws -> String { let code = OAuthCode(codeID: generatedCode, clientID: clientID, redirectURI: redirectURI, userID: userID, expiryDate: Date().addingTimeInterval(60), scopes: scopes, codeChallenge: codeChallenge, codeChallengeMethod: codeChallengeMethod) codes[generatedCode] = code return generatedCode } - + func generateDeviceCode(userID: String, clientID: String, scopes: [String]?) throws -> String { let deviceCode = OAuthDeviceCode(deviceCodeID: generatedCode, userCode: "USER_CODE", clientID: clientID, userID: userID, expiryDate: Date().addingTimeInterval(60), scopes: scopes) deviceCodes[generatedCode] = deviceCode @@ -33,7 +32,7 @@ class FakeCodeManager: CodeManager { usedCodes.append(code.codeID) codes.removeValue(forKey: code.codeID) } - + func deviceCodeUsed(_ deviceCode: OAuthDeviceCode) { usedCodes.append(deviceCode.deviceCodeID) deviceCodes.removeValue(forKey: deviceCode.deviceCodeID) diff --git a/Tests/VaporOAuthTests/Fakes/StubCodeManager.swift b/Tests/VaporOAuthTests/Fakes/StubCodeManager.swift index d03f38e..0a1be8e 100644 --- a/Tests/VaporOAuthTests/Fakes/StubCodeManager.swift +++ b/Tests/VaporOAuthTests/Fakes/StubCodeManager.swift @@ -4,7 +4,6 @@ class StubCodeManager: CodeManager { var codeToReturn = "ABCDEFHIJKLMNO" - // Updated to include PKCE parameters func generateCode( userID: String, clientID: String, From 04cac65229f6b57a840f196051fcf59f008dabe3 Mon Sep 17 00:00:00 2001 From: Vamsi Madduluri Date: Wed, 10 Jan 2024 14:58:08 +0530 Subject: [PATCH 02/12] Refactor: Update OAuthClient properties for Sendable compatibility --- Sources/VaporOAuth/Models/OAuthClient.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/VaporOAuth/Models/OAuthClient.swift b/Sources/VaporOAuth/Models/OAuthClient.swift index 9e8e5a0..131d3fd 100644 --- a/Sources/VaporOAuth/Models/OAuthClient.swift +++ b/Sources/VaporOAuth/Models/OAuthClient.swift @@ -1,6 +1,6 @@ import Vapor -public final class OAuthClient: Extendable { +public final class OAuthClient: Extendable, Sendable { public let clientID: String public let redirectURIs: [String]? public let clientSecret: String? From 9788cb4d06dc3fadae9503bf8b72482291c2abb1 Mon Sep 17 00:00:00 2001 From: Vamsi Madduluri Date: Wed, 10 Jan 2024 14:59:11 +0530 Subject: [PATCH 03/12] Refactor: Updated StaticClientRetriever to work with concurrent access --- .../DefaultImplementations/StaticClientRetriever.swift | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Sources/VaporOAuth/DefaultImplementations/StaticClientRetriever.swift b/Sources/VaporOAuth/DefaultImplementations/StaticClientRetriever.swift index 69a5abd..21f6d24 100644 --- a/Sources/VaporOAuth/DefaultImplementations/StaticClientRetriever.swift +++ b/Sources/VaporOAuth/DefaultImplementations/StaticClientRetriever.swift @@ -1,15 +1,13 @@ -import Vapor - -public actor StaticClientRetriever: ClientRetriever { +public struct StaticClientRetriever: ClientRetriever { private let clients: [String: OAuthClient] - + public init(clients: [OAuthClient]) { self.clients = clients.reduce(into: [String: OAuthClient]()) { (dict, client) in dict[client.clientID] = client } } - - public func getClient(clientID: String) async throws -> OAuthClient? { + + public func getClient(clientID: String) throws -> OAuthClient? { return clients[clientID] } } From a1398bff8947c47a8f545a206345eef950ef7cc3 Mon Sep 17 00:00:00 2001 From: Vamsi Madduluri Date: Wed, 10 Jan 2024 15:20:46 +0530 Subject: [PATCH 04/12] Refactor OAuthHelper+remote.swift --- Sources/VaporOAuth/Helper/OAuthHelper+remote.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Sources/VaporOAuth/Helper/OAuthHelper+remote.swift b/Sources/VaporOAuth/Helper/OAuthHelper+remote.swift index 1aff65c..9ba9687 100644 --- a/Sources/VaporOAuth/Helper/OAuthHelper+remote.swift +++ b/Sources/VaporOAuth/Helper/OAuthHelper+remote.swift @@ -3,15 +3,15 @@ import Vapor actor RemoteTokenResponseActor { var remoteTokenResponse: RemoteTokenResponse? - + func setRemoteTokenResponse(_ response: RemoteTokenResponse) { self.remoteTokenResponse = response } - + func hasTokenResponse() async -> Bool { return remoteTokenResponse != nil } - + func getRemoteTokenResponse() async throws -> RemoteTokenResponse { guard let response = remoteTokenResponse else { throw Abort(.internalServerError) @@ -67,7 +67,7 @@ extension OAuthHelper { responseActor: responseActor ) } - + let remoteTokenResponse = try await responseActor.getRemoteTokenResponse() guard let user = remoteTokenResponse.user else { @@ -78,7 +78,7 @@ extension OAuthHelper { } ) } - + private static func setupRemoteTokenResponse( request: Request, tokenIntrospectionEndpoint: String, From 999f9fbd457c9d29b6f703df673574986722cdb5 Mon Sep 17 00:00:00 2001 From: Vamsi Madduluri Date: Wed, 10 Jan 2024 15:23:34 +0530 Subject: [PATCH 05/12] Remove unnecessary file headers and imports --- Sources/VaporOAuth/Models/OAuthDeviceCode.swift | 7 ------- .../TokenHandlers/DeviceCodeTokenHandler.swift | 7 ------- .../GrantTests/DeviceCodeGrantTests.swift | 13 +++---------- 3 files changed, 3 insertions(+), 24 deletions(-) diff --git a/Sources/VaporOAuth/Models/OAuthDeviceCode.swift b/Sources/VaporOAuth/Models/OAuthDeviceCode.swift index 78ffb4b..32ea682 100644 --- a/Sources/VaporOAuth/Models/OAuthDeviceCode.swift +++ b/Sources/VaporOAuth/Models/OAuthDeviceCode.swift @@ -1,10 +1,3 @@ -// -// OAuthDeviceCode.swift -// -// -// Created by Vamsi Madduluri on 24/08/23. -// - import Foundation public final class OAuthDeviceCode { diff --git a/Sources/VaporOAuth/RouteHandlers/TokenHandlers/DeviceCodeTokenHandler.swift b/Sources/VaporOAuth/RouteHandlers/TokenHandlers/DeviceCodeTokenHandler.swift index e8407bb..dc22d8a 100644 --- a/Sources/VaporOAuth/RouteHandlers/TokenHandlers/DeviceCodeTokenHandler.swift +++ b/Sources/VaporOAuth/RouteHandlers/TokenHandlers/DeviceCodeTokenHandler.swift @@ -1,10 +1,3 @@ -// -// DeviceCodeTokenHandler.swift -// -// -// Created by Vamsi Madduluri on 24/08/23. -// - import Vapor struct DeviceCodeTokenHandler { diff --git a/Tests/VaporOAuthTests/GrantTests/DeviceCodeGrantTests.swift b/Tests/VaporOAuthTests/GrantTests/DeviceCodeGrantTests.swift index e114da0..1cbb1d4 100644 --- a/Tests/VaporOAuthTests/GrantTests/DeviceCodeGrantTests.swift +++ b/Tests/VaporOAuthTests/GrantTests/DeviceCodeGrantTests.swift @@ -1,10 +1,3 @@ -// -// DeviceCodeGrantTests.swift -// -// -// Created by Vamsi Madduluri on 24/08/23. -// - import XCTVapor @testable import VaporOAuth @@ -126,9 +119,9 @@ class DeviceCodeTokenTests: XCTestCase { scopes: scopes ) fakeDeviceCodeManager.deviceCodes[expiredDeviceCodeID] = expiredDeviceCode - + let response = try await getDeviceCodeResponse(deviceCode: expiredDeviceCodeID) - + XCTAssertEqual(response.status, .badRequest) let errorResponse = try response.content.decode(ErrorResponse.self) XCTAssertEqual(errorResponse.error, "expired_token") @@ -161,5 +154,5 @@ class DeviceCodeTokenTests: XCTestCase { deviceCode: deviceCode ) } - + } From 25ab0306288982de5b8acbe13e37cdfce579f85a Mon Sep 17 00:00:00 2001 From: Vamsi Madduluri Date: Wed, 10 Jan 2024 15:23:44 +0530 Subject: [PATCH 06/12] Add Sendable conformance to OAuthFlowType --- Sources/VaporOAuth/Utilities/OAuthFlowType.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/VaporOAuth/Utilities/OAuthFlowType.swift b/Sources/VaporOAuth/Utilities/OAuthFlowType.swift index 4f7c45a..43c31f8 100644 --- a/Sources/VaporOAuth/Utilities/OAuthFlowType.swift +++ b/Sources/VaporOAuth/Utilities/OAuthFlowType.swift @@ -1,4 +1,4 @@ -public enum OAuthFlowType: String { +public enum OAuthFlowType: String, Sendable { case authorization = "authorization_code" case implicit = "implicit" case password = "password" From 6e4ed870c2b69d873287c8732092ffe13a46ef20 Mon Sep 17 00:00:00 2001 From: Vamsi Madduluri Date: Wed, 10 Jan 2024 15:32:42 +0530 Subject: [PATCH 07/12] Add PKCEValidator for PKCE validation --- .../VaporOAuth/Validators/CodeValidator.swift | 24 ++++--------- .../VaporOAuth/Validators/PKCEValidator.swift | 34 +++++++++++++++++++ 2 files changed, 40 insertions(+), 18 deletions(-) create mode 100644 Sources/VaporOAuth/Validators/PKCEValidator.swift diff --git a/Sources/VaporOAuth/Validators/CodeValidator.swift b/Sources/VaporOAuth/Validators/CodeValidator.swift index 5cfdb87..2790662 100644 --- a/Sources/VaporOAuth/Validators/CodeValidator.swift +++ b/Sources/VaporOAuth/Validators/CodeValidator.swift @@ -1,36 +1,24 @@ import Foundation -import Crypto // Import SwiftCrypto for SHA-256 +import Crypto struct CodeValidator { func validateCode(_ code: OAuthCode, clientID: String, redirectURI: String, codeVerifier: String?) -> Bool { guard code.clientID == clientID else { return false } - + guard code.expiryDate >= Date() else { return false } - + guard code.redirectURI == redirectURI else { return false } - - // Optional PKCE validation + if let codeChallenge = code.codeChallenge, let codeChallengeMethod = code.codeChallengeMethod, let verifier = codeVerifier { - switch codeChallengeMethod { - case "S256": - // Transform the codeVerifier using SHA256 and base64-url-encode it - guard let verifierData = verifier.data(using: .utf8) else { return false } - let verifierHash = SHA256.hash(data: verifierData) - let encodedVerifier = Data(verifierHash).base64URLEncodedString() - - return codeChallenge == encodedVerifier - default: - // If the code challenge method is unknown, fail the validation - return false - } + return PKCEValidator.validate(codeChallenge: codeChallenge, verifier: verifier, method: code.codeChallengeMethod) } - + // If no PKCE was used (codeVerifier is nil), skip PKCE validation return true } diff --git a/Sources/VaporOAuth/Validators/PKCEValidator.swift b/Sources/VaporOAuth/Validators/PKCEValidator.swift new file mode 100644 index 0000000..4a9c231 --- /dev/null +++ b/Sources/VaporOAuth/Validators/PKCEValidator.swift @@ -0,0 +1,34 @@ +import Foundation +import Crypto + +struct PKCEValidator { + + static func validate(codeChallenge: String, verifier: String?, method: String?) -> Bool { + guard let verifier = verifier else { + // Fail validation if codeVerifier is not provided + return false + } + + guard let method = method else { + // Default to plain if no method is provided + return codeChallenge == verifier + } + + switch method { + case "S256": + return validateS256(codeChallenge: codeChallenge, verifier: verifier) + case "plain": + return codeChallenge == verifier + default: + // Unsupported code challenge method + return false + } + } + + private static func validateS256(codeChallenge: String, verifier: String) -> Bool { + guard let verifierData = verifier.data(using: .utf8) else { return false } + let hashedVerifier = SHA256.hash(data: verifierData) + let base64UrlEncodedHash = Data(hashedVerifier).base64URLEncodedString() + return codeChallenge == base64UrlEncodedHash + } +} From db1d572c1eb3e33d02dd5fa12cd3e57f12afb674 Mon Sep 17 00:00:00 2001 From: Vamsi Madduluri Date: Wed, 10 Jan 2024 15:42:40 +0530 Subject: [PATCH 08/12] Add nonce parameter to generateCode function --- .../VaporOAuth/DefaultImplementations/EmptyCodeManager.swift | 3 ++- Sources/VaporOAuth/Protocols/CodeManager.swift | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Sources/VaporOAuth/DefaultImplementations/EmptyCodeManager.swift b/Sources/VaporOAuth/DefaultImplementations/EmptyCodeManager.swift index 39a4479..4fcde23 100644 --- a/Sources/VaporOAuth/DefaultImplementations/EmptyCodeManager.swift +++ b/Sources/VaporOAuth/DefaultImplementations/EmptyCodeManager.swift @@ -11,7 +11,8 @@ public struct EmptyCodeManager: CodeManager { redirectURI: String, scopes: [String]?, codeChallenge: String?, - codeChallengeMethod: String? + codeChallengeMethod: String?, + nonce: String? ) async throws -> String { return "" } diff --git a/Sources/VaporOAuth/Protocols/CodeManager.swift b/Sources/VaporOAuth/Protocols/CodeManager.swift index 99e8aa3..1b80ab9 100644 --- a/Sources/VaporOAuth/Protocols/CodeManager.swift +++ b/Sources/VaporOAuth/Protocols/CodeManager.swift @@ -8,9 +8,10 @@ public protocol CodeManager: Sendable { /// - scopes: The requested scopes. /// - codeChallenge: The code challenge. /// - codeChallengeMethod: The code challenge method. + /// - nonce: The nonce. /// - Returns: The generated OAuth code. /// - Throws: An error if the code generation fails. - func generateCode(userID: String, clientID: String, redirectURI: String, scopes: [String]?, codeChallenge: String?, codeChallengeMethod: String?) async throws -> String + func generateCode(userID: String, clientID: String, redirectURI: String, scopes: [String]?, codeChallenge: String?, codeChallengeMethod: String?, nonce: String?) async throws -> String /// Retrieves the OAuth code associated with the specified code. /// - Parameter code: The OAuth code. From 7f319da7e5b22e6b97061171b0327d54f0faa238 Mon Sep 17 00:00:00 2001 From: Vamsi Madduluri Date: Wed, 10 Jan 2024 15:42:48 +0530 Subject: [PATCH 09/12] Refactor async methods in OAuthHelper+remote.swift --- Sources/VaporOAuth/Helper/OAuthHelper+remote.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/VaporOAuth/Helper/OAuthHelper+remote.swift b/Sources/VaporOAuth/Helper/OAuthHelper+remote.swift index 9ba9687..5f99447 100644 --- a/Sources/VaporOAuth/Helper/OAuthHelper+remote.swift +++ b/Sources/VaporOAuth/Helper/OAuthHelper+remote.swift @@ -8,11 +8,11 @@ actor RemoteTokenResponseActor { self.remoteTokenResponse = response } - func hasTokenResponse() async -> Bool { + func hasTokenResponse() -> Bool { return remoteTokenResponse != nil } - func getRemoteTokenResponse() async throws -> RemoteTokenResponse { + func getRemoteTokenResponse() throws -> RemoteTokenResponse { guard let response = remoteTokenResponse else { throw Abort(.internalServerError) } From 2fa53e6e8a3a13b58e4c5ee75c51cf6b62256b90 Mon Sep 17 00:00:00 2001 From: Vamsi Madduluri Date: Wed, 10 Jan 2024 15:43:03 +0530 Subject: [PATCH 10/12] Add token generation methods to TokenManager protocol --- .../VaporOAuth/Protocols/TokenManager.swift | 52 +++++++++++++++---- 1 file changed, 43 insertions(+), 9 deletions(-) diff --git a/Sources/VaporOAuth/Protocols/TokenManager.swift b/Sources/VaporOAuth/Protocols/TokenManager.swift index 55f5108..2794015 100644 --- a/Sources/VaporOAuth/Protocols/TokenManager.swift +++ b/Sources/VaporOAuth/Protocols/TokenManager.swift @@ -1,7 +1,15 @@ -import Vapor - +/// A protocol that defines the behavior of a token manager. public protocol TokenManager: Sendable { - // Generates access, refresh, and ID tokens. Should be called after successful authentication. + + /// Generates access, refresh, and ID tokens. Should be called after successful authentication. + /// - Parameters: + /// - clientID: The client ID. + /// - userID: The user ID. + /// - scopes: The scopes. + /// - accessTokenExpiryTime: The expiry time for the access token. + /// - idTokenExpiryTime: The expiry time for the ID token. + /// - nonce: The nonce. + /// - Returns: A tuple containing the generated access token, refresh token, and ID token. func generateTokens( clientID: String, userID: String?, @@ -11,7 +19,13 @@ public protocol TokenManager: Sendable { nonce: String? ) async throws -> (AccessToken, RefreshToken, IDToken) - // Generates only an access token. Should be called after successful authentication. + /// Generates only an access token. Should be called after successful authentication. + /// - Parameters: + /// - clientID: The client ID. + /// - userID: The user ID. + /// - scopes: The scopes. + /// - expiryTime: The expiry time for the access token. + /// - Returns: The generated access token. func generateAccessToken( clientID: String, userID: String?, @@ -19,7 +33,13 @@ public protocol TokenManager: Sendable { expiryTime: Int ) async throws -> AccessToken - // Generates both access and refresh tokens. Should be called after successful PKCE validation. + /// Generates both access and refresh tokens. Should be called after successful PKCE validation. + /// - Parameters: + /// - clientID: The client ID. + /// - userID: The user ID. + /// - scopes: The scopes. + /// - accessTokenExpiryTime: The expiry time for the access token. + /// - Returns: A tuple containing the generated access token and refresh token. func generateAccessRefreshTokens( clientID: String, userID: String?, @@ -27,16 +47,30 @@ public protocol TokenManager: Sendable { accessTokenExpiryTime: Int ) async throws -> (AccessToken, RefreshToken) - // Retrieves a refresh token by its string representation. + /// Retrieves a refresh token by its string representation. + /// - Parameter refreshToken: The string representation of the refresh token. + /// - Returns: The refresh token, if found. Otherwise, `nil`. func getRefreshToken(_ refreshToken: String) async throws -> RefreshToken? - // Retrieves an access token by its string representation. + /// Retrieves an access token by its string representation. + /// - Parameter accessToken: The string representation of the access token. + /// - Returns: The access token, if found. Otherwise, `nil`. func getAccessToken(_ accessToken: String) async throws -> AccessToken? - // Updates a refresh token, typically to change its scope. + /// Updates a refresh token, typically to change its scope. + /// - Parameters: + /// - refreshToken: The refresh token to update. + /// - scopes: The new scopes for the refresh token. func updateRefreshToken(_ refreshToken: RefreshToken, scopes: [String]) async throws - // Generates an ID token. Should be called after successful authentication. + /// Generates an ID token. Should be called after successful authentication. + /// - Parameters: + /// - clientID: The client ID. + /// - userID: The user ID. + /// - scopes: The scopes. + /// - expiryTime: The expiry time for the ID token. + /// - nonce: The nonce. + /// - Returns: The generated ID token. func generateIDToken( clientID: String, userID: String, From 07471ad19c5df2ce00cb2eda7d9931246016c5df Mon Sep 17 00:00:00 2001 From: Vamsi Madduluri Date: Wed, 10 Jan 2024 15:43:16 +0530 Subject: [PATCH 11/12] Add nonce parameter to code generation --- Sources/VaporOAuth/RouteHandlers/AuthorizePostHandler.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Sources/VaporOAuth/RouteHandlers/AuthorizePostHandler.swift b/Sources/VaporOAuth/RouteHandlers/AuthorizePostHandler.swift index 57e874c..8425d3e 100644 --- a/Sources/VaporOAuth/RouteHandlers/AuthorizePostHandler.swift +++ b/Sources/VaporOAuth/RouteHandlers/AuthorizePostHandler.swift @@ -53,7 +53,8 @@ struct AuthorizePostHandler { redirectURI: requestObject.redirectURIBaseString, scopes: requestObject.scopes, codeChallenge: requestObject.codeChallenge, - codeChallengeMethod: requestObject.codeChallengeMethod + codeChallengeMethod: requestObject.codeChallengeMethod, + nonce: requestObject.nonce ) redirectURI += "?code=\(generatedCode)" } else if requestObject.responseType == ResponseType.idToken || requestObject.responseType == ResponseType.tokenAndIdToken { From 711e9e6f4c9c6422a69c5766dd94d0504399acaa Mon Sep 17 00:00:00 2001 From: Vamsi Madduluri Date: Wed, 10 Jan 2024 15:43:43 +0530 Subject: [PATCH 12/12] Add nonce parameter to generateCode method for tests --- .../DefaultImplementationTests.swift | 3 ++- Tests/VaporOAuthTests/Fakes/FakeCodeManager.swift | 2 +- Tests/VaporOAuthTests/Fakes/StubCodeManager.swift | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Tests/VaporOAuthTests/DefaultImplementationTests/DefaultImplementationTests.swift b/Tests/VaporOAuthTests/DefaultImplementationTests/DefaultImplementationTests.swift index 24fdfe9..48f87e6 100644 --- a/Tests/VaporOAuthTests/DefaultImplementationTests/DefaultImplementationTests.swift +++ b/Tests/VaporOAuthTests/DefaultImplementationTests/DefaultImplementationTests.swift @@ -73,7 +73,8 @@ class DefaultImplementationTests: XCTestCase { redirectURI: "https://api.brokenhands.io/callback", scopes: nil, codeChallenge: "dummyChallenge", - codeChallengeMethod: "S256" + codeChallengeMethod: "S256", + nonce: "nonce" ) // Perform the assertion diff --git a/Tests/VaporOAuthTests/Fakes/FakeCodeManager.swift b/Tests/VaporOAuthTests/Fakes/FakeCodeManager.swift index 11063d6..2178576 100644 --- a/Tests/VaporOAuthTests/Fakes/FakeCodeManager.swift +++ b/Tests/VaporOAuthTests/Fakes/FakeCodeManager.swift @@ -16,7 +16,7 @@ class FakeCodeManager: CodeManager { return deviceCodes[deviceCode] } - func generateCode(userID: String, clientID: String, redirectURI: String, scopes: [String]?, codeChallenge: String?, codeChallengeMethod: String?) throws -> String { + func generateCode(userID: String, clientID: String, redirectURI: String, scopes: [String]?, codeChallenge: String?, codeChallengeMethod: String?, nonce: String?) throws -> String { let code = OAuthCode(codeID: generatedCode, clientID: clientID, redirectURI: redirectURI, userID: userID, expiryDate: Date().addingTimeInterval(60), scopes: scopes, codeChallenge: codeChallenge, codeChallengeMethod: codeChallengeMethod) codes[generatedCode] = code return generatedCode diff --git a/Tests/VaporOAuthTests/Fakes/StubCodeManager.swift b/Tests/VaporOAuthTests/Fakes/StubCodeManager.swift index 0a1be8e..839a400 100644 --- a/Tests/VaporOAuthTests/Fakes/StubCodeManager.swift +++ b/Tests/VaporOAuthTests/Fakes/StubCodeManager.swift @@ -10,7 +10,8 @@ class StubCodeManager: CodeManager { redirectURI: String, scopes: [String]?, codeChallenge: String?, - codeChallengeMethod: String? + codeChallengeMethod: String?, + nonce: String? ) async throws -> String { return codeToReturn }