Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Sign] Session request expiry #679

Merged
merged 6 commits into from
Jan 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1410"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "WalletConnectSignTests"
BuildableName = "WalletConnectSignTests"
BlueprintName = "WalletConnectSignTests"
ReferencedContainer = "container:">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
4 changes: 2 additions & 2 deletions Example/IntegrationTests/Sign/SignClientTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ final class SignClientTests: XCTestCase {
}
dapp.onSessionSettled = { [unowned self] settledSession in
Task(priority: .high) {
let request = Request(id: RPCID(0), topic: settledSession.topic, method: requestMethod, params: requestParams, chainId: chain)
let request = Request(id: RPCID(0), topic: settledSession.topic, method: requestMethod, params: requestParams, chainId: chain, expiry: nil)
try await dapp.client.request(params: request)
}
}
Expand Down Expand Up @@ -230,7 +230,7 @@ final class SignClientTests: XCTestCase {
}
dapp.onSessionSettled = { [unowned self] settledSession in
Task(priority: .high) {
let request = Request(id: RPCID(0), topic: settledSession.topic, method: requestMethod, params: requestParams, chainId: chain)
let request = Request(id: RPCID(0), topic: settledSession.topic, method: requestMethod, params: requestParams, chainId: chain, expiry: nil)
try await dapp.client.request(params: request)
}
}
Expand Down
44 changes: 39 additions & 5 deletions Sources/WalletConnectSign/Engine/Common/SessionEngine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Combine
final class SessionEngine {
enum Errors: Error {
case sessionNotFound(topic: String)
case sessionRequestExpired
}

var onSessionsUpdate: (([Session]) -> Void)?
Expand All @@ -15,17 +16,20 @@ final class SessionEngine {

private let sessionStore: WCSessionStorage
private let networkingInteractor: NetworkInteracting
private let historyService: HistoryService
private let kms: KeyManagementServiceProtocol
private var publishers = [AnyCancellable]()
private let logger: ConsoleLogging

init(
networkingInteractor: NetworkInteracting,
historyService: HistoryService,
kms: KeyManagementServiceProtocol,
sessionStore: WCSessionStorage,
logger: ConsoleLogging
) {
self.networkingInteractor = networkingInteractor
self.historyService = historyService
self.kms = kms
self.sessionStore = sessionStore
self.logger = logger
Expand Down Expand Up @@ -54,9 +58,9 @@ final class SessionEngine {
guard session.hasPermission(forMethod: request.method, onChain: request.chainId) else {
throw WalletConnectError.invalidPermissions
}
let chainRequest = SessionType.RequestParams.Request(method: request.method, params: request.params)
let chainRequest = SessionType.RequestParams.Request(method: request.method, params: request.params, expiry: request.expiry)
let sessionRequestParams = SessionType.RequestParams(request: chainRequest, chainId: request.chainId)
let protocolMethod = SessionRequestProtocolMethod()
let protocolMethod = SessionRequestProtocolMethod(ttl: request.calculateTtl())
let rpcRequest = RPCRequest(method: protocolMethod.method, params: sessionRequestParams)
try await networkingInteractor.request(rpcRequest, topic: request.topic, protocolMethod: SessionRequestProtocolMethod())
}
Expand All @@ -65,8 +69,24 @@ final class SessionEngine {
guard sessionStore.hasSession(forTopic: topic) else {
throw Errors.sessionNotFound(topic: topic)
}
let response = RPCResponse(id: requestId, outcome: response)
try await networkingInteractor.respond(topic: topic, response: response, protocolMethod: SessionRequestProtocolMethod())

let protocolMethod = SessionRequestProtocolMethod()

guard sessionRequestNotExpired(requestId: requestId) else {
try await networkingInteractor.respondError(
topic: topic,
requestId: requestId,
protocolMethod: protocolMethod,
reason: SignReasonCode.sessionRequestExpired
)
throw Errors.sessionRequestExpired
}

try await networkingInteractor.respond(
topic: topic,
response: RPCResponse(id: requestId, outcome: response),
protocolMethod: protocolMethod
)
}

func emit(topic: String, event: SessionType.EventParams.Event, chainId: Blockchain) async throws {
Expand Down Expand Up @@ -159,6 +179,13 @@ private extension SessionEngine {
}
}

func sessionRequestNotExpired(requestId: RPCID) -> Bool {
guard let request = historyService.getSessionRequest(id: requestId)
else { return false }

return !request.isExpired()
}

func respondError(payload: SubscriptionPayload, reason: SignReasonCode, protocolMethod: ProtocolMethod) {
Task(priority: .high) {
do {
Expand Down Expand Up @@ -191,7 +218,9 @@ private extension SessionEngine {
topic: payload.topic,
method: payload.request.request.method,
params: payload.request.request.params,
chainId: payload.request.chainId)
chainId: payload.request.chainId,
expiry: payload.request.request.expiry
)

guard let session = sessionStore.getSession(forTopic: topic) else {
return respondError(payload: payload, reason: .noSessionForTopic, protocolMethod: protocolMethod)
Expand All @@ -202,6 +231,11 @@ private extension SessionEngine {
guard session.hasPermission(forMethod: request.method, onChain: request.chainId) else {
return respondError(payload: payload, reason: .unauthorizedMethod(request.method), protocolMethod: protocolMethod)
}

guard !request.isExpired() else {
return respondError(payload: payload, reason: .sessionRequestExpired, protocolMethod: protocolMethod)
}

onSessionRequest?(request)
}

Expand Down
47 changes: 42 additions & 5 deletions Sources/WalletConnectSign/Request.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,57 @@ public struct Request: Codable, Equatable {
public let method: String
public let params: AnyCodable
public let chainId: Blockchain
public let expiry: UInt64?

internal init(id: RPCID, topic: String, method: String, params: AnyCodable, chainId: Blockchain) {
internal init(id: RPCID, topic: String, method: String, params: AnyCodable, chainId: Blockchain, expiry: UInt64?) {
self.id = id
self.topic = topic
self.method = method
self.params = params
self.chainId = chainId
self.expiry = expiry
}

public init(topic: String, method: String, params: AnyCodable, chainId: Blockchain) {
self.init(id: RPCID(JsonRpcID.generate()), topic: topic, method: method, params: params, chainId: chainId)
public init(topic: String, method: String, params: AnyCodable, chainId: Blockchain, expiry: UInt64? = nil) {
self.init(id: RPCID(JsonRpcID.generate()), topic: topic, method: method, params: params, chainId: chainId, expiry: expiry)
}

internal init<C>(id: RPCID, topic: String, method: String, params: C, chainId: Blockchain) where C: Codable {
self.init(id: id, topic: topic, method: method, params: AnyCodable(params), chainId: chainId)
init<C>(id: RPCID, topic: String, method: String, params: C, chainId: Blockchain, expiry: UInt64?) where C: Codable {
self.init(id: id, topic: topic, method: method, params: AnyCodable(params), chainId: chainId, expiry: expiry)
}

func isExpired(currentDate: Date = Date()) -> Bool {
guard let expiry = expiry else { return false }

let expiryDate = Date(timeIntervalSince1970: TimeInterval(expiry))

guard
abs(currentDate.distance(to: expiryDate)) < Constants.maxExpiry,
abs(currentDate.distance(to: expiryDate)) > Constants.minExpiry
else { return true }

return expiryDate < currentDate
}

func calculateTtl(currentDate: Date = Date()) -> Int {
guard let expiry = expiry else { return SessionRequestProtocolMethod.defaultTtl }

let expiryDate = Date(timeIntervalSince1970: TimeInterval(expiry))
let diff = expiryDate - currentDate.timeIntervalSince1970

guard
diff.timeIntervalSince1970 < Constants.maxExpiry,
diff.timeIntervalSince1970 > Constants.minExpiry
else { return SessionRequestProtocolMethod.defaultTtl }

return Int(diff.timeIntervalSince1970)
}
}

private extension Request {

struct Constants {
static let minExpiry: TimeInterval = 300 // 5 minutes
static let maxExpiry: TimeInterval = 604800 // 7 days
}
}
42 changes: 42 additions & 0 deletions Sources/WalletConnectSign/Services/HistoryService.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import Foundation

final class HistoryService {

private let history: RPCHistory

init(history: RPCHistory) {
self.history = history
}

func getPendingRequests() -> [Request] {
return history.getPending()
.compactMap { mapRequestRecord($0) }
.filter { !$0.isExpired() }
}

func getPendingRequests(topic: String) -> [Request] {
return getPendingRequests().filter { $0.topic == topic }
}

public func getSessionRequest(id: RPCID) -> Request? {
guard let record = history.get(recordId: id) else { return nil }
return mapRequestRecord(record)
}
}

private extension HistoryService {

func mapRequestRecord(_ record: RPCHistory.Record) -> Request? {
guard let request = try? record.request.params?.get(SessionType.RequestParams.self)
else { return nil }

return Request(
id: record.id,
topic: record.topic,
method: request.request.method,
params: request.request.params,
chainId: request.chainId,
expiry: request.request.expiry
)
}
}
22 changes: 6 additions & 16 deletions Sources/WalletConnectSign/Sign/SignClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ public final class SignClient: SignClientProtocol {
private let nonControllerSessionStateMachine: NonControllerSessionStateMachine
private let controllerSessionStateMachine: ControllerSessionStateMachine
private let appProposeService: AppProposeService
private let history: RPCHistory
private let historyService: HistoryService
private let cleanupService: SignCleanupService

private let sessionProposalPublisherSubject = PassthroughSubject<Session.Proposal, Never>()
Expand Down Expand Up @@ -140,7 +140,7 @@ public final class SignClient: SignClientProtocol {
controllerSessionStateMachine: ControllerSessionStateMachine,
appProposeService: AppProposeService,
disconnectService: DisconnectService,
history: RPCHistory,
historyService: HistoryService,
cleanupService: SignCleanupService,
pairingClient: PairingClient
) {
Expand All @@ -153,7 +153,7 @@ public final class SignClient: SignClientProtocol {
self.nonControllerSessionStateMachine = nonControllerSessionStateMachine
self.controllerSessionStateMachine = controllerSessionStateMachine
self.appProposeService = appProposeService
self.history = history
self.historyService = historyService
self.cleanupService = cleanupService
self.disconnectService = disconnectService
self.pairingClient = pairingClient
Expand Down Expand Up @@ -323,27 +323,17 @@ public final class SignClient: SignClientProtocol {
/// - Returns: Pending requests received from peer with `wc_sessionRequest` protocol method
/// - Parameter topic: topic representing session for which you want to get pending requests. If nil, you will receive pending requests for all active sessions.
public func getPendingRequests(topic: String? = nil) -> [Request] {
let pendingRequests: [Request] = history.getPending()
.compactMap {
guard let request = try? $0.request.params?.get(SessionType.RequestParams.self) else { return nil }
return Request(id: $0.id, topic: $0.topic, method: request.request.method, params: request.request.params, chainId: request.chainId)
}
if let topic = topic {
return pendingRequests.filter {$0.topic == topic}
return historyService.getPendingRequests(topic: topic)
} else {
return pendingRequests
return historyService.getPendingRequests()
}
}

/// - Parameter id: id of a wc_sessionRequest jsonrpc request
/// - Returns: json rpc record object for given id or nil if record for give id does not exits
public func getSessionRequestRecord(id: RPCID) -> Request? {
guard
let record = history.get(recordId: id),
let request = try? record.request.params?.get(SessionType.RequestParams.self)
else { return nil }

return Request(id: record.id, topic: record.topic, method: record.request.method, params: request, chainId: request.chainId)
return historyService.getSessionRequest(id: id)
}

/// Delete all stored data such as: pairings, sessions, keys
Expand Down
5 changes: 3 additions & 2 deletions Sources/WalletConnectSign/Sign/SignClientFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ public struct SignClientFactory {
let sessionStore = SessionStorage(storage: SequenceStore<WCSession>(store: .init(defaults: keyValueStorage, identifier: SignStorageIdentifiers.sessions.rawValue)))
let sessionToPairingTopic = CodableStore<String>(defaults: RuntimeKeyValueStorage(), identifier: SignStorageIdentifiers.sessionToPairingTopic.rawValue)
let proposalPayloadsStore = CodableStore<RequestSubscriptionPayload<SessionType.ProposeParams>>(defaults: RuntimeKeyValueStorage(), identifier: SignStorageIdentifiers.proposals.rawValue)
let sessionEngine = SessionEngine(networkingInteractor: networkingClient, kms: kms, sessionStore: sessionStore, logger: logger)
let historyService = HistoryService(history: rpcHistory)
let sessionEngine = SessionEngine(networkingInteractor: networkingClient, historyService: historyService, kms: kms, sessionStore: sessionStore, logger: logger)
let nonControllerSessionStateMachine = NonControllerSessionStateMachine(networkingInteractor: networkingClient, kms: kms, sessionStore: sessionStore, logger: logger)
let controllerSessionStateMachine = ControllerSessionStateMachine(networkingInteractor: networkingClient, kms: kms, sessionStore: sessionStore, logger: logger)
let approveEngine = ApproveEngine(networkingInteractor: networkingClient, proposalPayloadsStore: proposalPayloadsStore, sessionToPairingTopic: sessionToPairingTopic, pairingRegisterer: pairingClient, metadata: metadata, kms: kms, logger: logger, pairingStore: pairingStore, sessionStore: sessionStore)
Expand All @@ -47,7 +48,7 @@ public struct SignClientFactory {
controllerSessionStateMachine: controllerSessionStateMachine,
appProposeService: appProposerService,
disconnectService: disconnectService,
history: rpcHistory,
historyService: historyService,
cleanupService: cleanupService,
pairingClient: pairingClient
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
import Foundation

struct SessionRequestProtocolMethod: ProtocolMethod {

static let defaultTtl: Int = 300

let method: String = "wc_sessionRequest"

let requestConfig = RelayConfig(tag: 1108, prompt: true, ttl: 300)
private let ttl: Int

var requestConfig: RelayConfig {
RelayConfig(tag: 1108, prompt: true, ttl: ttl)
}

var responseConfig: RelayConfig {
RelayConfig(tag: 1109, prompt: false, ttl: ttl)
}

let responseConfig = RelayConfig(tag: 1109, prompt: false, ttl: 300)
init(ttl: Int = Self.defaultTtl) {
self.ttl = ttl
}
}
1 change: 1 addition & 0 deletions Sources/WalletConnectSign/Types/Session/SessionType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ internal enum SessionType {
struct Request: Codable, Equatable {
let method: String
let params: AnyCodable
let expiry: UInt64?
}
}

Expand Down
Loading