Skip to content

Commit

Permalink
Merge pull request #679 from WalletConnect/feature/sign-session-expiry
Browse files Browse the repository at this point in the history
[Sign] Session request expiry
  • Loading branch information
flypaper0 authored Jan 24, 2023
2 parents 0a66de3 + d788d16 commit 7d5d528
Show file tree
Hide file tree
Showing 14 changed files with 350 additions and 34 deletions.
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

0 comments on commit 7d5d528

Please sign in to comment.