diff --git a/Sources/Auth/AuthClient.swift b/Sources/Auth/AuthClient.swift index a9c751104..6ea7bc053 100644 --- a/Sources/Auth/AuthClient.swift +++ b/Sources/Auth/AuthClient.swift @@ -1,31 +1,113 @@ import Foundation +import Combine +import WalletConnectUtils +import WalletConnectPairing class AuthClient { enum Errors: Error { case malformedPairingURI + case unknownWalletAddress + case noPairingMatchingTopic + } + private var authRequestPublisherSubject = PassthroughSubject<(id: RPCID, message: String), Never>() + public var authRequestPublisher: AnyPublisher<(id: RPCID, message: String), Never> { + authRequestPublisherSubject.eraseToAnyPublisher() + } + + private var authResponsePublisherSubject = PassthroughSubject<(id: RPCID, cacao: Cacao), Never>() + public var authResponsePublisher: AnyPublisher<(id: RPCID, cacao: Cacao), Never> { + authResponsePublisherSubject.eraseToAnyPublisher() } private let appPairService: AppPairService - private let appRequestService: AuthRequestService + private let appRequestService: AppRequestService + private let appRespondSubscriber: AppRespondSubscriber private let walletPairService: WalletPairService + private let walletRequestSubscriber: WalletRequestSubscriber + private let walletRespondService: WalletRespondService + private let cleanupService: CleanupService + private let pairingStorage: WCPairingStorage + private let pendingRequestsProvider: PendingRequestsProvider + public let logger: ConsoleLogging + + private var account: Account? - init(appPairService: AppPairService, appRequestService: AuthRequestService, walletPairService: WalletPairService) { + init(appPairService: AppPairService, + appRequestService: AppRequestService, + appRespondSubscriber: AppRespondSubscriber, + walletPairService: WalletPairService, + walletRequestSubscriber: WalletRequestSubscriber, + walletRespondService: WalletRespondService, + account: Account?, + pendingRequestsProvider: PendingRequestsProvider, + cleanupService: CleanupService, + logger: ConsoleLogging, + pairingStorage: WCPairingStorage) { self.appPairService = appPairService self.appRequestService = appRequestService self.walletPairService = walletPairService + self.walletRequestSubscriber = walletRequestSubscriber + self.walletRespondService = walletRespondService + self.appRespondSubscriber = appRespondSubscriber + self.account = account + self.pendingRequestsProvider = pendingRequestsProvider + self.cleanupService = cleanupService + self.logger = logger + self.pairingStorage = pairingStorage + + setUpPublishers() } - func request(params: RequestParams) async throws -> String { + public func pair(uri: String) async throws { + guard let pairingURI = WalletConnectURI(string: uri) else { + throw Errors.malformedPairingURI + } + try await walletPairService.pair(pairingURI) + } + + public func request(_ params: RequestParams) async throws -> String { + logger.debug("Requesting Authentication") let uri = try await appPairService.create() try await appRequestService.request(params: params, topic: uri.topic) return uri.absoluteString } - func pair(uri: String) async throws { - guard let pairingURI = WalletConnectURI(string: uri) else { - throw Errors.malformedPairingURI + public func request(_ params: RequestParams, topic: String) async throws { + logger.debug("Requesting Authentication on existing pairing") + guard pairingStorage.hasPairing(forTopic: topic) else { + throw Errors.noPairingMatchingTopic + } + try await appRequestService.request(params: params, topic: topic) + } + + public func respond(_ params: RespondParams) async throws { + guard let account = account else { throw Errors.unknownWalletAddress } + try await walletRespondService.respond(respondParams: params, account: account) + } + + public func getPendingRequests() throws -> [AuthRequest] { + guard let account = account else { throw Errors.unknownWalletAddress } + return try pendingRequestsProvider.getPendingRequests(account: account) + } + +#if DEBUG + /// Delete all stored data sach as: pairings, sessions, keys + /// + /// - Note: Doesn't unsubscribe from topics + public func cleanup() throws { + try cleanupService.cleanup() + } +#endif + + private func setUpPublishers() { + appRespondSubscriber.onResponse = { [unowned self] (id, cacao) in + authResponsePublisherSubject.send((id, cacao)) + } + + walletRequestSubscriber.onRequest = { [unowned self] (id, message) in + authRequestPublisherSubject.send((id, message)) } - try await walletPairService.pair(pairingURI) } } + diff --git a/Sources/Auth/Services/App/AuthRequestService.swift b/Sources/Auth/Services/App/AppRequestService.swift similarity index 97% rename from Sources/Auth/Services/App/AuthRequestService.swift rename to Sources/Auth/Services/App/AppRequestService.swift index b82d5259b..1220a7155 100644 --- a/Sources/Auth/Services/App/AuthRequestService.swift +++ b/Sources/Auth/Services/App/AppRequestService.swift @@ -3,7 +3,7 @@ import WalletConnectUtils import WalletConnectKMS import JSONRPC -actor AuthRequestService { +actor AppRequestService { private let networkingInteractor: NetworkInteracting private let appMetadata: AppMetadata private let kms: KeyManagementService diff --git a/Sources/Auth/Services/App/AuthRespondSubscriber.swift b/Sources/Auth/Services/App/AppRespondSubscriber.swift similarity index 98% rename from Sources/Auth/Services/App/AuthRespondSubscriber.swift rename to Sources/Auth/Services/App/AppRespondSubscriber.swift index a6f1f8b57..d8695b98a 100644 --- a/Sources/Auth/Services/App/AuthRespondSubscriber.swift +++ b/Sources/Auth/Services/App/AppRespondSubscriber.swift @@ -3,7 +3,7 @@ import Foundation import WalletConnectUtils import JSONRPC -class AuthRespondSubscriber { +class AppRespondSubscriber { private let networkingInteractor: NetworkInteracting private let logger: ConsoleLogging private let rpcHistory: RPCHistory diff --git a/Sources/Auth/Services/Common/CacaoFormatter.swift b/Sources/Auth/Services/Common/CacaoFormatter.swift index 43ddb9058..c35286b87 100644 --- a/Sources/Auth/Services/Common/CacaoFormatter.swift +++ b/Sources/Auth/Services/Common/CacaoFormatter.swift @@ -2,11 +2,11 @@ import Foundation import WalletConnectUtils protocol CacaoFormatting { - func format(_ request: AuthRequestParams, _ signature: CacaoSignature, _ issuer: Account) -> Cacao + func format(_ request: AuthRequestParams, _ signature: CacaoSignature, _ account: Account) -> Cacao } class CacaoFormatter: CacaoFormatting { - func format(_ request: AuthRequestParams, _ signature: CacaoSignature, _ issuer: Account) -> Cacao { + func format(_ request: AuthRequestParams, _ signature: CacaoSignature, _ account: Account) -> Cacao { fatalError("not implemented") } } diff --git a/Sources/Auth/Services/Common/CleanupService.swift b/Sources/Auth/Services/Common/CleanupService.swift new file mode 100644 index 000000000..dac2b63c5 --- /dev/null +++ b/Sources/Auth/Services/Common/CleanupService.swift @@ -0,0 +1,20 @@ +import Foundation +import WalletConnectKMS +import WalletConnectUtils +import WalletConnectPairing + +final class CleanupService { + + private let pairingStore: WCPairingStorage + private let kms: KeyManagementServiceProtocol + + init(pairingStore: WCPairingStorage, kms: KeyManagementServiceProtocol, sessionToPairingTopic: CodableStore) { + self.pairingStore = pairingStore + self.kms = kms + } + + func cleanup() throws { + pairingStore.deleteAll() + try kms.deleteAll() + } +} diff --git a/Sources/Auth/Services/Wallet/PendingRequestsProvider.swift b/Sources/Auth/Services/Wallet/PendingRequestsProvider.swift new file mode 100644 index 000000000..16fe22128 --- /dev/null +++ b/Sources/Auth/Services/Wallet/PendingRequestsProvider.swift @@ -0,0 +1,22 @@ +import Foundation +import JSONRPC +import WalletConnectUtils + +class PendingRequestsProvider { + private let rpcHistory: RPCHistory + + init(rpcHistory: RPCHistory) { + self.rpcHistory = rpcHistory + } + + public func getPendingRequests(account: Account) throws -> [AuthRequest] { + let pendingRequests: [AuthRequest] = rpcHistory.getPending() + .filter {$0.request.method == "wc_authRequest"} + .compactMap { + guard let params = try? $0.request.params?.get(AuthRequestParams.self) else {return nil} + let message = SIWEMessageFormatter().formatMessage(from: params.payloadParams, address: account.address) + return AuthRequest(id: $0.request.id!, message: message) + } + return pendingRequests + } +} diff --git a/Sources/Auth/Services/Wallet/AuthRequestSubscriber.swift b/Sources/Auth/Services/Wallet/WalletRequestSubscriber.swift similarity index 76% rename from Sources/Auth/Services/Wallet/AuthRequestSubscriber.swift rename to Sources/Auth/Services/Wallet/WalletRequestSubscriber.swift index e4acc5b04..54819c1da 100644 --- a/Sources/Auth/Services/Wallet/AuthRequestSubscriber.swift +++ b/Sources/Auth/Services/Wallet/WalletRequestSubscriber.swift @@ -3,7 +3,7 @@ import Foundation import WalletConnectUtils import JSONRPC -class AuthRequestSubscriber { +class WalletRequestSubscriber { private let networkingInteractor: NetworkInteracting private let logger: ConsoleLogging private let address: String @@ -29,13 +29,9 @@ class AuthRequestSubscriber { logger.debug("Malformed auth request params") return } - do { - let message = try messageFormatter.formatMessage(from: authRequestParams.payloadParams, address: address) - guard let requestId = subscriptionPayload.request.id else { return } - onRequest?(requestId, message) - } catch { - logger.debug(error) - } + let message = messageFormatter.formatMessage(from: authRequestParams.payloadParams, address: address) + guard let requestId = subscriptionPayload.request.id else { return } + onRequest?(requestId, message) }.store(in: &publishers) } diff --git a/Sources/Auth/Services/Wallet/AuthRespondService.swift b/Sources/Auth/Services/Wallet/WalletRespondService.swift similarity index 92% rename from Sources/Auth/Services/Wallet/AuthRespondService.swift rename to Sources/Auth/Services/Wallet/WalletRespondService.swift index 006459d37..81a37db03 100644 --- a/Sources/Auth/Services/Wallet/AuthRespondService.swift +++ b/Sources/Auth/Services/Wallet/WalletRespondService.swift @@ -3,7 +3,7 @@ import WalletConnectKMS import JSONRPC import WalletConnectUtils -actor AuthRespondService { +actor WalletRespondService { enum Errors: Error { case recordForIdNotFound case malformedAuthRequestParams @@ -23,7 +23,7 @@ actor AuthRespondService { self.rpcHistory = rpcHistory } - func respond(respondParams: RespondParams, issuer: Account) async throws { + func respond(respondParams: RespondParams, account: Account) async throws { guard let request = rpcHistory.get(recordId: RPCID(respondParams.id))?.request else { throw Errors.recordForIdNotFound } guard let authRequestParams = try? request.params?.get(AuthRequestParams.self) else { throw Errors.malformedAuthRequestParams } @@ -33,7 +33,7 @@ actor AuthRespondService { let agreementKeys = try kms.performKeyAgreement(selfPublicKey: selfPubKey, peerPublicKey: peerPubKey) try kms.setAgreementSecret(agreementKeys, topic: responseTopic) - let cacao = CacaoFormatter().format(authRequestParams, respondParams.signature, issuer) + let cacao = CacaoFormatter().format(authRequestParams, respondParams.signature, account) let response = RPCResponse(id: request.id!, result: cacao) try await networkingInteractor.respond(topic: respondParams.topic, response: response, tag: AuthResponseParams.tag, envelopeType: .type1(pubKey: selfPubKey.rawRepresentation)) diff --git a/Sources/Auth/Types/Aliases/Account.swift b/Sources/Auth/Types/Aliases/Account.swift new file mode 100644 index 000000000..84759e639 --- /dev/null +++ b/Sources/Auth/Types/Aliases/Account.swift @@ -0,0 +1,4 @@ +import WalletConnectUtils +import Foundation + +typealias Account = WalletConnectUtils.Account diff --git a/Sources/Auth/Types/AppMetadata.swift b/Sources/Auth/Types/Aliases/AppMetadata.swift similarity index 100% rename from Sources/Auth/Types/AppMetadata.swift rename to Sources/Auth/Types/Aliases/AppMetadata.swift diff --git a/Sources/Auth/Types/Aliases/RPCID.swift b/Sources/Auth/Types/Aliases/RPCID.swift new file mode 100644 index 000000000..51cb31cdd --- /dev/null +++ b/Sources/Auth/Types/Aliases/RPCID.swift @@ -0,0 +1,4 @@ +import Foundation +import JSONRPC + +public typealias RPCID = JSONRPC.RPCID diff --git a/Sources/Auth/Types/WalletConnectURI.swift b/Sources/Auth/Types/Aliases/WalletConnectURI.swift similarity index 100% rename from Sources/Auth/Types/WalletConnectURI.swift rename to Sources/Auth/Types/Aliases/WalletConnectURI.swift diff --git a/Sources/Auth/Types/Public/AuthRequest.swift b/Sources/Auth/Types/Public/AuthRequest.swift new file mode 100644 index 000000000..f431ccb4d --- /dev/null +++ b/Sources/Auth/Types/Public/AuthRequest.swift @@ -0,0 +1,7 @@ +import Foundation + +public struct AuthRequest: Equatable, Codable { + public let id: RPCID + /// EIP-4361: Sign-In with Ethereum message + public let message: String +} diff --git a/Sources/WalletConnectSign/Sign/SignClient.swift b/Sources/WalletConnectSign/Sign/SignClient.swift index 1dc88cae2..43df10383 100644 --- a/Sources/WalletConnectSign/Sign/SignClient.swift +++ b/Sources/WalletConnectSign/Sign/SignClient.swift @@ -3,9 +3,6 @@ import WalletConnectRelay import WalletConnectUtils import WalletConnectKMS import Combine -#if os(iOS) -import UIKit -#endif /// An Object that expose public API to provide interactions with WalletConnect SDK /// diff --git a/Sources/WalletConnectUtils/RPCHistory.swift b/Sources/WalletConnectUtils/RPCHistory.swift index 8112cacc3..d0707688d 100644 --- a/Sources/WalletConnectUtils/RPCHistory.swift +++ b/Sources/WalletConnectUtils/RPCHistory.swift @@ -64,4 +64,8 @@ public final class RPCHistory { } } } + + public func getPending() -> [Record] { + storage.getAll().filter {$0.response == nil} + } } diff --git a/Tests/AuthTests/AuthRequstSubscriberTests.swift b/Tests/AuthTests/AuthRequstSubscriberTests.swift index 990a9cb4f..dbf7bba20 100644 --- a/Tests/AuthTests/AuthRequstSubscriberTests.swift +++ b/Tests/AuthTests/AuthRequstSubscriberTests.swift @@ -8,14 +8,14 @@ import JSONRPC class AuthRequstSubscriberTests: XCTestCase { var networkingInteractor: NetworkingInteractorMock! - var sut: AuthRequestSubscriber! + var sut: WalletRequestSubscriber! var messageFormatter: SIWEMessageFormatterMock! let defaultTimeout: TimeInterval = 0.01 override func setUp() { networkingInteractor = NetworkingInteractorMock() messageFormatter = SIWEMessageFormatterMock() - sut = AuthRequestSubscriber(networkingInteractor: networkingInteractor, + sut = WalletRequestSubscriber(networkingInteractor: networkingInteractor, logger: ConsoleLoggerMock(), messageFormatter: messageFormatter, address: "") }