Skip to content

Commit

Permalink
Merge pull request #616 from WalletConnect/feature/auth-multi-account
Browse files Browse the repository at this point in the history
[Auth] Multi account support
  • Loading branch information
flypaper0 authored Dec 7, 2022
2 parents 20d11b7 + ed8372b commit b2ef792
Show file tree
Hide file tree
Showing 23 changed files with 212 additions and 143 deletions.
24 changes: 13 additions & 11 deletions Example/IntegrationTests/Auth/AuthTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ final class AuthTests: XCTestCase {

var appAuthClient: AuthClient!
var walletAuthClient: AuthClient!

let walletAccount = Account(chainIdentifier: "eip155:1", address: "0x724d0D2DaD3fbB0C168f947B87Fa5DBe36F1A8bf")!
let prvKey = Data(hex: "462c1dad6832d7d96ccf87bd6a686a4110e114aaaebd5512e552c0e3a87b480f")
let eip1271Signature = "0xc1505719b2504095116db01baaf276361efd3a73c28cf8cc28dabefa945b8d536011289ac0a3b048600c1e692ff173ca944246cf7ceb319ac2262d27b395c82b1c"
private var publishers = [AnyCancellable]()
Expand All @@ -22,13 +24,12 @@ final class AuthTests: XCTestCase {
setupClients()
}

private func setupClients(address: String = "0x724d0D2DaD3fbB0C168f947B87Fa5DBe36F1A8bf", iatProvider: IATProvider = DefaultIATProvider()) {
let walletAccount = Account(chainIdentifier: "eip155:1", address: address)!
private func setupClients(iatProvider: IATProvider = DefaultIATProvider()) {
(appPairingClient, appAuthClient) = makeClients(prefix: "🤖 App", iatProvider: iatProvider)
(walletPairingClient, walletAuthClient) = makeClients(prefix: "🐶 Wallet", account: walletAccount, iatProvider: iatProvider)
(walletPairingClient, walletAuthClient) = makeClients(prefix: "🐶 Wallet", iatProvider: iatProvider)
}

func makeClients(prefix: String, account: Account? = nil, iatProvider: IATProvider) -> (PairingClient, AuthClient) {
func makeClients(prefix: String, iatProvider: IATProvider) -> (PairingClient, AuthClient) {
let logger = ConsoleLogger(suffix: prefix, loggingLevel: .debug)
let keychain = KeychainStorageMock()
let relayClient = RelayClient(relayHost: InputConfig.relayHost, projectId: InputConfig.projectId, keychainStorage: keychain, socketFactory: DefaultSocketFactory(), logger: logger)
Expand All @@ -48,7 +49,6 @@ final class AuthTests: XCTestCase {

let authClient = AuthClientFactory.create(
metadata: AppMetadata(name: name, description: "", url: "", icons: [""]),
account: account,
projectId: InputConfig.projectId,
signerFactory: DefaultSignerFactory(),
logger: logger,
Expand Down Expand Up @@ -83,8 +83,8 @@ final class AuthTests: XCTestCase {
Task(priority: .high) {
let signerFactory = DefaultSignerFactory()
let signer = MessageSignerFactory(signerFactory: signerFactory).create(projectId: InputConfig.projectId)
let signature = try! signer.sign(message: request.message, privateKey: prvKey, type: .eip191)
try! await walletAuthClient.respond(requestId: request.id, signature: signature)
let signature = try! signer.sign(payload: request.payload, address: walletAccount.address, privateKey: prvKey, type: .eip191)
try! await walletAuthClient.respond(requestId: request.id, signature: signature, from: walletAccount)
}
}
.store(in: &publishers)
Expand All @@ -97,7 +97,9 @@ final class AuthTests: XCTestCase {
}

func testEIP1271RespondSuccess() async {
setupClients(address: "0x2faf83c542b68f1b4cdc0e770e8cb9f567b08f71", iatProvider: IATProviderMock())
setupClients(iatProvider: IATProviderMock())

let account = Account(chainIdentifier: "eip155:1", address: "0x2faf83c542b68f1b4cdc0e770e8cb9f567b08f71")!

let responseExpectation = expectation(description: "successful response delivered")
let uri = try! await appPairingClient.create()
Expand All @@ -117,7 +119,7 @@ final class AuthTests: XCTestCase {
walletAuthClient.authRequestPublisher.sink { [unowned self] request in
Task(priority: .high) {
let signature = CacaoSignature(t: .eip1271, s: eip1271Signature)
try! await walletAuthClient.respond(requestId: request.id, signature: signature)
try! await walletAuthClient.respond(requestId: request.id, signature: signature, from: account)
}
}
.store(in: &publishers)
Expand All @@ -138,7 +140,7 @@ final class AuthTests: XCTestCase {
walletAuthClient.authRequestPublisher.sink { [unowned self] request in
Task(priority: .high) {
let signature = CacaoSignature(t: .eip1271, s: eip1271Signature)
try! await walletAuthClient.respond(requestId: request.id, signature: signature)
try! await walletAuthClient.respond(requestId: request.id, signature: signature, from: walletAccount)
}
}
.store(in: &publishers)
Expand Down Expand Up @@ -181,7 +183,7 @@ final class AuthTests: XCTestCase {
Task(priority: .high) {
let invalidSignature = "438effc459956b57fcd9f3dac6c675f9cee88abf21acab7305e8e32aa0303a883b06dcbd956279a7a2ca21ffa882ff55cc22e8ab8ec0f3fe90ab45f306938cfa1b"
let cacaoSignature = CacaoSignature(t: .eip191, s: invalidSignature)
try! await walletAuthClient.respond(requestId: request.id, signature: cacaoSignature)
try! await walletAuthClient.respond(requestId: request.id, signature: cacaoSignature, from: walletAccount)
}
}
.store(in: &publishers)
Expand Down
23 changes: 22 additions & 1 deletion Example/IntegrationTests/Auth/Signer/CacaoSignerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,31 @@ class CacaoSignerTest: XCTestCase {
- https://example.com/my-web2-claim.json
"""

let payload = AuthPayload(requestParams: RequestParams(
domain: "service.invalid",
chainId: "eip155:1",
nonce: "32891756",
aud: "https://service.invalid/login",
nbf: nil,
exp: nil,
statement: "I accept the ServiceOrg Terms of Service: https://service.invalid/tos",
requestId: nil,
resources: [
"ipfs://bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq/",
"https://example.com/my-web2-claim.json"
]
), iat: "2021-09-30T16:25:24Z")

let signature = CacaoSignature(t: .eip191, s: "0x438effc459956b57fcd9f3dac6c675f9cee88abf21acab7305e8e32aa0303a883b06dcbd956279a7a2ca21ffa882ff55cc22e8ab8ec0f3fe90ab45f306938cfa1b")

func testCacaoSign() throws {
XCTAssertEqual(try signer.sign(message: message, privateKey: privateKey, type: .eip191), signature)
let address = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
let formatted = try SIWEMessageFormatter().formatMessage(
from: payload,
address: address
)
XCTAssertEqual(formatted, message)
XCTAssertEqual(try signer.sign(payload: payload, address: address, privateKey: privateKey, type: .eip191), signature)
}

func testCacaoVerify() async throws {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,6 @@ struct ThirdPartyConfigurator: Configurator {
icons: ["https://avatars.githubusercontent.com/u/37784886"]
))

Auth.configure(
account: Account("eip155:1:0xe5EeF1368781911d265fDB6946613dA61915a501")!,
signerFactory: DefaultSignerFactory()
)
Auth.configure(signerFactory: DefaultSignerFactory())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,27 @@ import WalletConnectUtils

final class AuthRequestInteractor {

private let signer = MessageSignerFactory(signerFactory: DefaultSignerFactory()).create()
private let account = Account("eip155:1:0xe5EeF1368781911d265fDB6946613dA61915a501")!

func approve(request: AuthRequest) async throws {
let privateKey = Data(hex: "e56da0e170b5e09a8bb8f1b693392c7d56c3739a9c75740fbc558a2877868540")
let signer = MessageSignerFactory(signerFactory: DefaultSignerFactory()).create()
let signature = try signer.sign(message: request.message, privateKey: privateKey, type: .eip191)
try await Auth.instance.respond(requestId: request.id, signature: signature)
let signature = try signer.sign(
payload: request.payload,
address: account.address,
privateKey: privateKey,
type: .eip191)
try await Auth.instance.respond(requestId: request.id, signature: signature, from: account)
}

func reject(request: AuthRequest) async throws {
try await Auth.instance.reject(requestId: request.id)
}

func formatted(request: AuthRequest) -> String {
return try! Auth.instance.formatMessage(
payload: request.payload,
address: account.address
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ final class AuthRequestPresenter: ObservableObject {
}

var message: String {
return request.message
return interactor.formatted(request: request)
}

@MainActor
Expand Down
11 changes: 1 addition & 10 deletions Sources/Auth/Auth.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ public class Auth {
}
return AuthClientFactory.create(
metadata: Pair.metadata,
account: config.account,
projectId: Networking.projectId,
signerFactory: config.signerFactory,
networkingClient: Networking.interactor,
Expand All @@ -33,18 +32,10 @@ public class Auth {

private init() { }

/// Auth instance wallet config method. For Wallet usage
/// - Parameters:
/// - account: account that wallet will be authenticating with.
/// - signerFactory: Auth signers factory
static public func configure(account: Account, signerFactory: SignerFactory) {
Auth.config = Auth.Config(account: account, signerFactory: signerFactory)
}

/// Auth instance wallet config method. For DApp usage
/// - Parameters:
/// - signerFactory: Auth signers factory
static public func configure(signerFactory: SignerFactory) {
Auth.config = Auth.Config(account: nil, signerFactory: signerFactory)
Auth.config = Auth.Config(signerFactory: signerFactory)
}
}
16 changes: 6 additions & 10 deletions Sources/Auth/AuthClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@ import Combine
///
/// Access via `Auth.instance`
public class AuthClient {
enum Errors: Error {
case unknownWalletAddress
}

// MARK: - Public Properties

Expand Down Expand Up @@ -46,13 +43,11 @@ public class AuthClient {
private let walletRequestSubscriber: WalletRequestSubscriber
private let walletRespondService: WalletRespondService
private let pendingRequestsProvider: PendingRequestsProvider
private var account: Account?

init(appRequestService: AppRequestService,
appRespondSubscriber: AppRespondSubscriber,
walletRequestSubscriber: WalletRequestSubscriber,
walletRespondService: WalletRespondService,
account: Account?,
pendingRequestsProvider: PendingRequestsProvider,
logger: ConsoleLogging,
socketConnectionStatusPublisher: AnyPublisher<SocketConnectionStatus, Never>,
Expand All @@ -62,7 +57,6 @@ public class AuthClient {
self.walletRequestSubscriber = walletRequestSubscriber
self.walletRespondService = walletRespondService
self.appRespondSubscriber = appRespondSubscriber
self.account = account
self.pendingRequestsProvider = pendingRequestsProvider
self.logger = logger
self.socketConnectionStatusPublisher = socketConnectionStatusPublisher
Expand All @@ -83,8 +77,7 @@ public class AuthClient {
/// - Parameters:
/// - requestId: authentication request id
/// - signature: CACAO signature of requested message
public func respond(requestId: RPCID, signature: CacaoSignature) async throws {
guard let account = account else { throw Errors.unknownWalletAddress }
public func respond(requestId: RPCID, signature: CacaoSignature, from account: Account) async throws {
try await walletRespondService.respond(requestId: requestId, signature: signature, account: account)
}

Expand All @@ -96,11 +89,14 @@ public class AuthClient {

/// Query pending authentication requests
/// - Returns: Pending authentication requests
public func getPendingRequests() throws -> [AuthRequest] {
guard let account = account else { throw Errors.unknownWalletAddress }
public func getPendingRequests(account: Account) throws -> [AuthRequest] {
return try pendingRequestsProvider.getPendingRequests(account: account)
}

public func formatMessage(payload: AuthPayload, address: String) throws -> String {
return try SIWEMessageFormatter().formatMessage(from: payload, address: address)
}

private func setUpPublishers() {
appRespondSubscriber.onResponse = { [unowned self] (id, result) in
authResponsePublisherSubject.send((id, result))
Expand Down
6 changes: 1 addition & 5 deletions Sources/Auth/AuthClientFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ public struct AuthClientFactory {

public static func create(
metadata: AppMetadata,
account: Account?,
projectId: String,
signerFactory: SignerFactory,
networkingClient: NetworkingInteractor,
Expand All @@ -18,7 +17,6 @@ public struct AuthClientFactory {

return AuthClientFactory.create(
metadata: metadata,
account: account,
projectId: projectId,
signerFactory: signerFactory,
logger: logger,
Expand All @@ -32,7 +30,6 @@ public struct AuthClientFactory {

static func create(
metadata: AppMetadata,
account: Account?,
projectId: String,
signerFactory: SignerFactory,
logger: ConsoleLogging,
Expand All @@ -51,7 +48,7 @@ public struct AuthClientFactory {
let messageSigner = messageSignerFactory.create(projectId: projectId)
let appRespondSubscriber = AppRespondSubscriber(networkingInteractor: networkingClient, logger: logger, rpcHistory: history, signatureVerifier: messageSigner, pairingRegisterer: pairingRegisterer, messageFormatter: messageFormatter)
let walletErrorResponder = WalletErrorResponder(networkingInteractor: networkingClient, logger: logger, kms: kms, rpcHistory: history)
let walletRequestSubscriber = WalletRequestSubscriber(networkingInteractor: networkingClient, logger: logger, kms: kms, messageFormatter: messageFormatter, address: account?.address, walletErrorResponder: walletErrorResponder, pairingRegisterer: pairingRegisterer)
let walletRequestSubscriber = WalletRequestSubscriber(networkingInteractor: networkingClient, logger: logger, kms: kms, walletErrorResponder: walletErrorResponder, pairingRegisterer: pairingRegisterer)
let walletRespondService = WalletRespondService(networkingInteractor: networkingClient, logger: logger, kms: kms, rpcHistory: history, walletErrorResponder: walletErrorResponder)
let pendingRequestsProvider = PendingRequestsProvider(rpcHistory: history)

Expand All @@ -60,7 +57,6 @@ public struct AuthClientFactory {
appRespondSubscriber: appRespondSubscriber,
walletRequestSubscriber: walletRequestSubscriber,
walletRespondService: walletRespondService,
account: account,
pendingRequestsProvider: pendingRequestsProvider,
logger: logger,
socketConnectionStatusPublisher: networkingClient.socketConnectionStatusPublisher,
Expand Down
1 change: 0 additions & 1 deletion Sources/Auth/AuthConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import Foundation

extension Auth {
struct Config {
let account: Account?
let signerFactory: SignerFactory
}
}
6 changes: 5 additions & 1 deletion Sources/Auth/Services/App/AppRespondSubscriber.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,11 @@ class AppRespondSubscriber {
let message = try? messageFormatter.formatMessage(from: cacao.p)
else { self.onResponse?(requestId, .failure(.malformedResponseParams)); return }

guard messageFormatter.formatMessage(from: requestPayload.payloadParams, address: address) == message
guard
let recovered = try? messageFormatter.formatMessage(
from: requestPayload.payloadParams,
address: address
), recovered == message
else { self.onResponse?(requestId, .failure(.messageCompromised)); return }

Task(priority: .high) {
Expand Down
42 changes: 26 additions & 16 deletions Sources/Auth/Services/Common/SIWEMessageFormatter.swift
Original file line number Diff line number Diff line change
@@ -1,25 +1,35 @@
import Foundation

protocol SIWEMessageFormatting {
func formatMessage(from authPayload: AuthPayload, address: String) -> String?
func formatMessage(from payload: AuthPayload, address: String) throws -> String
func formatMessage(from payload: CacaoPayload) throws -> String
}

struct SIWEMessageFormatter: SIWEMessageFormatting {
func formatMessage(from payload: AuthPayload, address: String) -> String? {
guard let chain = Blockchain(payload.chainId) else {return nil}
let message = SIWEMessage(domain: payload.domain,
uri: payload.aud,
address: address,
version: payload.version,
nonce: payload.nonce,
chainId: chain.reference,
iat: payload.iat,
nbf: payload.nbf,
exp: payload.exp,
statement: payload.statement,
requestId: payload.requestId,
resources: payload.resources
public struct SIWEMessageFormatter: SIWEMessageFormatting {

enum Errors: Error {
case invalidChainID
}

public init() { }

public func formatMessage(from payload: AuthPayload, address: String) throws -> String {
guard let chain = Blockchain(payload.chainId) else {
throw Errors.invalidChainID
}
let message = SIWEMessage(
domain: payload.domain,
uri: payload.aud,
address: address,
version: payload.version,
nonce: payload.nonce,
chainId: chain.reference,
iat: payload.iat,
nbf: payload.nbf,
exp: payload.exp,
statement: payload.statement,
requestId: payload.requestId,
resources: payload.resources
)
return message.formatted
}
Expand Down
Loading

0 comments on commit b2ef792

Please sign in to comment.