diff --git a/Package.swift b/Package.swift index d695c11..f19fd4e 100644 --- a/Package.swift +++ b/Package.swift @@ -17,14 +17,24 @@ let package = Package( .package(name:"SessionManager",url: "https://github.com/Web3Auth/session-manager-swift.git",from: "6.0.2"), .package(name: "curvelib.swift", url: "https://github.com/tkey/curvelib.swift", from: "2.0.0"), .package(url: "https://github.com/attaswift/BigInt.git", from: "5.3.0"), + .package(url: "https://github.com/torusresearch/torus-utils-swift.git", from: "10.0.1"), + .package(url: "https://github.com/vapor/jwt-kit.git", from: "4.0.0"), + .package(url: "https://github.com/auth0/JWTDecode.swift.git", from: "3.2.0") ], targets: [ .target( name: "Web3Auth", - dependencies: ["KeychainSwift", .product(name: "curveSecp256k1", package: "curvelib.swift"), "SessionManager", "BigInt"]), + dependencies: [ + "KeychainSwift", + .product(name: "curveSecp256k1", package: "curvelib.swift"), + "SessionManager", + "BigInt", + .product(name: "TorusUtils", package: "torus-utils-swift"), + .product(name: "JWTDecode", package: "JWTDecode.swift") + ]), .testTarget( name: "Web3AuthTests", - dependencies: ["Web3Auth"]) + dependencies: ["Web3Auth", .product(name: "JWTKit", package: "jwt-kit")]) ], swiftLanguageVersions: [.v5] ) diff --git a/README.md b/README.md index ee9671f..24874c2 100644 --- a/README.md +++ b/README.md @@ -52,15 +52,15 @@ Checkout [SDK Reference](https://web3auth.io/docs/sdk/pnp/ios/install#configure- ```swift import Web3Auth -let web3auth = try Web3Auth(W3AInitParams( +let web3auth = try Web3Auth(Web3AuthOptions( // Get your Web3Auth Client Id from dashboard.web3auth.io clientId: "YOUR_WEB3AUTH_CLIENT_ID", - network: .sapphire_mainnet, + web3AuthNetwork: .sapphire_mainnet, redirectUrl: "bundleId://auth" )) // Login -let result = try await web3Auth.login(W3ALoginParams(loginProvider: .GOOGLE)) +let result = try await web3Auth.login(LoginParams(loginProvider: .GOOGLE)) // Logout try await web3auth.logout() diff --git a/Sources/Web3Auth/Provider.swift b/Sources/Web3Auth/AuthConnection.swift similarity index 87% rename from Sources/Web3Auth/Provider.swift rename to Sources/Web3Auth/AuthConnection.swift index 7b977a1..a1242b6 100644 --- a/Sources/Web3Auth/Provider.swift +++ b/Sources/Web3Auth/AuthConnection.swift @@ -1,6 +1,6 @@ import Foundation -public enum Web3AuthProvider: String, Codable { +public enum AuthConnection: String, Codable { case GOOGLE = "google" case FACEBOOK = "facebook" case REDDIT = "reddit" @@ -15,7 +15,7 @@ public enum Web3AuthProvider: String, Codable { case WEIBO = "weibo" case WECHAT = "wechat" case EMAIL_PASSWORDLESS = "email_passwordless" - case JWT = "jwt" + case CUSTOM = "custom" case SMS_PASSWORDLESS = "sms_passwordless" case FARCASTER = "farcaster" } diff --git a/Sources/Web3Auth/Data+extensions.swift b/Sources/Web3Auth/Data+extensions.swift index e0ecc6e..dd74364 100644 --- a/Sources/Web3Auth/Data+extensions.swift +++ b/Sources/Web3Auth/Data+extensions.swift @@ -21,6 +21,14 @@ internal extension Data { result = result.replacingOccurrences(of: "=", with: "") return result } + + var bytes: Array { + Array(self) + } + + func toHexString() -> String { + bytes.toHexString() + } } internal extension Data { diff --git a/Sources/Web3Auth/EmailFlowType.swift b/Sources/Web3Auth/EmailFlowType.swift new file mode 100644 index 0000000..4f130b1 --- /dev/null +++ b/Sources/Web3Auth/EmailFlowType.swift @@ -0,0 +1,7 @@ +import Foundation + +public enum EmailFlowType: String, Codable { + case link + case code +} + diff --git a/Sources/Web3Auth/KeyChainHelper.swift b/Sources/Web3Auth/KeyChainHelper.swift new file mode 100644 index 0000000..d28b495 --- /dev/null +++ b/Sources/Web3Auth/KeyChainHelper.swift @@ -0,0 +1,65 @@ +import Foundation +import Security + +final class KeychainHelper { + + static let shared = KeychainHelper() + private init() {} + + // Save any Codable value + func save(_ value: T, forKey key: String) { + guard let data = try? JSONEncoder().encode(value) else { return } + + let query: [String: Any] = [ + kSecClass as String: kSecClassGenericPassword, + kSecAttrAccount as String: key + ] + + // Delete existing before adding new + SecItemDelete(query as CFDictionary) + + let addQuery: [String: Any] = [ + kSecClass as String: kSecClassGenericPassword, + kSecAttrAccount as String: key, + kSecValueData as String: data + ] + + SecItemAdd(addQuery as CFDictionary, nil) + } + + // Get any Codable value + func get(forKey key: String, as type: T.Type) -> T? { + let query: [String: Any] = [ + kSecClass as String: kSecClassGenericPassword, + kSecAttrAccount as String: key, + kSecReturnData as String: true, + kSecMatchLimit as String: kSecMatchLimitOne + ] + + var item: AnyObject? + SecItemCopyMatching(query as CFDictionary, &item) + + guard let data = item as? Data else { return nil } + return try? JSONDecoder().decode(type, from: data) + } + + // Delete a single key + func delete(forKey key: String) { + let query: [String: Any] = [ + kSecClass as String: kSecClassGenericPassword, + kSecAttrAccount as String: key + ] + SecItemDelete(query as CFDictionary) + } + + // Clear all in keychain + func clearAll() { + let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword] + SecItemDelete(query as CFDictionary) + } +} + +struct KeychainKeys { + static let isSFA = "isSFA" +} + diff --git a/Sources/Web3Auth/KeychainManager.swift b/Sources/Web3Auth/KeychainManager.swift index a5b27d7..5cc1811 100644 --- a/Sources/Web3Auth/KeychainManager.swift +++ b/Sources/Web3Auth/KeychainManager.swift @@ -2,13 +2,13 @@ import SessionManager extension KeychainManager { func saveDappShare(userInfo: Web3AuthUserInfo) { - guard let verifer = userInfo.verifier, let veriferID = userInfo.verifierId, let dappShare = userInfo.dappShare else { return } + guard let verifer = userInfo.authConnectionId, let veriferID = userInfo.userId, let dappShare = userInfo.dappShare else { return } let key = verifer + " | " + veriferID KeychainManager.shared.save(key: .custom(dappShare), val: key) } - func getDappShare(verifier: String) -> String? { - return searchDappShare(query: verifier) + func getDappShare(authConnectionId: String) -> String? { + return searchDappShare(query: authConnectionId) } private func searchDappShare(query: String) -> String? { diff --git a/Sources/Web3Auth/Network.swift b/Sources/Web3Auth/Network.swift deleted file mode 100644 index 8cf5729..0000000 --- a/Sources/Web3Auth/Network.swift +++ /dev/null @@ -1,14 +0,0 @@ -import Foundation - -/** - List of networks that can run Web3Auth. - */ -public enum Network: String, Codable { - case mainnet - case testnet - case cyan - case aqua - case celeste - case sapphire_devnet - case sapphire_mainnet -} diff --git a/Sources/Web3Auth/Networking/NetworkClient.swift b/Sources/Web3Auth/Networking/NetworkClient.swift index 81bae74..6c9ee9f 100644 --- a/Sources/Web3Auth/Networking/NetworkClient.swift +++ b/Sources/Web3Auth/Networking/NetworkClient.swift @@ -7,9 +7,9 @@ enum Router: NetworkManagerProtocol { var path: String { switch self { case .get: - return "/api/configuration" + return "/api/v2/configuration" case .set: - return "/api/configuration" + return "/api/v2/configuration" } } diff --git a/Sources/Web3Auth/OSLog.swift b/Sources/Web3Auth/OSLog.swift index d5c8fda..5f69c63 100644 --- a/Sources/Web3Auth/OSLog.swift +++ b/Sources/Web3Auth/OSLog.swift @@ -6,7 +6,7 @@ var web3AuthLogType = OSLogType.default public struct Web3AuthLogger { static let inactiveLog = OSLog.disabled static let core = OSLog(subsystem: subsystem, category: "core") - static let network = OSLog(subsystem: subsystem, category: "network") + static let web3AuthNetwork = OSLog(subsystem: subsystem, category: "web3AuthNetwork") } @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) diff --git a/Sources/Web3Auth/Types.swift b/Sources/Web3Auth/Types.swift index 1af475b..ce06e6d 100644 --- a/Sources/Web3Auth/Types.swift +++ b/Sources/Web3Auth/Types.swift @@ -1,6 +1,7 @@ //TODO: Split up this file. import Foundation +import FetchNodeDetails public struct Signature: Codable { let r: String @@ -68,31 +69,13 @@ public enum MFALevel: String, Codable { case NONE = "none" } -public enum TypeOfLogin: String, Codable { - case google - case facebook - case reddit - case discord - case twitch - case apple - case github - case linkedin - case twitter - case weibo - case line - case email_passwordless - case passwordless - case jwt - case sms_passwordless - case farcaster -} - public enum ChainNamespace: String, Codable { case eip155 case solana + case other } -public struct W3AWhiteLabelData: Codable { +public struct WhiteLabelData: Codable { public init(appName: String? = nil, logoLight: String? = nil, logoDark: String? = nil, defaultLanguage: Language? = Language.en, mode: ThemeModes? = ThemeModes.auto, theme: [String: String]? = nil, appUrl: String? = nil, useLogoLoader: Bool? = false) { self.appName = appName self.logoLight = logoLight @@ -126,16 +109,15 @@ public struct W3AWhiteLabelData: Codable { } } -public struct W3ALoginConfig: Codable { - public init(verifier: String, typeOfLogin: TypeOfLogin, name: String? = nil, description: String? = nil, clientId: String, - verifierSubIdentifier: String? = nil, logoHover: String? = nil, logoLight: String? = nil, logoDark: String? = nil, mainOption: Bool? = nil, - showOnModal: Bool? = nil, showOnDesktop: Bool? = nil, showOnMobile: Bool? = nil) { - self.verifier = verifier - self.typeOfLogin = typeOfLogin +public struct AuthConnectionConfig: Codable { + public init(authConnectionId: String, authConnection: AuthConnection, name: String? = nil, description: String? = nil, clientId: String, groupedAuthConnectionId: String? = nil , logoHover: String? = nil, logoLight: String? = nil, logoDark: String? = nil, mainOption: Bool? = nil, + showOnModal: Bool? = nil, showOnDesktop: Bool? = nil, showOnMobile: Bool? = nil, jwtParameters: ExtraLoginOptions? = nil) { + self.authConnectionId = authConnectionId + self.authConnection = authConnection self.name = name self.description = description self.clientId = clientId - self.verifierSubIdentifier = verifierSubIdentifier + self.groupedAuthConnectionId = groupedAuthConnectionId self.logoHover = logoHover self.logoLight = logoLight self.logoDark = logoDark @@ -143,14 +125,15 @@ public struct W3ALoginConfig: Codable { self.showOnModal = showOnModal self.showOnDesktop = showOnDesktop self.showOnMobile = showOnMobile + self.jwtParameters = jwtParameters } - let verifier: String - let typeOfLogin: TypeOfLogin + let authConnectionId: String + let authConnection: AuthConnection let name: String? let description: String? let clientId: String - let verifierSubIdentifier: String? + let groupedAuthConnectionId: String? let logoHover: String? let logoLight: String? let logoDark: String? @@ -158,15 +141,16 @@ public struct W3ALoginConfig: Codable { let showOnModal: Bool? let showOnDesktop: Bool? let showOnMobile: Bool? + let jwtParameters: ExtraLoginOptions? public init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) - verifier = try values.decode(String.self, forKey: .verifier) - typeOfLogin = try values.decode(TypeOfLogin.self, forKey: .typeOfLogin) + authConnectionId = try values.decode(String.self, forKey: .authConnectionId) + authConnection = try values.decode(AuthConnection.self, forKey: .authConnection) name = try values.decodeIfPresent(String.self, forKey: .name) description = try values.decodeIfPresent(String.self, forKey: .description) clientId = try values.decode(String.self, forKey: .clientId) - verifierSubIdentifier = try values.decodeIfPresent(String.self, forKey: .verifierSubIdentifier) + groupedAuthConnectionId = try values.decodeIfPresent(String.self, forKey: .groupedAuthConnectionId) logoHover = try values.decodeIfPresent(String.self, forKey: .logoHover) logoLight = try values.decodeIfPresent(String.self, forKey: .logoLight) logoDark = try values.decodeIfPresent(String.self, forKey: .logoDark) @@ -174,117 +158,234 @@ public struct W3ALoginConfig: Codable { showOnModal = try values.decodeIfPresent(Bool.self, forKey: .showOnModal) showOnDesktop = try values.decodeIfPresent(Bool.self, forKey: .showOnDesktop) showOnMobile = try values.decodeIfPresent(Bool.self, forKey: .showOnMobile) + jwtParameters = try values.decodeIfPresent(ExtraLoginOptions.self, forKey: .jwtParameters) } } -public struct W3AInitParams: Codable { - public init(clientId: String, network: Network, buildEnv: BuildEnv? = BuildEnv.production, sdkUrl: URL? = nil, walletSdkUrl: URL? = nil, redirectUrl: String, loginConfig: [String: W3ALoginConfig]? = nil, whiteLabel: W3AWhiteLabelData? = nil, chainNamespace: ChainNamespace? = ChainNamespace.eip155, useCoreKitKey: Bool? = false, mfaSettings: MfaSettings? = nil, sessionTime: Int = 30 * 86400, originData: [String: String]? = nil, dashboardUrl: URL? = nil) { +public struct Web3AuthOptions: Codable { + public init(clientId: String, redirectUrl: String, originData: [String: String]? = nil, authBuildEnv: BuildEnv? = .production, sdkUrl: String? = nil, + storageServerUrl: String? = nil,sessionSocketUrl: String? = nil, authConnectionConfig: [AuthConnectionConfig]? = nil, + whiteLabel: WhiteLabelData? = nil, dashboardUrl: String? = nil, accountAbstractionConfig: String? = nil, walletSdkUrl: String? = nil, + includeUserDataInToken: Bool? = true, chains: [Chains]? = nil, defaultChainId: String? = "0x1", enableLogging: Bool? = false, sessionTime: Int = 30 * 86400, web3AuthNetwork: Web3AuthNetwork, useSFAKey: Bool? = nil, walletServicesConfig: WalletServicesConfig? = nil, + mfaSettings: MfaSettings? = nil) { self.clientId = clientId - self.network = network - self.buildEnv = buildEnv + self.redirectUrl = redirectUrl + self.originData = originData + self.authBuildEnv = authBuildEnv if sdkUrl != nil { self.sdkUrl = sdkUrl } else { - self.sdkUrl = URL(string: getSdkUrl(buildEnv: self.buildEnv)) - } - if walletSdkUrl != nil { - self.walletSdkUrl = walletSdkUrl - } else { - self.walletSdkUrl = URL(string: getWalletSdkUrl(buildEnv: self.buildEnv)) + self.sdkUrl = getSdkUrl(buildEnv: self.authBuildEnv) } - self.redirectUrl = redirectUrl - self.loginConfig = loginConfig + self.storageServerUrl = storageServerUrl + self.sessionSocketUrl = sessionSocketUrl + + self.authConnectionConfig = authConnectionConfig self.whiteLabel = whiteLabel - self.chainNamespace = chainNamespace - self.useCoreKitKey = useCoreKitKey - self.mfaSettings = mfaSettings - self.sessionTime = min(30 * 86400, sessionTime) - self.originData = originData if dashboardUrl != nil { self.dashboardUrl = dashboardUrl } else { - self.dashboardUrl = URL(string: getDashboardUrl(buildEnv: self.buildEnv)) + self.dashboardUrl = getDashboardUrl(buildEnv: self.authBuildEnv) } + self.accountAbstractionConfig = accountAbstractionConfig + if walletSdkUrl != nil { + self.walletSdkUrl = walletSdkUrl + } else { + self.walletSdkUrl = getWalletSdkUrl(buildEnv: self.authBuildEnv) + } + self.includeUserDataInToken = includeUserDataInToken + self.chains = chains + self.defaultChainId = defaultChainId + self.enableLogging = enableLogging + self.sessionTime = min(sessionTime, 30 * 86400) // Clamp to max 30 days + self.web3AuthNetwork = web3AuthNetwork + self.useSFAKey = useSFAKey + self.walletServicesConfig = walletServicesConfig + self.mfaSettings = mfaSettings } - public init(clientId: String, network: Network, redirectUrl: String) { + public init(clientId: String, web3AuthNetwork: Web3AuthNetwork, redirectUrl: String) { self.clientId = clientId - self.network = network - buildEnv = BuildEnv.production - sdkUrl = URL(string: getSdkUrl(buildEnv: buildEnv)) - walletSdkUrl = URL(string: getWalletSdkUrl(buildEnv: buildEnv)) self.redirectUrl = redirectUrl - loginConfig = nil - whiteLabel = nil - chainNamespace = ChainNamespace.eip155 - useCoreKitKey = false - mfaSettings = nil - sessionTime = 30 * 86400 - chainConfig = nil - originData = nil - dashboardUrl = URL(string: getDashboardUrl(buildEnv: buildEnv)) + self.originData = nil + self.authBuildEnv = BuildEnv.production + sdkUrl = getSdkUrl(buildEnv: authBuildEnv) + self.storageServerUrl = nil + self.sessionSocketUrl = nil + self.authConnectionConfig = nil + self.whiteLabel = nil + dashboardUrl = getDashboardUrl(buildEnv: authBuildEnv) + self.accountAbstractionConfig = nil + walletSdkUrl = getWalletSdkUrl(buildEnv: authBuildEnv) + self.includeUserDataInToken = true + self.chains = nil + self.defaultChainId = "0x1" + self.enableLogging = false + self.sessionTime = 30 * 86400 + self.web3AuthNetwork = web3AuthNetwork + self.useSFAKey = false + self.walletServicesConfig = nil + self.mfaSettings = nil } let clientId: String - let network: Network - let buildEnv: BuildEnv? - var sdkUrl: URL? - var walletSdkUrl: URL? var redirectUrl: String - let loginConfig: [String: W3ALoginConfig]? - var whiteLabel: W3AWhiteLabelData? - let chainNamespace: ChainNamespace? - let useCoreKitKey: Bool? - let mfaSettings: MfaSettings? - let sessionTime: Int - var chainConfig: ChainConfig? = nil var originData: [String: String]? - var dashboardUrl: URL? + let authBuildEnv: BuildEnv? + var sdkUrl: String? + var storageServerUrl: String? + var sessionSocketUrl: String? + var authConnectionConfig: [AuthConnectionConfig]? + var whiteLabel: WhiteLabelData? + var dashboardUrl: String? + var accountAbstractionConfig: String? + var walletSdkUrl: String? + let includeUserDataInToken: Bool? + var chains: [Chains]? = nil + var defaultChainId: String? = "0x1" + let enableLogging: Bool? + let sessionTime: Int + var web3AuthNetwork: Web3AuthNetwork + var useSFAKey: Bool? + var walletServicesConfig: WalletServicesConfig? + let mfaSettings: MfaSettings? + + + enum CodingKeys: String, CodingKey { + case clientId + case redirectUrl + case originData + case authBuildEnv = "buildEnv" + case sdkUrl + case storageServerUrl + case sessionSocketUrl + case authConnectionConfig + case whiteLabel + case dashboardUrl + case accountAbstractionConfig + case walletSdkUrl + case includeUserDataInToken + case chains + case defaultChainId + case enableLogging + case sessionTime + case web3AuthNetwork = "network" + case useSFAKey + case walletServicesConfig + case mfaSettings + } public init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) clientId = try values.decode(String.self, forKey: .clientId) - network = try values.decode(Network.self, forKey: .network) - buildEnv = try values.decodeIfPresent(BuildEnv.self, forKey: .buildEnv) ?? BuildEnv.production - let customSdkUrl = try values.decodeIfPresent(String.self, forKey: .sdkUrl) - if customSdkUrl != nil { - sdkUrl = URL(string: customSdkUrl!)! + redirectUrl = try values.decode(String.self, forKey: .redirectUrl) + originData = try values.decodeIfPresent([String: String].self, forKey: .originData) + authBuildEnv = try values.decodeIfPresent(BuildEnv.self, forKey: .authBuildEnv) ?? .production + + if let customSdkUrl = try values.decodeIfPresent(String.self, forKey: .sdkUrl) { + sdkUrl = customSdkUrl } else { - sdkUrl = URL(string: getSdkUrl(buildEnv: buildEnv)) + sdkUrl = getSdkUrl(buildEnv: authBuildEnv) } - let customWalletSdkUrl = try values.decodeIfPresent(String.self, forKey: .walletSdkUrl) - if customWalletSdkUrl != nil { - walletSdkUrl = URL(string: customWalletSdkUrl!)! + + storageServerUrl = try values.decodeIfPresent(String.self, forKey: .storageServerUrl) + sessionSocketUrl = try values.decodeIfPresent(String.self, forKey: .sessionSocketUrl) + authConnectionConfig = try values.decodeIfPresent([AuthConnectionConfig].self, forKey: .authConnectionConfig) + whiteLabel = try values.decodeIfPresent(WhiteLabelData.self, forKey: .whiteLabel) + dashboardUrl = try values.decodeIfPresent(String.self, forKey: .dashboardUrl) + accountAbstractionConfig = try values.decodeIfPresent(String.self, forKey: .accountAbstractionConfig) + + if let customWalletSdkUrl = try values.decodeIfPresent(String.self, forKey: .walletSdkUrl) { + walletSdkUrl = customWalletSdkUrl } else { - walletSdkUrl = URL(string: getWalletSdkUrl(buildEnv: buildEnv)) + walletSdkUrl = getWalletSdkUrl(buildEnv: authBuildEnv) } - redirectUrl = try values.decode(String.self, forKey: .redirectUrl) - loginConfig = try values.decodeIfPresent([String: W3ALoginConfig].self, forKey: .loginConfig) - whiteLabel = try values.decodeIfPresent(W3AWhiteLabelData.self, forKey: .whiteLabel) - chainNamespace = try values.decodeIfPresent(ChainNamespace.self, forKey: .chainNamespace) ?? ChainNamespace.eip155 - useCoreKitKey = try values.decodeIfPresent(Bool.self, forKey: .useCoreKitKey) - mfaSettings = try values.decodeIfPresent(MfaSettings.self, forKey: .mfaSettings) + + includeUserDataInToken = try values.decodeIfPresent(Bool.self, forKey: .includeUserDataInToken) + chains = try values.decodeIfPresent([Chains].self, forKey: .chains) + defaultChainId = try values.decodeIfPresent(String.self, forKey: .defaultChainId) ?? "0x1" + enableLogging = try values.decodeIfPresent(Bool.self, forKey: .enableLogging) sessionTime = try values.decodeIfPresent(Int.self, forKey: .sessionTime) ?? 30 * 86400 - originData = try values.decodeIfPresent([String: String].self, forKey: .originData) - dashboardUrl = try values.decodeIfPresent(String.self, forKey: .dashboardUrl).flatMap { URL(string: $0) } + web3AuthNetwork = try values.decode(Web3AuthNetwork.self, forKey: .web3AuthNetwork) + useSFAKey = try values.decodeIfPresent(Bool.self, forKey: .useSFAKey) + walletServicesConfig = try values.decodeIfPresent(WalletServicesConfig.self, forKey: .walletServicesConfig) + mfaSettings = try values.decodeIfPresent(MfaSettings.self, forKey: .mfaSettings) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(clientId, forKey: .clientId) + try container.encode(redirectUrl, forKey: .redirectUrl) + try container.encodeIfPresent(originData, forKey: .originData) + try container.encode(authBuildEnv, forKey: .authBuildEnv) + + // Encode sdkUrl only if it's different from the default computed value + let defaultSdkUrl = getSdkUrl(buildEnv: authBuildEnv) + if sdkUrl != defaultSdkUrl { + try container.encode(sdkUrl, forKey: .sdkUrl) + } + + try container.encodeIfPresent(storageServerUrl, forKey: .storageServerUrl) + try container.encodeIfPresent(sessionSocketUrl, forKey: .sessionSocketUrl) + try container.encodeIfPresent(authConnectionConfig, forKey: .authConnectionConfig) + try container.encodeIfPresent(whiteLabel, forKey: .whiteLabel) + try container.encodeIfPresent(dashboardUrl, forKey: .dashboardUrl) + try container.encodeIfPresent(accountAbstractionConfig, forKey: .accountAbstractionConfig) + + let defaultWalletSdkUrl = getWalletSdkUrl(buildEnv: authBuildEnv) + if walletSdkUrl != defaultWalletSdkUrl { + try container.encode(walletSdkUrl, forKey: .walletSdkUrl) + } + + try container.encodeIfPresent(includeUserDataInToken, forKey: .includeUserDataInToken) + try container.encodeIfPresent(chains, forKey: .chains) + try container.encodeIfPresent(defaultChainId, forKey: .defaultChainId) + try container.encodeIfPresent(enableLogging, forKey: .enableLogging) + try container.encode(sessionTime, forKey: .sessionTime) + + // Encode as lowercase string + try container.encode(web3AuthNetwork.lowercaseString, forKey: .web3AuthNetwork) + + try container.encodeIfPresent(useSFAKey, forKey: .useSFAKey) + try container.encodeIfPresent(walletServicesConfig, forKey: .walletServicesConfig) + try container.encodeIfPresent(mfaSettings, forKey: .mfaSettings) + } +} + +public struct WalletServicesConfig: Codable { + var confirmationStrategy: ConfirmationStrategy? = .defaultStrategy + var whiteLabel: WhiteLabelData? = nil +} + +public enum ConfirmationStrategy: String, Codable { + case popup = "popup" + case modal = "modal" + case autoApprove = "auto-approve" + case defaultStrategy = "default" + + private enum CodingKeys: String, CodingKey { + case popup, modal + case autoApprove = "auto-approve" + case defaultStrategy = "default" } } public func getSdkUrl(buildEnv: BuildEnv?) -> String { - let openLoginVersion = "v9" + let authServiceVersion = "v10" switch buildEnv { case .staging: - return "https://staging-auth.web3auth.io/\(openLoginVersion)" + return "https://staging-auth.web3auth.io/\(authServiceVersion)" case .testing: return "https://develop-auth.web3auth.io" default: - return "https://auth.web3auth.io/\(openLoginVersion)" + return "https://auth.web3auth.io/\(authServiceVersion)" } } public func getWalletSdkUrl(buildEnv: BuildEnv?) -> String { - let walletServicesVersion = "v4" + let walletServicesVersion = "v5" guard let buildEnv = buildEnv else { return "https://wallet.web3auth.io" } @@ -300,7 +401,7 @@ public func getWalletSdkUrl(buildEnv: BuildEnv?) -> String { } public func getDashboardUrl(buildEnv: BuildEnv?) -> String { - let authDashboardVersion = "v9" + let authDashboardVersion = "v10" let walletAccountConstant = "wallet/account" switch buildEnv { case .staging: @@ -312,59 +413,56 @@ public func getDashboardUrl(buildEnv: BuildEnv?) -> String { } } -public struct W3ALoginParams: Codable { - public init(loginProvider: Web3AuthProvider, dappShare: String? = nil, - extraLoginOptions: ExtraLoginOptions? = nil, redirectUrl: String? = nil, appState: String? = nil, - mfaLevel: MFALevel? = nil, curve: SUPPORTED_KEY_CURVES = .SECP256K1, dappUrl: String? = nil) { - self.loginProvider = loginProvider.rawValue - self.dappShare = dappShare - self.extraLoginOptions = extraLoginOptions - self.redirectUrl = redirectUrl +public struct LoginParams: Codable { + public init(authConnection: AuthConnection, authConnectionId: String? = nil, groupedAuthConnectionId: String? = nil, appState: String? = nil, + mfaLevel: MFALevel? = nil, extraLoginOptions: ExtraLoginOptions? = nil, dappShare: String? = nil, curve: SUPPORTED_KEY_CURVES = .SECP256K1, + dappUrl: String? = nil, loginHint: String? = nil, idToken: String? = nil) { + self.authConnection = authConnection.rawValue + self.authConnectionId = authConnectionId + self.groupedAuthConnectionId = groupedAuthConnectionId self.appState = appState self.mfaLevel = mfaLevel - self.curve = curve - self.dappUrl = dappUrl - } - - public init(loginProvider: String, dappShare: String? = nil, - extraLoginOptions: ExtraLoginOptions? = nil, redirectUrl: String? = nil, appState: String? = nil, - mfaLevel: MFALevel? = nil, curve: SUPPORTED_KEY_CURVES = .SECP256K1, dappUrl: String? = nil) { - self.loginProvider = loginProvider - self.dappShare = dappShare self.extraLoginOptions = extraLoginOptions - self.redirectUrl = redirectUrl - self.appState = appState - self.mfaLevel = mfaLevel + self.dappShare = dappShare self.curve = curve self.dappUrl = dappUrl + self.loginHint = loginHint + self.idToken = idToken } - let loginProvider: String - var dappShare: String? - let extraLoginOptions: ExtraLoginOptions? - var redirectUrl: String? + let authConnection: String + let authConnectionId: String? + let groupedAuthConnectionId: String? let appState: String? let mfaLevel: MFALevel? + var extraLoginOptions: ExtraLoginOptions? + var dappShare: String? let curve: SUPPORTED_KEY_CURVES let dappUrl: String? + var loginHint: String? + var idToken: String? public init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) - loginProvider = try values.decode(String.self, forKey: .loginProvider) - dappShare = try values.decodeIfPresent(String.self, forKey: .dappShare) - extraLoginOptions = try values.decodeIfPresent(ExtraLoginOptions.self, forKey: .extraLoginOptions) - redirectUrl = try values.decodeIfPresent(String.self, forKey: .redirectUrl) + authConnection = try values.decode(String.self, forKey: .authConnection) + authConnectionId = try values.decode(String.self, forKey: .authConnectionId) + groupedAuthConnectionId = try values.decode(String.self, forKey: .groupedAuthConnectionId) appState = try values.decodeIfPresent(String.self, forKey: .appState) mfaLevel = try values.decodeIfPresent(MFALevel.self, forKey: .mfaLevel) + extraLoginOptions = try values.decodeIfPresent(ExtraLoginOptions.self, forKey: .extraLoginOptions) + dappShare = try values.decodeIfPresent(String.self, forKey: .dappShare) curve = try values.decodeIfPresent(SUPPORTED_KEY_CURVES.self, forKey: .curve) ?? .SECP256K1 dappUrl = try values.decodeIfPresent(String.self, forKey: .dappUrl) + loginHint = try values.decodeIfPresent(String.self, forKey: .loginHint) + idToken = try values.decodeIfPresent(String.self, forKey: .idToken) } } public struct ExtraLoginOptions: Codable { public init(display: String? = nil, prompt: String? = nil, max_age: String? = nil, ui_locales: String? = nil, id_token_hint: String? = nil, id_token: String? = nil, login_hint: String? = nil, acr_values: String? = nil, scope: String? = nil, - audience: String? = nil, connection: String? = nil, domain: String? = nil, client_id: String? = nil, redirect_uri: String? = nil, leeway: Int? = 0, verifierIdField: String? = nil, isVerifierIdCaseSensitive: Bool? = false, additionalParams: [String: String]? = nil) { + audience: String? = nil, connection: String? = nil, domain: String? = nil, client_id: String? = nil, redirect_uri: String? = nil, leeway: Int? = 0, userIdField: String? = nil, isUserIdCaseSensitive: Bool? = false, additionalParams: [String: String]? = nil, access_token: String? = nil, + flow_type: EmailFlowType = EmailFlowType.code) { self.display = display self.prompt = prompt self.max_age = max_age @@ -380,9 +478,11 @@ public struct ExtraLoginOptions: Codable { self.client_id = client_id self.redirect_uri = redirect_uri self.leeway = leeway - self.verifierIdField = verifierIdField - self.isVerifierIdCaseSensitive = isVerifierIdCaseSensitive + self.userIdField = userIdField + self.isUserIdCaseSensitive = isUserIdCaseSensitive self.additionalParams = additionalParams + self.access_token = access_token + self.flow_type = EmailFlowType.code } let display: String? @@ -400,9 +500,12 @@ public struct ExtraLoginOptions: Codable { let client_id: String? let redirect_uri: String? let leeway: Int? - let verifierIdField: String? - let isVerifierIdCaseSensitive: Bool? + let userIdField: String? + let isUserIdCaseSensitive: Bool? let additionalParams: [String: String]? + let access_token: String? + let flow_type: EmailFlowType? + public init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) @@ -421,9 +524,11 @@ public struct ExtraLoginOptions: Codable { client_id = try values.decodeIfPresent(String.self, forKey: .client_id) redirect_uri = try values.decodeIfPresent(String.self, forKey: .redirect_uri) leeway = try values.decodeIfPresent(Int.self, forKey: .leeway) - verifierIdField = try values.decodeIfPresent(String.self, forKey: .verifierIdField) - isVerifierIdCaseSensitive = try values.decodeIfPresent(Bool.self, forKey: .isVerifierIdCaseSensitive) + userIdField = try values.decodeIfPresent(String.self, forKey: .userIdField) + isUserIdCaseSensitive = try values.decodeIfPresent(Bool.self, forKey: .isUserIdCaseSensitive) additionalParams = try values.decodeIfPresent([String: String].self, forKey: .additionalParams) + access_token = try values.decodeIfPresent(String.self, forKey: .access_token) + flow_type = try values.decodeIfPresent(EmailFlowType.self, forKey: .flow_type) ?? EmailFlowType.code } } @@ -475,7 +580,7 @@ public struct MfaSetting: Codable { } } -public struct ChainConfig: Codable { +public struct Chains: Codable { public init(chainNamespace: ChainNamespace = ChainNamespace.eip155, decimals: Int? = 18, blockExplorerUrl: String? = nil, chainId: String, displayName: String? = nil, logo: String? = nil, rpcTarget: String, ticker: String? = nil, tickerName: String? = nil) { self.chainNamespace = chainNamespace self.decimals = decimals @@ -512,9 +617,64 @@ public struct ChainConfig: Codable { } } +public struct WalletUiConfig: Codable { + public var enablePortfolioWidget: Bool? + public var enableConfirmationModal: Bool? + public var enableWalletConnect: Bool? + public var enableTokenDisplay: Bool? + public var enableNftDisplay: Bool? + public var enableShowAllTokensButton: Bool? + public var enableBuyButton: Bool? + public var enableSendButton: Bool? + public var enableSwapButton: Bool? + public var enableReceiveButton: Bool? + public var portfolioWidgetPosition: ButtonPositionType? + public var defaultPortfolio: DefaultPortfolioType? + + public init( + enablePortfolioWidget: Bool? = nil, + enableConfirmationModal: Bool? = nil, + enableWalletConnect: Bool? = nil, + enableTokenDisplay: Bool? = nil, + enableNftDisplay: Bool? = nil, + enableShowAllTokensButton: Bool? = nil, + enableBuyButton: Bool? = nil, + enableSendButton: Bool? = nil, + enableSwapButton: Bool? = nil, + enableReceiveButton: Bool? = nil, + portfolioWidgetPosition: ButtonPositionType? = nil, + defaultPortfolio: DefaultPortfolioType? = nil + ) { + self.enablePortfolioWidget = enablePortfolioWidget + self.enableConfirmationModal = enableConfirmationModal + self.enableWalletConnect = enableWalletConnect + self.enableTokenDisplay = enableTokenDisplay + self.enableNftDisplay = enableNftDisplay + self.enableShowAllTokensButton = enableShowAllTokensButton + self.enableBuyButton = enableBuyButton + self.enableSendButton = enableSendButton + self.enableSwapButton = enableSwapButton + self.enableReceiveButton = enableReceiveButton + self.portfolioWidgetPosition = portfolioWidgetPosition + self.defaultPortfolio = defaultPortfolio + } +} + +public enum ButtonPositionType: String, Codable { + case bottomLeft = "bottom-left" + case topLeft = "top-left" + case bottomRight = "bottom-right" + case topRight = "top-right" +} + +public enum DefaultPortfolioType: String, Codable { + case token = "token" + case nft = "nft" +} + struct SdkUrlParams: Codable { - let options: W3AInitParams - let params: W3ALoginParams + var options: Web3AuthOptions + let params: LoginParams let actionType: String enum CodingKeys: String, CodingKey { @@ -525,7 +685,7 @@ struct SdkUrlParams: Codable { } struct WalletServicesParams: Codable { - let options: W3AInitParams + let options: Web3AuthOptions let appState: String? enum CodingKeys: String, CodingKey { @@ -535,7 +695,7 @@ struct WalletServicesParams: Codable { } struct SetUpMFAParams: Codable { - let options: W3AInitParams + let options: Web3AuthOptions let params: [String: String?] let actionType: String let sessionId: String @@ -548,25 +708,105 @@ struct SetUpMFAParams: Codable { } } -struct ProjectConfigResponse: Codable { - let smsOtpEnabled, walletConnectEnabled: Bool - let whitelist: Whitelist - let whiteLabelData: W3AWhiteLabelData? +public struct Whitelist: Codable { + let urls: [String] + let signedUrls: [String: String] enum CodingKeys: String, CodingKey { + case urls + case signedUrls = "signed_urls" + } +} + +public struct ProjectConfigResponse: Codable { + public var userDataInIdToken: Bool? = true + public var sessionTime: Int? = 86400 + public var enableKeyExport: Bool? = false + public var whitelist: Whitelist + public var chains: [Chains]? = nil + public var smartAccounts: SmartAccountsConfig? = nil + public var walletUiConfig: WalletUiConfig? = nil + public var embeddedWalletAuth: [AuthConnectionConfig]? = nil + public var smsOtpEnabled: Bool? = nil + public var walletConnectEnabled: Bool? = nil + public var walletConnectProjectId: String? = nil + public var whitelabel: WhiteLabelData? = nil + + enum CodingKeys: String, CodingKey { + case userDataInIdToken + case sessionTime + case enableKeyExport + case whitelist + case chains + case smartAccounts + case walletUiConfig + case embeddedWalletAuth case smsOtpEnabled = "sms_otp_enabled" case walletConnectEnabled = "wallet_connect_enabled" - case whitelist - case whiteLabelData = "whitelabel" + case walletConnectProjectId + case whitelabel + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.userDataInIdToken = try container.decodeIfPresent(Bool.self, forKey: .userDataInIdToken) ?? true + self.sessionTime = try container.decodeIfPresent(Int.self, forKey: .sessionTime) ?? 86400 + self.enableKeyExport = try container.decodeIfPresent(Bool.self, forKey: .enableKeyExport) ?? false + self.whitelist = try container.decode(Whitelist.self, forKey: .whitelist) + self.chains = try container.decodeIfPresent([Chains].self, forKey: .chains) + self.smartAccounts = try container.decodeIfPresent(SmartAccountsConfig.self, forKey: .smartAccounts) + self.walletUiConfig = try container.decodeIfPresent(WalletUiConfig.self, forKey: .walletUiConfig) + self.embeddedWalletAuth = try container.decodeIfPresent([AuthConnectionConfig].self, forKey: .embeddedWalletAuth) + self.smsOtpEnabled = try container.decodeIfPresent(Bool.self, forKey: .smsOtpEnabled) ?? false + self.walletConnectEnabled = try container.decodeIfPresent(Bool.self, forKey: .walletConnectEnabled) ?? false + self.walletConnectProjectId = try container.decodeIfPresent(String.self, forKey: .walletConnectProjectId) + self.whitelabel = try container.decodeIfPresent(WhiteLabelData.self, forKey: .whitelabel) } } -struct Whitelist: Codable { - let urls: [String] - let signedUrls: [String: String] +public struct SmartAccountsConfig: Codable { + public var smartAccountType: SmartAccountType + public var chains: [ChainConfig] enum CodingKeys: String, CodingKey { - case urls - case signedUrls = "signed_urls" + case smartAccountType + case chains + } +} + +public struct ChainConfig: Codable { + public var chainId: String + public var bundlerConfig: BundlerConfig + public var paymasterConfig: PaymasterConfig? + + enum CodingKeys: String, CodingKey { + case chainId + case bundlerConfig + case paymasterConfig } } + +public struct BundlerConfig: Codable { + public var url: String +} + +public struct PaymasterConfig: Codable { + public var url: String +} + +public enum SmartAccountType: String, Codable { + case metamask = "metamask" + case biconomy = "biconomy" + case kernel = "kernel" + case safe = "safe" + case trust = "trust" + case light = "light" + case simple = "simple" + case nexus = "nexus" +} + +public struct Web3AuthSubVerifierInfo: Codable { + var verifier: String + var idToken: String +} + diff --git a/Sources/Web3Auth/Util.swift b/Sources/Web3Auth/Util.swift index fcdac2e..2b81b1f 100644 --- a/Sources/Web3Auth/Util.swift +++ b/Sources/Web3Auth/Util.swift @@ -1,6 +1,7 @@ import Foundation +import FetchNodeDetails -func plistValues(_ bundle: Bundle) -> (clientId: String, network: Network, redirectUrl: String)? { +func plistValues(_ bundle: Bundle) -> (clientId: String, web3AuthNetwork: Web3AuthNetwork, redirectUrl: String)? { guard let path = bundle.path(forResource: "Web3Auth", ofType: "plist"), let values = NSDictionary(contentsOfFile: path) as? [String: Any] @@ -13,18 +14,45 @@ func plistValues(_ bundle: Bundle) -> (clientId: String, network: Network, redir let clientId = values["ClientId"] as? String, let networkValue = values["Network"] as? String, let redirectUrl = values["RedirectUrl"] as? String, - let network = Network(rawValue: networkValue) + let web3AuthNetwork = web3AuthNetworkFromString(networkValue) else { print("Web3Auth.plist file at \(path) is missing or having incorrect 'ClientId' and/or 'Network' entries!") print("File currently has the following entries: \(values)") return nil } - return (clientId: clientId, network: network, redirectUrl) + return (clientId: clientId, web3AuthNetwork: web3AuthNetwork, redirectUrl) } -extension W3AWhiteLabelData { - func merge(with other: W3AWhiteLabelData) -> W3AWhiteLabelData { - return W3AWhiteLabelData( +extension Web3AuthNetwork { + var lowercaseString: String { + switch self { + case .SAPPHIRE_DEVNET: return "sapphire_devnet" + case .SAPPHIRE_MAINNET: return "sapphire_mainnet" + case .MAINNET: return "mainnet" + case .TESTNET: return "testnet" + case .CYAN: return "cyan" + case .AQUA: return "aqua" + case .CELESTE: return "celeste" + } + } +} + +func web3AuthNetworkFromString(_ string: String) -> Web3AuthNetwork? { + switch string.uppercased() { + case "SAPPHIRE_DEVNET": return .SAPPHIRE_DEVNET + case "SAPPHIRE_MAINNET": return .SAPPHIRE_MAINNET + case "MAINNET": return .MAINNET + case "TESTNET": return .TESTNET + case "CYAN": return .CYAN + case "AQUA": return .AQUA + case "CELESTE": return .CELESTE + default: return nil + } +} + +extension WhiteLabelData { + func merge(with other: WhiteLabelData) -> WhiteLabelData { + return WhiteLabelData( appName: appName ?? other.appName, logoLight: logoLight ?? other.logoLight, logoDark: logoDark ?? other.logoDark, diff --git a/Sources/Web3Auth/Web3Auth.swift b/Sources/Web3Auth/Web3Auth.swift index c3857a9..2d65cdf 100644 --- a/Sources/Web3Auth/Web3Auth.swift +++ b/Sources/Web3Auth/Web3Auth.swift @@ -2,46 +2,67 @@ import AuthenticationServices import curveSecp256k1 import OSLog import SessionManager +import BigInt +#if canImport(UIKit) +import UIKit +#endif +import Combine +import FetchNodeDetails +import SessionManager +import TorusUtils +import JWTDecode +#if canImport(curveSecp256k1) + import curveSecp256k1 +#endif + /** Authentication using Web3Auth. */ public class Web3Auth: NSObject { - private var initParams: W3AInitParams + private var web3AuthOptions: Web3AuthOptions private var authSession: ASWebAuthenticationSession? - // You can check the state variable before logging the user in, if the user - // has an active session the state variable will already have all the values you + // You can check the web3AuthResponse variable before logging the user in, if the user + // has an active session the web3AuthResponse variable will already have all the values you // get from login so the user does not have to re-login - public var state: Web3AuthState? + public var web3AuthResponse: Web3AuthResponse? var sessionManager: SessionManager var webViewController: WebViewController = DispatchQueue.main.sync { WebViewController(onSignResponse: { _ in }) } - private var w3ALoginParams: W3ALoginParams? + private var loginParams: LoginParams? private static var signResponse: SignResponse? - - let SIGNER_MAP: [Network: String] = [ - .mainnet: "https://signer.web3auth.io", - .testnet: "https://signer.web3auth.io", - .cyan: "https://signer-polygon.web3auth.io", - .aqua: "https://signer-polygon.web3auth.io", - .sapphire_mainnet: "https://signer.web3auth.io", - .sapphire_devnet: "https://signer.web3auth.io", + private var projectConfigResponse: ProjectConfigResponse? = nil + let nodeDetailManager: NodeDetailManager + let torusUtils: TorusUtils + + let SIGNER_MAP: [Web3AuthNetwork: String] = [ + .MAINNET: "https://signer.web3auth.io", + .TESTNET: "https://signer.web3auth.io", + .CYAN: "https://signer-polygon.web3auth.io", + .AQUA: "https://signer-polygon.web3auth.io", + .SAPPHIRE_MAINNET: "https://signer.web3auth.io", + .SAPPHIRE_DEVNET: "https://signer.web3auth.io", ] /** Web3Auth component for authenticating with web-based flow. ``` - Web3Auth(OLInitParams(clientId: clientId, network: .mainnet)) + Web3Auth(Web3AuthOptions(clientId: clientId, network: .mainnet)) ``` - parameter params: Init params for your Web3Auth instance. - returns: Web3Auth component. */ - public init(_ params: W3AInitParams) async throws { - initParams = params - Router.baseURL = SIGNER_MAP[params.network] ?? "" - sessionManager = SessionManager(sessionTime: params.sessionTime, allowedOrigin: params.redirectUrl) + public init(options: Web3AuthOptions) async throws { + web3AuthOptions = options + Router.baseURL = SIGNER_MAP[options.web3AuthNetwork] ?? "" + let isSFA = KeychainHelper.shared.get(forKey: "isSFA", as: Bool.self) + let sessionNamespace = isSFA ?? false ? "sfa" : "" + sessionManager = SessionManager(sessionTime: options.sessionTime, allowedOrigin: options.redirectUrl, sessionNamespace: sessionNamespace) + nodeDetailManager = NodeDetailManager(network: options.web3AuthNetwork) + let torusOptions = TorusOptions(clientId: options.clientId, network: options.web3AuthNetwork, serverTimeOffset: options.sessionTime, enableOneKey: true) + try torusUtils = TorusUtils(params: torusOptions) super.init() let fetchConfigResult = try await fetchProjectConfig() if fetchConfigResult { @@ -50,10 +71,12 @@ public class Web3Auth: NSObject { sessionManager.setSessionId(sessionId: sessionId!) do { // Restore from valid session - let loginDetailsDict = try await sessionManager.authorizeSession(origin: params.redirectUrl) - guard let loginDetails = Web3AuthState(dict: loginDetailsDict, sessionID: sessionManager.getSessionId(), - network: initParams.network) else { throw Web3AuthError.decodingError } - state = loginDetails + let loginDetailsDict = try await sessionManager.authorizeSession(origin: options.redirectUrl) + guard let loginDetails = Web3AuthResponse(dict: loginDetailsDict, sessionID: sessionManager.getSessionId(), web3AuthNetwork: options.web3AuthNetwork) + else { + throw Web3AuthError.decodingError + } + web3AuthResponse = loginDetails } catch SessionManagerError.dataNotFound { // Clear invalid session SessionManager.deleteSessionIdFromStorage() @@ -64,13 +87,14 @@ public class Web3Auth: NSObject { } public func logout() async throws { - guard let state = state else { throw Web3AuthError.noUserFound } + guard let web3AuthResponse = web3AuthResponse else { throw Web3AuthError.noUserFound } try await sessionManager.invalidateSession() SessionManager.deleteSessionIdFromStorage() - if let verifer = state.userInfo?.verifier, let dappShare = KeychainManager.shared.getDappShare(verifier: verifer) { + if let authConnectionId = web3AuthResponse.userInfo?.authConnectionId, let dappShare = KeychainManager.shared.getDappShare(authConnectionId: authConnectionId) { KeychainManager.shared.delete(key: .custom(dappShare)) } - self.state = nil + KeychainHelper.shared.clearAll() + self.web3AuthResponse = nil } public func getLoginId(sessionId: String, data: T) async throws -> String? { @@ -78,10 +102,10 @@ public class Web3Auth: NSObject { return try await sessionManager.createSession(data: data) } - private func getLoginDetails(_ callbackURL: URL) async throws -> Web3AuthState { - let loginDetailsDict = try await sessionManager.authorizeSession(origin: initParams.redirectUrl) + private func getLoginDetails(_ callbackURL: URL) async throws -> Web3AuthResponse { + let loginDetailsDict = try await sessionManager.authorizeSession(origin: web3AuthOptions.redirectUrl) guard - let loginDetails = Web3AuthState(dict: loginDetailsDict, sessionID: sessionManager.getSessionId(), network: initParams.network) + let loginDetails = Web3AuthResponse(dict: loginDetailsDict, sessionID: sessionManager.getSessionId(), web3AuthNetwork: web3AuthOptions.web3AuthNetwork) else { throw Web3AuthError.decodingError } @@ -117,9 +141,9 @@ public class Web3Auth: NSObject { */ public convenience init(_ bundle: Bundle = Bundle.main) async throws { let values = plistValues(bundle)! - try await self.init(W3AInitParams( + try await self.init(options: Web3AuthOptions( clientId: values.clientId, - network: values.network, + web3AuthNetwork: values.web3AuthNetwork, redirectUrl: values.redirectUrl )) } @@ -138,7 +162,7 @@ public class Web3Auth: NSObject { User info: Name: \(result.userInfo.name) Profile image: \(result.userInfo.profileImage ?? "N/A") - Type of login: \(result.userInfo.typeOfLogin) + Type of login: \(result.userInfo.authConnection) """) case .failure(let error): print("Error: \(error)") @@ -151,22 +175,16 @@ public class Web3Auth: NSObject { - parameter callback: Callback called with the result of the WebAuth flow. */ - public func login(_ loginParams: W3ALoginParams) async throws -> Web3AuthState { - self.w3ALoginParams = loginParams + public func login(loginParams: LoginParams) async throws -> Web3AuthResponse { + self.loginParams = loginParams // assign loginParams redirectUrl from intiParamas redirectUrl - if w3ALoginParams!.redirectUrl == nil { - w3ALoginParams!.redirectUrl = initParams.redirectUrl - } - if w3ALoginParams?.redirectUrl == nil { - throw Web3AuthError.invalidOrMissingRedirectURI - } - if let loginConfig = initParams.loginConfig?.values.first, - let savedDappShare = KeychainManager.shared.getDappShare(verifier: loginConfig.verifier) { - w3ALoginParams?.dappShare = savedDappShare + if let authConnectionConfig = web3AuthOptions.authConnectionConfig?.first, + let savedDappShare = KeychainManager.shared.getDappShare(authConnectionId: authConnectionConfig.authConnectionId) { + self.loginParams?.dappShare = savedDappShare } - - let sdkUrlParams = SdkUrlParams(options: initParams, params: w3ALoginParams!, actionType: "login") + + let sdkUrlParams = SdkUrlParams(options: web3AuthOptions, params: self.loginParams!, actionType: "login") let sessionId = try SessionManager.generateRandomSessionID()! let loginId = try await getLoginId(sessionId: sessionId, data: sdkUrlParams) @@ -174,13 +192,13 @@ public class Web3Auth: NSObject { "loginId": loginId, ] - let url = try Web3Auth.generateAuthSessionURL(initParams: initParams, jsonObject: jsonObject, sdkUrl: initParams.sdkUrl?.absoluteString, path: "start") + let url = try Web3Auth.generateAuthSessionURL(web3AuthOptions: web3AuthOptions, jsonObject: jsonObject, sdkUrl: web3AuthOptions.sdkUrl, path: "start") - return try await withCheckedThrowingContinuation({ (continuation: CheckedContinuation) in + return try await withCheckedThrowingContinuation({ (continuation: CheckedContinuation) in DispatchQueue.main.async { [self] in // Ensure the UI-related setup is on the main thread. self.authSession = ASWebAuthenticationSession( - url: url, callbackURLScheme: URL(string: w3ALoginParams!.redirectUrl!)!.scheme + url: url, callbackURLScheme: URL(string: self.web3AuthOptions.redirectUrl)?.scheme ) { callbackURL, authError in guard @@ -208,7 +226,7 @@ public class Web3Auth: NSObject { KeychainManager.shared.saveDappShare(userInfo: safeUserInfo) } - self.state = loginDetails + self.web3AuthResponse = loginDetails continuation.resume(returning: loginDetails) } catch { continuation.resume(throwing: Web3AuthError.unknownError) @@ -224,62 +242,231 @@ public class Web3Auth: NSObject { } }) } + + public func connectTo(loginParams: LoginParams) async throws -> Web3AuthResponse { + + sessionManager = SessionManager( + sessionTime: self.web3AuthOptions.sessionTime, + allowedOrigin: web3AuthOptions.redirectUrl, + sessionNamespace: (loginParams.idToken?.isEmpty == false) ? "sfa" : "" + ) + // Case 1: No idToken provided + if loginParams.idToken?.isEmpty ?? true { + if let loginHint = loginParams.loginHint, !loginHint.isEmpty { + // Create or update extraLoginOptions with loginHint + var updatedExtraLoginOptions = loginParams.extraLoginOptions + if updatedExtraLoginOptions == nil { + updatedExtraLoginOptions = ExtraLoginOptions(login_hint: loginHint) + } else { + updatedExtraLoginOptions?.login_hint = loginHint + } + + var updatedLoginParams = loginParams + updatedLoginParams.extraLoginOptions = updatedExtraLoginOptions + + return try await login(loginParams: updatedLoginParams) // PnP login + } else { + return try await login(loginParams: loginParams) // PnP login + } + } + + // Case 2: idToken exists + if let groupedId = loginParams.groupedAuthConnectionId, !groupedId.isEmpty { + let newLoginParams = LoginParams( + authConnection: .CUSTOM, + authConnectionId: groupedId, + idToken: loginParams.idToken + ) + let subVerifierInfoArray = [ + Web3AuthSubVerifierInfo( + verifier: loginParams.authConnectionId ?? "", + idToken: loginParams.idToken ?? "" + ) + ] + KeychainHelper.shared.save(true, forKey: KeychainKeys.isSFA) + return try await connect(loginParams: newLoginParams, subVerifierInfoArray: subVerifierInfoArray) + } else { + KeychainHelper.shared.save(true, forKey: KeychainKeys.isSFA) + return try await connect(loginParams: loginParams) // SFA login fallback + } + } + + + private func getTorusKey(loginParams: LoginParams, subVerifierInfoArray: [Web3AuthSubVerifierInfo]? = nil) async throws -> TorusKey { + var retrieveSharesResponse: TorusKey + + let userId = getUserId(from: loginParams.idToken!) + let details = try await nodeDetailManager.getNodeDetails(verifier: loginParams.authConnectionId!, verifierID: userId!) + + if let subVerifierInfoArray = subVerifierInfoArray, !subVerifierInfoArray.isEmpty { + var aggregateIdTokenSeeds = [String]() + var subVerifierIds = [String]() + var verifyParams = [VerifyParams]() + for value in subVerifierInfoArray { + aggregateIdTokenSeeds.append(value.idToken) + + let verifyParam = VerifyParams(verifier_id: userId, idtoken: value.idToken) + + verifyParams.append(verifyParam) + subVerifierIds.append(value.verifier) + } + aggregateIdTokenSeeds.sort() + + let verifierParams = VerifierParams(verifier_id: userId!, sub_verifier_ids: subVerifierIds, verify_params: verifyParams) + + let aggregateIdToken = try curveSecp256k1.keccak256(data: Data(aggregateIdTokenSeeds.joined(separator: "\u{001d}").utf8)).toHexString() + + retrieveSharesResponse = try await torusUtils.retrieveShares( + endpoints: details.getTorusNodeEndpoints(), + verifier: loginParams.authConnectionId!, + verifierParams: verifierParams, + idToken: aggregateIdToken + ) + } else { + let verifierParams = VerifierParams(verifier_id: userId!) + + retrieveSharesResponse = try await torusUtils.retrieveShares( + endpoints: details.getTorusNodeEndpoints(), + verifier: loginParams.authConnectionId!, + verifierParams: verifierParams, + idToken: loginParams.idToken! + ) + } + + if retrieveSharesResponse.metadata.upgraded == true { + throw Web3AuthError.mfaAlreadyEnabled + } + + return retrieveSharesResponse + } - public func enableMFA(_ loginParams: W3ALoginParams? = nil) async throws -> Bool { + public func connect(loginParams: LoginParams, subVerifierInfoArray: [Web3AuthSubVerifierInfo]? = nil) async throws -> Web3AuthResponse { + let torusKey: TorusKey + if let array = subVerifierInfoArray, !array.isEmpty { + torusKey = try await getTorusKey(loginParams: loginParams, subVerifierInfoArray: array) + } else { + torusKey = try await getTorusKey(loginParams: loginParams) + } + + let privateKey = if (torusKey.finalKeyData.privKey.isEmpty) { + torusKey.oAuthKeyData.privKey + } else { + torusKey.finalKeyData.privKey + } + + var decodedUserInfo: Web3AuthUserInfo? = nil + + do { + let jwt = try decode(jwt: loginParams.idToken!) + decodedUserInfo = Web3AuthUserInfo.init(email: jwt.body["email"] as? String ?? "", + name: jwt.body["name"] as? String ?? "", + profileImage: jwt.body["picture"] as? String ?? "", + groupedAuthConnectionId: loginParams.groupedAuthConnectionId, + authConnectionId: loginParams.authConnectionId, userId: jwt.body["user_id"] as? String ?? "", + dappShare: nil, idToken: nil, oAuthIdToken: nil, oAuthAccessToken: nil, isMfaEnabled: false, authConnection: "custom", appState: nil) + } catch { + throw Web3AuthError.inValidLogin + } + + let sessionId = try SessionManager.generateRandomSessionID()! + sessionManager.setSessionId(sessionId: sessionId) + + let web3AuthResponse = Web3AuthResponse(privateKey: privateKey, ed25519PrivateKey: nil, sessionId: nil, userInfo: decodedUserInfo, error: nil, coreKitKey: nil, coreKitEd25519PrivKey: nil, factorKey: nil, signatures: getSignatureData(sessionTokenData: torusKey.sessionData.sessionTokenData), tssShareIndex: 0, tssPubKey: nil, tssShare: nil, tssTag: nil, tssNonce: 0, nodeIndexes: [], keyMode: nil) + + _ = try await sessionManager.createSession(data: web3AuthResponse) + + SessionManager.saveSessionIdToStorage(sessionId) + sessionManager.setSessionId(sessionId: sessionId) + //self.state = sfaKey + return web3AuthResponse + } + + private func getSignatureData(sessionTokenData: [SessionToken?]) -> [String] { + return sessionTokenData + .compactMap { $0 } // Filters out nil values + .map { session in + """ + {"data":"\(session.token)","sig":"\(session.signature)"} + """ + } + } + + private func getUserId(from token: String) -> String? { + do { + let jwt = try decode(jwt: token) + return jwt.claim(name: "user_id").string + } catch { + print("Failed to decode JWT: \(error)") + return nil + } + } + + public func enableMFA(_ loginParams: LoginParams? = nil) async throws -> Bool { // Note that this function can be called without login on restored session, so loginParams should not be optional. - if state?.userInfo?.isMfaEnabled == true { + if web3AuthResponse?.userInfo?.isMfaEnabled == true { throw Web3AuthError.mfaAlreadyEnabled } - let sessionId = sessionManager.getSessionId() + + if let idToken = self.loginParams?.idToken, !idToken.isEmpty { + throw Web3AuthError.enabledMfaNotAllowed + } + + let sessionId = SessionManager.getSessionIdFromStorage()! if !sessionId.isEmpty { if loginParams != nil { - self.w3ALoginParams = loginParams - if w3ALoginParams?.redirectUrl == nil { - w3ALoginParams?.redirectUrl = initParams.redirectUrl - } - if w3ALoginParams?.redirectUrl == nil { - throw Web3AuthError.invalidOrMissingRedirectURI - } + self.loginParams = loginParams } var extraLoginOptions: ExtraLoginOptions? = ExtraLoginOptions() if loginParams?.extraLoginOptions != nil { extraLoginOptions = loginParams?.extraLoginOptions } else { - extraLoginOptions = w3ALoginParams?.extraLoginOptions + extraLoginOptions = self.loginParams?.extraLoginOptions } - extraLoginOptions?.login_hint = state?.userInfo?.verifierId + extraLoginOptions?.login_hint = web3AuthResponse?.userInfo?.userId let jsonData = try? JSONEncoder().encode(extraLoginOptions) let _extraLoginOptions = String(data: jsonData!, encoding: .utf8) - let redirectUrl = if self.w3ALoginParams != nil { - self.w3ALoginParams?.redirectUrl - } else { - initParams.redirectUrl - } + let redirectUrl = web3AuthOptions.redirectUrl + + let newSessionId = try SessionManager.generateRandomSessionID()! + let loginIdObject: [String: String?] = [ + "loginId": newSessionId, + "platform": "iOS", + ] + + let jsonEncoder = JSONEncoder() + let data = try? jsonEncoder.encode(loginIdObject) let params: [String: String?] = [ - "loginProvider": state?.userInfo?.typeOfLogin, + "authConnection": web3AuthResponse?.userInfo?.authConnection, + "authConnectionId": web3AuthResponse?.userInfo?.authConnectionId, + "groupedAuthConnectionId" : web3AuthResponse?.userInfo?.groupedAuthConnectionId, "mfaLevel": MFALevel.MANDATORY.rawValue, - "redirectUrl": URL(string: redirectUrl!)?.absoluteString, + "redirectUrl": redirectUrl, "extraLoginOptions": _extraLoginOptions, + "appState": data?.toBase64URL(), ] - let setUpMFAParams = SetUpMFAParams(options: initParams, params: params, actionType: "enable_mfa", sessionId: sessionId) - let sessionId = try SessionManager.generateRandomSessionID()! - let loginId = try await getLoginId(sessionId: sessionId, data: setUpMFAParams) + let setUpMFAParams = SetUpMFAParams(options: web3AuthOptions, params: params, actionType: "enable_mfa", sessionId: sessionId) + if let jsonData = try? JSONEncoder().encode(setUpMFAParams), + let jsonString = String(data: jsonData, encoding: .utf8) { + print("setUpMFAParams JSON: \(jsonString)") + } + + let loginId = try await getLoginId(sessionId: newSessionId, data: setUpMFAParams) let jsonObject: [String: String?] = [ "loginId": loginId, ] - let url = try Web3Auth.generateAuthSessionURL(initParams: initParams, jsonObject: jsonObject, sdkUrl: initParams.sdkUrl?.absoluteString, path: "start") + let url = try Web3Auth.generateAuthSessionURL(web3AuthOptions: web3AuthOptions, jsonObject: jsonObject, sdkUrl: web3AuthOptions.sdkUrl, path: "start") return try await withCheckedThrowingContinuation({ (continuation: CheckedContinuation) in DispatchQueue.main.async { // Ensure UI-related calls are made on the main thread self.authSession = ASWebAuthenticationSession( - url: url, callbackURLScheme: URL(string: redirectUrl!)?.scheme + url: url, callbackURLScheme: URL(string: self.web3AuthOptions.redirectUrl)?.scheme ) { callbackURL, authError in guard authError == nil, @@ -305,7 +492,7 @@ public class Web3Auth: NSObject { if let safeUserInfo = loginDetails.userInfo { KeychainManager.shared.saveDappShare(userInfo: safeUserInfo) } - self.state = loginDetails + self.web3AuthResponse = loginDetails continuation.resume(returning: true) } catch { continuation.resume(throwing: Web3AuthError.unknownError) @@ -324,26 +511,30 @@ public class Web3Auth: NSObject { } } - public func manageMFA(_ loginParams: W3ALoginParams? = nil) async throws -> Bool { - if state?.userInfo?.isMfaEnabled == false { + public func manageMFA(_ loginParams: LoginParams? = nil) async throws -> Bool { + if web3AuthResponse?.userInfo?.isMfaEnabled == false { throw Web3AuthError.mfaNotEnabled } - let sessionId = sessionManager.getSessionId() + if let idToken = self.loginParams?.idToken, !idToken.isEmpty { + throw Web3AuthError.enabledMfaNotAllowed + } + + let sessionId = SessionManager.getSessionIdFromStorage()! if sessionId.isEmpty { throw Web3AuthError.runtimeError("SessionId not found. Please login first.") } - var modifiedLoginParams = self.w3ALoginParams - var modifiedInitParams = initParams + var modifiedLoginParams = self.loginParams + var modifiedInitParams = web3AuthOptions if loginParams != nil { modifiedLoginParams = loginParams - modifiedLoginParams?.redirectUrl = modifiedInitParams.dashboardUrl?.absoluteString + //modifiedLoginParams?.redirectUrl = modifiedInitParams.dashboardUrl } var extraLoginOptions: ExtraLoginOptions? = modifiedLoginParams?.extraLoginOptions ?? loginParams?.extraLoginOptions ?? ExtraLoginOptions() - extraLoginOptions?.login_hint = state?.userInfo?.verifierId + extraLoginOptions?.login_hint = web3AuthResponse?.userInfo?.userId let jsonData = try? JSONEncoder().encode(extraLoginOptions) let _extraLoginOptions = jsonData.flatMap { String(data: $0, encoding: .utf8) } @@ -357,25 +548,27 @@ public class Web3Auth: NSObject { let jsonEncoder = JSONEncoder() let data = try? jsonEncoder.encode(loginIdObject) - let dappUrl = modifiedInitParams.redirectUrl + let dappUrl = self.web3AuthOptions.redirectUrl let params: [String: String?] = [ - "loginProvider": state?.userInfo?.typeOfLogin, + "authConnection": web3AuthResponse?.userInfo?.authConnection, + "authConnectionId": web3AuthResponse?.userInfo?.authConnectionId, + "groupedAuthConnectionId" : web3AuthResponse?.userInfo?.groupedAuthConnectionId, "mfaLevel": MFALevel.MANDATORY.rawValue, - "redirectUrl": modifiedInitParams.dashboardUrl?.absoluteString, + "redirectUrl": modifiedInitParams.dashboardUrl, "extraLoginOptions": _extraLoginOptions, "appState": data?.toBase64URL(), "dappUrl": dappUrl ] - modifiedInitParams.redirectUrl = modifiedInitParams.dashboardUrl!.absoluteString + modifiedInitParams.redirectUrl = modifiedInitParams.dashboardUrl! let setUpMFAParams = SetUpMFAParams(options: modifiedInitParams, params: params, actionType: "manage_mfa", sessionId: sessionId) let loginId = try await getLoginId(sessionId: newSessionId, data: setUpMFAParams) let jsonObject: [String: String?] = ["loginId": loginId] - let url = try Web3Auth.generateAuthSessionURL(initParams: modifiedInitParams, jsonObject: jsonObject, sdkUrl: modifiedInitParams.sdkUrl?.absoluteString, path: "start") + let url = try Web3Auth.generateAuthSessionURL(web3AuthOptions: modifiedInitParams, jsonObject: jsonObject, sdkUrl: modifiedInitParams.sdkUrl, path: "start") return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in DispatchQueue.main.async { @@ -403,24 +596,55 @@ public class Web3Auth: NSObject { } } - public func launchWalletServices(chainConfig: ChainConfig, path: String? = "wallet") async throws { + public func showWalletUI(path: String? = "wallet") async throws { let savedSessionId = SessionManager.getSessionIdFromStorage()! if !savedSessionId.isEmpty { - initParams.chainConfig = chainConfig - let walletServicesParams = WalletServicesParams(options: initParams, appState: nil) + var initOptionsJson = try JSONSerialization.jsonObject(with: JSONEncoder().encode(web3AuthOptions)) as! [String: Any] + + if let chains = projectConfigResponse?.chains { + let chainsData = try JSONEncoder().encode(chains) + let chainsJson = try JSONSerialization.jsonObject(with: chainsData) as! [Any] + initOptionsJson["chains"] = chainsJson + initOptionsJson["chainId"] = chains.first?.chainId ?? web3AuthOptions.defaultChainId ?? "0x1" + } + + + if let embeddedWalletAuth = projectConfigResponse?.embeddedWalletAuth { + let authData = try JSONEncoder().encode(embeddedWalletAuth) + let authArray = try JSONSerialization.jsonObject(with: authData) as! [Any] + initOptionsJson["embeddedWalletAuth"] = authArray + } + + if let smartAccounts = projectConfigResponse?.smartAccounts { + let saData = try JSONEncoder().encode(smartAccounts) + let saJson = try JSONSerialization.jsonObject(with: saData) as! [String: Any] + initOptionsJson["accountAbstractionConfig"] = saJson + } + + let paramMap: [String: Any] = [ + "options": initOptionsJson + ] + let sessionId = try SessionManager.generateRandomSessionID()! - let loginId = try await getLoginId(sessionId: sessionId ,data: walletServicesParams) + let jsonData = try JSONSerialization.data(withJSONObject: paramMap) + let jsonString = String(data: jsonData, encoding: .utf8)! - let jsonObject: [String: String?] = [ + let loginId = try await getLoginId(sessionId: sessionId, data: jsonString) + + var jsonObject: [String: String?] = [ "loginId": loginId, "sessionId": savedSessionId, "platform": "ios", ] + + if let isSFA = KeychainHelper.shared.get(forKey: "isSFA", as: Bool.self), isSFA { + jsonObject["sessionNamespace"] = "sfa" + } let url = try Web3Auth.generateAuthSessionURL( - initParams: initParams, + web3AuthOptions: web3AuthOptions, jsonObject: jsonObject, - sdkUrl: initParams.walletSdkUrl?.absoluteString, + sdkUrl: web3AuthOptions.walletSdkUrl, path: path ) @@ -438,20 +662,49 @@ public class Web3Auth: NSObject { } } - public func request(chainConfig: ChainConfig, method: String, requestParams: [Any], path: String? = "wallet/request", appState: String? = nil) async throws -> SignResponse? { + public func request(method: String, requestParams: [Any], path: String? = "wallet/request", appState: String? = nil) async throws -> SignResponse? { let sessionId = SessionManager.getSessionIdFromStorage()! if !sessionId.isEmpty { - initParams.chainConfig = chainConfig + var initOptionsJson = try JSONSerialization.jsonObject(with: JSONEncoder().encode(web3AuthOptions)) as! [String: Any] + + if let chains = projectConfigResponse?.chains { + let chainsData = try JSONEncoder().encode(chains) + let chainsJson = try JSONSerialization.jsonObject(with: chainsData) as! [Any] + initOptionsJson["chains"] = chainsJson + initOptionsJson["chainId"] = chains.first?.chainId ?? web3AuthOptions.defaultChainId ?? "0x1" + } + + if let embeddedWalletAuth = projectConfigResponse?.embeddedWalletAuth { + let authData = try JSONEncoder().encode(embeddedWalletAuth) + let authArray = try JSONSerialization.jsonObject(with: authData) as! [Any] + initOptionsJson["embeddedWalletAuth"] = authArray + } + + if let smartAccounts = projectConfigResponse?.smartAccounts { + let saData = try JSONEncoder().encode(smartAccounts) + let saJson = try JSONSerialization.jsonObject(with: saData) as! [String: Any] + initOptionsJson["accountAbstractionConfig"] = saJson + } + + let paramMap: [String: Any] = [ + "options": initOptionsJson + ] + + let jsonData = try JSONSerialization.data(withJSONObject: paramMap) + let jsonString = String(data: jsonData, encoding: .utf8)! - let walletServicesParams = WalletServicesParams(options: initParams, appState: appState) let loginId = try SessionManager.generateRandomSessionID()! - let _loginId = try await getLoginId(sessionId: loginId, data: walletServicesParams) + let _loginId = try await getLoginId(sessionId: loginId, data: jsonString) var signMessageMap: [String: String] = [:] signMessageMap["loginId"] = _loginId signMessageMap["sessionId"] = sessionId signMessageMap["platform"] = "ios" signMessageMap["appState"] = appState + + if let isSFA = KeychainHelper.shared.get(forKey: "isSFA", as: Bool.self), isSFA { + signMessageMap["sessionNamespace"] = "sfa" + } var requestData: [String: Any] = [:] requestData["method"] = method @@ -463,13 +716,14 @@ public class Web3Auth: NSObject { signMessageMap["request"] = requestDataJsonString } - let url = try Web3Auth.generateAuthSessionURL(initParams: initParams, jsonObject: signMessageMap, sdkUrl: initParams.walletSdkUrl?.absoluteString, path: path) + let url = try Web3Auth.generateAuthSessionURL(web3AuthOptions: web3AuthOptions, jsonObject: signMessageMap, sdkUrl: web3AuthOptions.walletSdkUrl, + path: path) // open url in webview return await withCheckedContinuation { continuation in Task { let webViewController = await MainActor.run { - WebViewController(redirectUrl: initParams.redirectUrl, onSignResponse: { signResponse in + WebViewController(redirectUrl: web3AuthOptions.redirectUrl, onSignResponse: { signResponse in continuation.resume(returning: signResponse) }, onCancel: { continuation.resume(returning: nil) @@ -488,7 +742,7 @@ public class Web3Auth: NSObject { } } - static func generateAuthSessionURL(initParams: W3AInitParams, jsonObject: [String: String?], sdkUrl: String?, path: String?) throws -> URL { + static func generateAuthSessionURL(web3AuthOptions: Web3AuthOptions, jsonObject: [String: String?], sdkUrl: String?, path: String?) throws -> URL { let jsonEncoder = JSONEncoder() jsonEncoder.outputFormatting.insert(.sortedKeys) @@ -539,7 +793,7 @@ public class Web3Auth: NSObject { public func fetchProjectConfig() async throws -> Bool { var response: Bool = false - let api = Router.get([.init(name: "project_id", value: initParams.clientId), .init(name: "network", value: initParams.network.rawValue), .init(name: "whitelist", value: "true")]) + let api = Router.get([.init(name: "project_id", value: web3AuthOptions.clientId), .init(name: "network", value: web3AuthOptions.web3AuthNetwork.name), .init(name: "build_env", value: web3AuthOptions.authBuildEnv?.rawValue)]) let result = await Service.request(router: api) switch result { case let .success(data): @@ -547,16 +801,23 @@ public class Web3Auth: NSObject { let decoder = JSONDecoder() let result = try decoder.decode(ProjectConfigResponse.self, from: data) // os_log("fetchProjectConfig API response is: %@", log: getTorusLogger(log: Web3AuthLogger.network, type: .info), type: .info, "\(String(describing: result))") - initParams.originData = result.whitelist.signedUrls.merging(initParams.originData ?? [:]) { _, new in new } - if let whiteLabelData = result.whiteLabelData { - if initParams.whiteLabel == nil { - initParams.whiteLabel = whiteLabelData - } else { - initParams.whiteLabel = initParams.whiteLabel?.merge(with: whiteLabelData) + projectConfigResponse = result + web3AuthOptions.originData = result.whitelist.signedUrls.merging(web3AuthOptions.originData ?? [:]) { _, new in new } + web3AuthOptions.authConnectionConfig = + (web3AuthOptions.authConnectionConfig ?? []) + (projectConfigResponse?.embeddedWalletAuth ?? []) + if let whiteLabelData = result.whitelabel { + web3AuthOptions.whiteLabel = web3AuthOptions.whiteLabel?.merge(with: whiteLabelData) ?? whiteLabelData + if web3AuthOptions.walletServicesConfig == nil { + web3AuthOptions.walletServicesConfig = WalletServicesConfig() + } + if var walletConfig = web3AuthOptions.walletServicesConfig { + walletConfig.whiteLabel = walletConfig.whiteLabel?.merge(with: whiteLabelData) ?? whiteLabelData + web3AuthOptions.walletServicesConfig = walletConfig } } response = true } catch { + //print("Decoding failed: \(error)") throw error } case let .failure(error): @@ -565,33 +826,33 @@ public class Web3Auth: NSObject { return response } - public func getPrivkey() -> String { - if state == nil { + public func getPrivateKey() -> String { + if web3AuthResponse == nil { return "" } - let privKey: String = initParams.useCoreKitKey == true ? state?.coreKitKey ?? "" : state?.privKey ?? "" - return privKey + let privateKey: String = web3AuthOptions.useSFAKey == true ? web3AuthResponse?.coreKitKey ?? "" : web3AuthResponse?.privateKey ?? "" + return privateKey } - public func getEd25519PrivKey() -> String { - if state == nil { + public func getEd25519PrivateKey() -> String { + if web3AuthResponse == nil { return "" } - let ed25519Key: String = initParams.useCoreKitKey == true ? - state?.coreKitEd25519PrivKey ?? "" : state?.ed25519PrivKey ?? "" + let ed25519Key: String = web3AuthOptions.useSFAKey == true ? + web3AuthResponse?.coreKitEd25519PrivKey ?? "" : web3AuthResponse?.ed25519PrivateKey ?? "" return ed25519Key } public func getUserInfo() throws -> Web3AuthUserInfo { - guard let state = state, let userInfo = state.userInfo else { throw Web3AuthError.noUserFound } + guard let web3AuthResponse = web3AuthResponse, let userInfo = web3AuthResponse.userInfo else { throw Web3AuthError.noUserFound } return userInfo } - public func getWeb3AuthResponse() throws -> Web3AuthState { - guard let state = state else { + public func getWeb3AuthResponse() throws -> Web3AuthResponse { + guard let web3AuthResponse = web3AuthResponse else { throw Web3AuthError.noUserFound } - return state + return web3AuthResponse } } diff --git a/Sources/Web3Auth/Web3AuthError.swift b/Sources/Web3Auth/Web3AuthError.swift index cf1f941..73ec969 100644 --- a/Sources/Web3Auth/Web3AuthError.swift +++ b/Sources/Web3Auth/Web3AuthError.swift @@ -15,6 +15,8 @@ public enum Web3AuthError: Error { case mfaAlreadyEnabled case mfaNotEnabled case invalidOrMissingRedirectURI + case inValidLogin + case enabledMfaNotAllowed } extension Web3AuthError: LocalizedError { @@ -42,6 +44,10 @@ extension Web3AuthError: LocalizedError { return "MFA is not enabled. Please enable MFA first." case .invalidOrMissingRedirectURI: return "Invalid or missing redirect URI." + case .inValidLogin: + return "Invalid login credentials." + case .enabledMfaNotAllowed: + return "Enabling MFA is not allowed for this user." } } } diff --git a/Sources/Web3Auth/Web3AuthState.swift b/Sources/Web3Auth/Web3AuthState.swift index 6f591a5..ec78c65 100644 --- a/Sources/Web3Auth/Web3AuthState.swift +++ b/Sources/Web3Auth/Web3AuthState.swift @@ -1,11 +1,12 @@ import Foundation +import FetchNodeDetails /** User's credentials and info obtained from Web3Auth. */ -public struct Web3AuthState: Codable { - public let privKey: String? - public let ed25519PrivKey: String? +public struct Web3AuthResponse: Codable { + public let privateKey: String? + public let ed25519PrivateKey: String? public let sessionId: String? public let userInfo: Web3AuthUserInfo? public let error: String? @@ -16,14 +17,34 @@ public struct Web3AuthState: Codable { public var tssShareIndex: Int? public var tssPubKey: String? public var tssShare: String? + public var tssTag: String? public var tssNonce: Int? public var nodeIndexes: [Int]? public var keyMode: String? + + enum CodingKeys: String, CodingKey { + case privateKey = "privKey" + case ed25519PrivateKey = "ed25519PrivKey" + case sessionId + case userInfo + case error + case coreKitKey + case coreKitEd25519PrivKey + case factorKey + case signatures + case tssShareIndex + case tssPubKey + case tssShare + case tssTag + case tssNonce + case nodeIndexes + case keyMode + } - public init(privKey: String?, ed25519PrivKey: String?, sessionId: String?, userInfo: Web3AuthUserInfo?, error: String?, - coreKitKey: String?, coreKitEd25519PrivKey: String?, factorKey: String?, signatures: [String]?, tssShareIndex: Int?, tssPubKey: String?, tssShare: String?, tssNonce: Int?, nodeIndexes: [Int]?, keyMode: String?) { - self.privKey = privKey - self.ed25519PrivKey = ed25519PrivKey + public init(privateKey: String?, ed25519PrivateKey: String?, sessionId: String?, userInfo: Web3AuthUserInfo?, error: String?, + coreKitKey: String?, coreKitEd25519PrivKey: String?, factorKey: String?, signatures: [String]?, tssShareIndex: Int?, tssPubKey: String?, tssShare: String?, tssTag: String? ,tssNonce: Int?, nodeIndexes: [Int]?, keyMode: String?) { + self.privateKey = privateKey + self.ed25519PrivateKey = ed25519PrivateKey self.sessionId = sessionId self.userInfo = userInfo self.error = error @@ -34,6 +55,7 @@ public struct Web3AuthState: Codable { self.tssShareIndex = tssShareIndex self.tssPubKey = tssPubKey self.tssShare = tssShare + self.tssTag = tssTag self.tssNonce = tssNonce self.nodeIndexes = nodeIndexes self.keyMode = keyMode @@ -41,8 +63,8 @@ public struct Web3AuthState: Codable { public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - privKey = try container.decodeIfPresent(String.self, forKey: .privKey) - ed25519PrivKey = try container.decodeIfPresent(String.self, forKey: .ed25519PrivKey) + privateKey = try container.decodeIfPresent(String.self, forKey: .privateKey) + ed25519PrivateKey = try container.decodeIfPresent(String.self, forKey: .ed25519PrivateKey) sessionId = try container.decodeIfPresent(String.self, forKey: .sessionId) userInfo = try container.decodeIfPresent(Web3AuthUserInfo.self, forKey: .userInfo) error = try container.decodeIfPresent(String.self, forKey: .error) @@ -53,25 +75,25 @@ public struct Web3AuthState: Codable { tssShareIndex = try container.decodeIfPresent(Int.self, forKey: .tssShareIndex) tssPubKey = try container.decodeIfPresent(String.self, forKey: .tssPubKey) tssShare = try container.decodeIfPresent(String.self, forKey: .tssShare) + tssTag = try container.decodeIfPresent(String.self, forKey: .tssTag) tssNonce = try container.decodeIfPresent(Int.self, forKey: .tssNonce) nodeIndexes = try container.decodeIfPresent([Int].self, forKey: .nodeIndexes) keyMode = try container.decodeIfPresent(String.self, forKey: .keyMode) } } -extension Web3AuthState { - init?(dict: [String: Any], sessionID: String, network: Network) { - guard let privKey = dict["privKey"] as? String, - let ed25519PrivKey = dict["ed25519PrivKey"] as? String, +extension Web3AuthResponse { + init?(dict: [String: Any], sessionID: String, web3AuthNetwork: Web3AuthNetwork) { + guard let privateKey = dict["privKey"] as? String, let userInfoDict = dict["userInfo"] as? [String: Any], let userInfo = Web3AuthUserInfo(dict: userInfoDict) else { return nil } let error = dict["error"] as? String - self.privKey = privKey - self.ed25519PrivKey = ed25519PrivKey + self.privateKey = privateKey sessionId = sessionID self.userInfo = userInfo self.error = error + self.ed25519PrivateKey = dict["ed25519PrivKey"] as? String ?? "" coreKitKey = dict["coreKitKey"] as? String ?? "" coreKitEd25519PrivKey = dict["coreKitEd25519PrivKey"] as? String ?? "" factorKey = dict["factorKey"] as? String @@ -79,6 +101,7 @@ extension Web3AuthState { tssShareIndex = dict["tssShareIndex"] as? Int tssPubKey = dict["tssPubKey"] as? String tssShare = dict["tssShare"] as? String + tssTag = dict["tssTag"] as? String tssNonce = dict["tssShare"] as? Int nodeIndexes = dict["nodeIndexes"] as? [Int] keyMode = dict["keyMode"] as? String diff --git a/Sources/Web3Auth/Web3AuthUserInfo.swift b/Sources/Web3Auth/Web3AuthUserInfo.swift index e320944..8b716a1 100644 --- a/Sources/Web3Auth/Web3AuthUserInfo.swift +++ b/Sources/Web3Auth/Web3AuthUserInfo.swift @@ -6,65 +6,68 @@ import Foundation public struct Web3AuthUserInfo: Codable { public let name: String? public let profileImage: String? - public let typeOfLogin: String? - public let aggregateVerifier: String? - public let verifier: String? - public let verifierId: String? + public let groupedAuthConnectionId: String? + public let authConnectionId: String? + public let userId: String? public let email: String? public let dappShare: String? public let idToken: String? public let oAuthIdToken: String? public let oAuthAccessToken: String? public let isMfaEnabled: Bool? + public let authConnection: String? + public let appState: String? - public init(name: String?, profileImage: String?, typeOfLogin: String?, aggregateVerifier: String?, - verifier: String?, verifierId: String?, email: String?, dappShare: String?, idToken: String?, oAuthIdToken: String?, oAuthAccessToken: String?, - isMfaEnabled: Bool?) { + public init(email: String, name: String?, profileImage: String?, groupedAuthConnectionId: String?, + authConnectionId: String?, userId: String?, dappShare: String?, idToken: String?, oAuthIdToken: String?, oAuthAccessToken: String?, + isMfaEnabled: Bool?, authConnection: String?, appState: String?) { self.name = name self.profileImage = profileImage - self.typeOfLogin = typeOfLogin - self.aggregateVerifier = aggregateVerifier - self.verifier = verifier - self.verifierId = verifierId + self.groupedAuthConnectionId = groupedAuthConnectionId + self.authConnectionId = authConnectionId + self.userId = userId self.email = email self.dappShare = dappShare self.idToken = idToken self.oAuthIdToken = oAuthIdToken self.oAuthAccessToken = oAuthAccessToken self.isMfaEnabled = isMfaEnabled + self.authConnection = authConnection + self.appState = appState } public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) name = try container.decodeIfPresent(String.self, forKey: .name) profileImage = try container.decodeIfPresent(String.self, forKey: .profileImage) - typeOfLogin = try container.decodeIfPresent(String.self, forKey: .typeOfLogin) - aggregateVerifier = try container.decodeIfPresent(String.self, forKey: .aggregateVerifier) - verifier = try container.decodeIfPresent(String.self, forKey: .verifier) - verifierId = try container.decodeIfPresent(String.self, forKey: .verifierId) + groupedAuthConnectionId = try container.decodeIfPresent(String.self, forKey:.groupedAuthConnectionId) + authConnectionId = try container.decodeIfPresent(String.self, forKey: .authConnectionId) + userId = try container.decodeIfPresent(String.self, forKey: .userId) email = try container.decodeIfPresent(String.self, forKey: .email) dappShare = try container.decodeIfPresent(String.self, forKey: .dappShare) idToken = try container.decodeIfPresent(String.self, forKey: .idToken) oAuthIdToken = try container.decodeIfPresent(String.self, forKey: .oAuthIdToken) oAuthAccessToken = try container.decodeIfPresent(String.self, forKey: .oAuthAccessToken) isMfaEnabled = try container.decodeIfPresent(Bool.self, forKey: .isMfaEnabled) + authConnection = try container.decodeIfPresent(String.self, forKey: .authConnection) + appState = try container.decodeIfPresent(String.self, forKey: .appState) } } extension Web3AuthUserInfo { init?(dict: [String: Any]) { - guard let typeOfLogin = dict["typeOfLogin"] else { return nil } - self.typeOfLogin = typeOfLogin as? String name = dict["name"] as? String ?? "" profileImage = dict["profileImage"] as? String ?? "" - aggregateVerifier = dict["aggregateVerifier"] as? String ?? "" - verifier = dict["verifier"] as? String ?? "" - verifierId = dict["verifierId"] as? String ?? "" + groupedAuthConnectionId = dict["groupedAuthConnectionId"] as? String ?? "" + authConnectionId = dict["authConnectionId"] as? String ?? "" + userId = dict["userId"] as? String ?? "" email = dict["email"] as? String ?? "" dappShare = dict["dappShare"] as? String ?? "" idToken = dict["idToken"] as? String ?? "" oAuthIdToken = dict["oAuthIdToken"] as? String ?? "" oAuthAccessToken = dict["oAuthAccessToken"] as? String ?? "" isMfaEnabled = dict["isMfaEnabled"] as? Bool ?? false + authConnection = dict["authConnection"] as? String ?? "" + appState = dict["appState"] as? String ?? "" } } diff --git a/Web3authSwiftSdkDemo/Web3authSwiftSdkDemo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Web3authSwiftSdkDemo/Web3authSwiftSdkDemo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 83c3636..f9b29e3 100644 --- a/Web3authSwiftSdkDemo/Web3authSwiftSdkDemo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Web3authSwiftSdkDemo/Web3authSwiftSdkDemo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -19,6 +19,15 @@ "version": "2.0.0" } }, + { + "package": "FetchNodeDetails", + "repositoryURL": "https://github.com/torusresearch/fetch-node-details-swift", + "state": { + "branch": null, + "revision": "5b42dd1675f8a51ffe64feb688db7b1d764d5fc0", + "version": "8.0.1" + } + }, { "package": "GenericJSON", "repositoryURL": "https://github.com/iwill/generic-json-swift", @@ -28,6 +37,24 @@ "version": "2.0.2" } }, + { + "package": "jwt-kit", + "repositoryURL": "https://github.com/vapor/jwt-kit.git", + "state": { + "branch": null, + "revision": "13e7513b3ba0afa13967daf77af2fb4ad087306c", + "version": "4.13.5" + } + }, + { + "package": "JWTDecode", + "repositoryURL": "https://github.com/auth0/JWTDecode.swift.git", + "state": { + "branch": null, + "revision": "36a5ce735a61c4bc119593f43ce2c027b4ca7392", + "version": "3.3.0" + } + }, { "package": "KeychainSwift", "repositoryURL": "https://github.com/evgenyneu/keychain-swift.git", @@ -55,6 +82,15 @@ "version": "6.1.0" } }, + { + "package": "swift-asn1", + "repositoryURL": "https://github.com/apple/swift-asn1.git", + "state": { + "branch": null, + "revision": "a54383ada6cecde007d374f58f864e29370ba5c3", + "version": "1.3.2" + } + }, { "package": "swift-atomics", "repositoryURL": "https://github.com/apple/swift-atomics.git", @@ -73,6 +109,15 @@ "version": "1.1.4" } }, + { + "package": "swift-crypto", + "repositoryURL": "https://github.com/apple/swift-crypto.git", + "state": { + "branch": null, + "revision": "e8d6eba1fef23ae5b359c46b03f7d94be2f41fed", + "version": "3.12.3" + } + }, { "package": "swift-http-types", "repositoryURL": "https://github.com/apple/swift-http-types", @@ -145,6 +190,15 @@ "version": "1.3.2" } }, + { + "package": "TorusUtils", + "repositoryURL": "https://github.com/torusresearch/torus-utils-swift.git", + "state": { + "branch": null, + "revision": "235a70839d3ff5723402df95d665b15b8c551ad8", + "version": "10.0.1" + } + }, { "package": "web3.swift", "repositoryURL": "https://github.com/argentlabs/web3.swift", diff --git a/Web3authSwiftSdkDemo/Web3authSwiftSdkDemo/LoginView.swift b/Web3authSwiftSdkDemo/Web3authSwiftSdkDemo/LoginView.swift index 06582b9..53b4ceb 100644 --- a/Web3authSwiftSdkDemo/Web3authSwiftSdkDemo/LoginView.swift +++ b/Web3authSwiftSdkDemo/Web3authSwiftSdkDemo/LoginView.swift @@ -6,7 +6,7 @@ struct LoginView: View { List { Button( action: { - vm.login(provider: .EMAIL_PASSWORDLESS) + vm.login(authConnection: .EMAIL_PASSWORDLESS) }, label: { Text("Sign In with Email Passwordless") @@ -14,7 +14,7 @@ struct LoginView: View { ) Button( action: { - vm.loginWithGoogle(provider: .GOOGLE) + vm.loginWithGoogle(authConnection: .GOOGLE) }, label: { Text("Sign In with Google") @@ -31,7 +31,7 @@ struct LoginView: View { Button( action: { - vm.login(provider: .APPLE) + vm.login(authConnection: .APPLE) }, label: { Text("Sign In with Apple") @@ -46,6 +46,15 @@ struct LoginView: View { Text("Sign In with Whitelabel") } ) + + Button( + action: { + vm.sfaLogin() + }, + label: { + Text("SFA Login") + } + ) } .alert(isPresented: $vm.showError) { Alert(title: Text("Error"), message: Text(vm.errorMessage), dismissButton: .default(Text("OK"))) diff --git a/Web3authSwiftSdkDemo/Web3authSwiftSdkDemo/Utils.swift b/Web3authSwiftSdkDemo/Web3authSwiftSdkDemo/Utils.swift new file mode 100644 index 0000000..27c5fb4 --- /dev/null +++ b/Web3authSwiftSdkDemo/Web3authSwiftSdkDemo/Utils.swift @@ -0,0 +1,79 @@ +import Foundation +import JWTKit + +// JWT payload structure. +struct TestPayload: JWTPayload, Equatable { + enum CodingKeys: String, CodingKey { + case subject = "sub" + case expiration = "exp" + case isAdmin = "admin" + case emailVerified = "email_verified" + case issuer = "iss" + case iat + case email + case audience = "aud" + case name + } + + var subject: SubjectClaim + var expiration: ExpirationClaim + var audience: AudienceClaim + var isAdmin: Bool + let emailVerified: Bool + var issuer: IssuerClaim + var iat: IssuedAtClaim + var email: String + var name: String + + // call its verify method. + func verify(using signer: JWTSigner) throws { + try expiration.verifyNotExpired() + } +} + + +func generateRandomEmail(of length: Int) -> String { + let letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + var s = "" + for _ in 0 ..< length { + s.append(letters.randomElement()!) + } + return s + "@gmail.com" +} + +func generateIdToken(email: String) throws -> String { + let verifierPrivateKeyForSigning = + """ + -----BEGIN PRIVATE KEY----- + MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCCD7oLrcKae+jVZPGx52Cb/lKhdKxpXjl9eGNa1MlY57A== + -----END PRIVATE KEY----- + """ + + do { + let signers = JWTSigners() + let keys = try ECDSAKey.private(pem: verifierPrivateKeyForSigning) + signers.use(.es256(key: keys)) + + // Parses the JWT and verifies its signature. + let today = Date() + let modifiedDate = Calendar.current.date(byAdding: .minute, value: 2, to: today)! + + let emailComponent = email.components(separatedBy: "@")[0] + let subject = "email|" + emailComponent + + let payload = TestPayload( + subject: SubjectClaim(stringLiteral: subject), + expiration: ExpirationClaim(value: modifiedDate), // eat + audience: "torus-key-test", + isAdmin: false, + emailVerified: true, + issuer: "torus-key-test", + iat: IssuedAtClaim(value: today), + email: email, + name: email) + let jwt = try signers.sign(payload) + return jwt + } catch { + throw error + } +} diff --git a/Web3authSwiftSdkDemo/Web3authSwiftSdkDemo/ViewModel.swift b/Web3authSwiftSdkDemo/Web3authSwiftSdkDemo/ViewModel.swift index 579a6fd..d67f966 100644 --- a/Web3authSwiftSdkDemo/Web3authSwiftSdkDemo/ViewModel.swift +++ b/Web3authSwiftSdkDemo/Web3authSwiftSdkDemo/ViewModel.swift @@ -1,11 +1,12 @@ import Foundation import web3 import Web3Auth +import FetchNodeDetails class ViewModel: ObservableObject { var web3Auth: Web3Auth? @Published var loggedIn: Bool = false - @Published var user: Web3AuthState? + @Published var user: Web3AuthResponse? @Published var isLoading = false @Published var navigationTitle: String = "" @Published var privateKey: String = "" @@ -13,38 +14,63 @@ class ViewModel: ObservableObject { @Published var userInfo: Web3AuthUserInfo? @Published var showError: Bool = false var errorMessage: String = "" - private var clientID: String = "BG4pe3aBso5SjVbpotFQGnXVHgxhgOxnqnNBKyjfEJ3izFvIVWUaMIzoCrAfYag8O6t6a6AOvdLcS4JR2sQMjR4" + private var clientID: String = "BPi5PB_UiIZ-cPz1GtV5i1I2iOSOHuimiXBI0e-Oe_u6X3oVAbCiAZOTEBtTXw4tsluTITPqA8zMsfxIKMjiqNQ" private var redirectUrl: String = "com.web3auth.sdkapp://auth" - private var network: Network = .sapphire_devnet + private var web3AuthNetwork: Web3AuthNetwork = .SAPPHIRE_MAINNET private var buildEnv: BuildEnv = .testing + let TORUS_TEST_EMAIL = "devnettestuser@tor.us" + let TEST_VERIFIER = "torus-test-health" + let TEST_AGGREGRATE_VERIFIER = "torus-aggregate-sapphire-mainnet" // private var clientID: String = "BEaGnq-mY0ZOXk2UT1ivWUe0PZ_iJX4Vyb6MtpOp7RMBu_6ErTrATlfuK3IaFcvHJr27h6L1T4owkBH6srLphIw" - // private var network: Network = .mainnet + // private var network: Web3AuthNetwork = .mainnet private var useCoreKit: Bool = false - private var chainConfig: ChainConfig = ChainConfig( - chainNamespace: ChainNamespace.eip155, - chainId: "0x1", - rpcTarget: "https://mainnet.infura.io/v3/79921cf5a1f149f7af0a0fef80cf3363", - ticker: "ETH" - ) - private var loginConfig: W3ALoginConfig = W3ALoginConfig( - verifier: "web3auth-auth0-email-passwordless-sapphire-devnet", - typeOfLogin: TypeOfLogin.jwt, - clientId: "d84f6xvbdV75VTGmHiMWfZLeSPk8M07C" - ) + private var chainConfig: [Chains] = [ + Chains( + chainNamespace: .eip155, + chainId: "0x1", + rpcTarget: "https://mainnet.infura.io/v3/79921cf5a1f149f7af0a0fef80cf3363", + ticker: "ETH" + ) + ] + private var authConnectionConfig: [AuthConnectionConfig] = [ + AuthConnectionConfig( + authConnectionId: "web3auth-auth0-email-passwordless-sapphire-devnet", + authConnection: .CUSTOM, + clientId: "d84f6xvbdV75VTGmHiMWfZLeSPk8M07C" + ) + ] func setup() async throws { guard web3Auth == nil else { return } + var authConfig: [AuthConnectionConfig] = [] + + authConfig.append( + AuthConnectionConfig( + authConnectionId: "w3ads", + authConnection: .GOOGLE, + clientId: "519228911939-snh959gvvmjieoo4j14kkaancbkjp34r.apps.googleusercontent.com", + groupedAuthConnectionId: "aggregate-mobile" + ) + ) + + authConfig.append( + AuthConnectionConfig( + authConnectionId: "auth0-test", + authConnection: .CUSTOM, + clientId: "hUVVf4SEsZT7syOiL0gLU9hFEtm2gQ6O", + groupedAuthConnectionId: "aggregate-mobile" + ) + ) await MainActor.run(body: { isLoading = true navigationTitle = "Loading" }) - web3Auth = try await Web3Auth(.init(clientId: clientID, network: network, buildEnv: buildEnv, redirectUrl: "com.web3auth.sdkapp://auth", + web3Auth = try await Web3Auth(options: .init(clientId: clientID, redirectUrl: "com.web3auth.sdkapp://auth", authBuildEnv: buildEnv, authConnectionConfig: authConfig, defaultChainId: "0x1", web3AuthNetwork: web3AuthNetwork, // sdkUrl: URL(string: "https://auth.mocaverse.xyz"), // walletSdkUrl: URL(string: "https://lrc-mocaverse.web3auth.io"), - // loginConfig: ["loginConfig": loginConfig], - useCoreKitKey: useCoreKit)) + useSFAKey: useCoreKit)) await MainActor.run(body: { - if self.web3Auth?.state != nil { + if self.web3Auth?.web3AuthResponse != nil { handleUserDetails() loggedIn = true } @@ -56,8 +82,8 @@ class ViewModel: ObservableObject { @MainActor func handleUserDetails() { do { loggedIn = true - privateKey = ((web3Auth?.getPrivkey() != "") ? web3Auth?.getPrivkey() : try web3Auth?.getWeb3AuthResponse().factorKey) ?? "" - ed25519PrivKey = web3Auth?.getEd25519PrivKey() ?? "" + privateKey = ((web3Auth?.getPrivateKey() != "") ? web3Auth?.getPrivateKey() : try web3Auth?.getWeb3AuthResponse().factorKey) ?? "" + ed25519PrivKey = web3Auth?.getEd25519PrivateKey() ?? "" userInfo = try web3Auth?.getUserInfo() } catch { errorMessage = error.localizedDescription @@ -65,12 +91,11 @@ class ViewModel: ObservableObject { } } - func login(provider: Web3AuthProvider) { + func login(authConnection: AuthConnection) { Task { do { - _ = try await web3Auth?.login(W3ALoginParams(loginProvider: provider, - extraLoginOptions: ExtraLoginOptions(display: nil, prompt: nil, max_age: nil, ui_locales: nil, id_token_hint: nil, id_token: nil, login_hint: "hello@tor.us", acr_values: nil, scope: nil, audience: nil, connection: nil, domain: nil, client_id: nil, redirect_uri: nil, leeway: nil, verifierIdField: nil, isVerifierIdCaseSensitive: nil, additionalParams: nil), - mfaLevel: .DEFAULT, + _ = try await web3Auth?.login(loginParams: LoginParams(authConnection: authConnection, + mfaLevel: .DEFAULT, extraLoginOptions: ExtraLoginOptions(display: nil, prompt: nil, max_age: nil, ui_locales: nil, id_token_hint: nil, id_token: nil, login_hint: "hello@tor.us", acr_values: nil, scope: nil, audience: nil, connection: nil, domain: nil, client_id: nil, redirect_uri: nil, leeway: nil, userIdField: nil, isUserIdCaseSensitive: nil, additionalParams: nil), curve: .SECP256K1 )) await handleUserDetails() @@ -80,20 +105,37 @@ class ViewModel: ObservableObject { } } - func loginWithGoogle(provider: Web3AuthProvider) { + func loginWithGoogle(authConnection: AuthConnection) { Task { do { - web3Auth = try await Web3Auth(.init( + var authConfig: [AuthConnectionConfig] = [] + + authConfig.append( + AuthConnectionConfig( + authConnectionId: "w3ads", + authConnection: .GOOGLE, + clientId: "519228911939-snh959gvvmjieoo4j14kkaancbkjp34r.apps.googleusercontent.com", + groupedAuthConnectionId: "aggregate-mobile" + ) + ) + + authConfig.append( + AuthConnectionConfig( + authConnectionId: "auth0-test", + authConnection: .CUSTOM, + clientId: "hUVVf4SEsZT7syOiL0gLU9hFEtm2gQ6O", + groupedAuthConnectionId: "aggregate-mobile" + ) + ) + web3Auth = try await Web3Auth(options: .init( clientId: clientID, - network: network, - buildEnv: buildEnv, - redirectUrl: redirectUrl, // we should probably use this throughout instead of being part of other classes - whiteLabel: W3AWhiteLabelData(appName: "Web3Auth Stub", defaultLanguage: .en, mode: .dark, theme: ["primary": "#123456"]), - useCoreKitKey: useCoreKit + redirectUrl: redirectUrl, authBuildEnv: buildEnv, authConnectionConfig: authConfig, web3AuthNetwork: web3AuthNetwork, + useSFAKey: useCoreKit )) - _ = try await web3Auth?.login(W3ALoginParams(loginProvider: provider, - redirectUrl: redirectUrl, - mfaLevel: .DEFAULT, + _ = try await web3Auth?.login(loginParams: LoginParams(authConnection: .GOOGLE, + authConnectionId: "w3ads", + groupedAuthConnectionId: "aggregate-mobile", + mfaLevel: .DEFAULT, extraLoginOptions: ExtraLoginOptions(display: nil, prompt: nil, max_age: nil, ui_locales: nil, id_token_hint: nil, id_token: nil, login_hint: nil, acr_values: nil, scope: nil, audience: nil, connection: nil, domain: "https://web3auth.au.auth0.com/", client_id: nil, redirect_uri: nil, leeway: nil, userIdField: "email", isUserIdCaseSensitive: false, additionalParams: nil), curve: .SECP256K1 )) await handleUserDetails() @@ -106,29 +148,23 @@ class ViewModel: ObservableObject { func loginWithGoogleCustomVerifier() { Task { do { - web3Auth = try await Web3Auth(.init( + web3Auth = try await Web3Auth(options: .init( clientId: clientID, - network: network, - buildEnv: buildEnv, - redirectUrl: redirectUrl, - loginConfig: [ - "random": - .init( - verifier: "w3a-agg-example", - typeOfLogin: .google, - name: "Web3Auth-Aggregate-Verifier-Google-Example", - clientId: "774338308167-q463s7kpvja16l4l0kko3nb925ikds2p.apps.googleusercontent.com", - verifierSubIdentifier: "w3a-google" - ), - ] + redirectUrl: redirectUrl, authBuildEnv: buildEnv, authConnectionConfig: [ + AuthConnectionConfig( + authConnectionId: "w3a-agg-example", + authConnection: .GOOGLE, + name: "Web3Auth-Aggregate-Verifier-Google-Example", + clientId: "774338308167-q463s7kpvja16l4l0kko3nb925ikds2p.apps.googleusercontent.com", + groupedAuthConnectionId: "w3a-google" + ) + ], web3AuthNetwork: web3AuthNetwork ) ) _ = try await web3Auth?.login( - W3ALoginParams( - loginProvider: "random", - dappShare: nil, - extraLoginOptions: ExtraLoginOptions(display: nil, prompt: nil, max_age: nil, ui_locales: nil, id_token_hint: nil, id_token: nil, login_hint: nil, acr_values: nil, scope: nil, audience: nil, connection: nil, domain: nil, client_id: nil, redirect_uri: nil, leeway: nil, verifierIdField: nil, isVerifierIdCaseSensitive: nil, additionalParams: nil), - mfaLevel: .DEFAULT, + loginParams: LoginParams( + authConnection: .GOOGLE, + mfaLevel: .DEFAULT, extraLoginOptions: ExtraLoginOptions(display: nil, prompt: nil, max_age: nil, ui_locales: nil, id_token_hint: nil, id_token: nil, login_hint: nil, acr_values: nil, scope: nil, audience: nil, connection: nil, domain: nil, client_id: nil, redirect_uri: nil, leeway: nil, userIdField: nil, isUserIdCaseSensitive: nil, additionalParams: nil), dappShare: nil, curve: .SECP256K1 )) await handleUserDetails() @@ -153,7 +189,7 @@ class ViewModel: ObservableObject { @MainActor func launchWalletServices() { Task { do { - try await web3Auth?.launchWalletServices(chainConfig: chainConfig) + try await web3Auth?.showWalletUI() } catch { errorMessage = error.localizedDescription showError = true @@ -165,8 +201,8 @@ class ViewModel: ObservableObject { Task { do { /* - web3Auth = try await Web3Auth(W3AInitParams(clientId: clientID, - network: network, + web3Auth = try await Web3Auth(Web3AuthOptions(clientId: clientID, + web3AuthNetwork: web3AuthNetwork, buildEnv: buildEnv, redirectUrl: redirectUrl, whiteLabel: W3AWhiteLabelData(appName: "Web3Auth Stub", defaultLanguage: .en, mode: .dark, theme: ["primary": "#123456"]))) @@ -193,7 +229,7 @@ class ViewModel: ObservableObject { @MainActor func request() { Task { do { - let key = self.web3Auth!.getPrivkey() + let key = self.web3Auth!.getPrivateKey() let pk = try KeyUtil.generatePublicKey(from: Data(hexString: key) ?? Data()) let pkAddress = KeyUtil.generateAddress(from: pk).asString() let checksumAddress = EthereumAddress(pkAddress).toChecksumAddress() @@ -201,9 +237,7 @@ class ViewModel: ObservableObject { params.append("Hello, Web3Auth from Android!") params.append(checksumAddress) params.append("Web3Auth") - let signResponse = try await self.web3Auth?.request(chainConfig: ChainConfig( - chainNamespace: ChainNamespace.eip155, chainId: "0x89", rpcTarget: "https://polygon-rpc.com" - ), method: "personal_sign", requestParams: params) + let signResponse = try await self.web3Auth?.request(method: "personal_sign", requestParams: params) if let response = signResponse { print("Sign response received: \(response)") } else { @@ -220,32 +254,59 @@ class ViewModel: ObservableObject { Task.detached { [unowned self] in do { web3Auth = try await Web3Auth( - W3AInitParams( + options: Web3AuthOptions( clientId: clientID, - network: network, - buildEnv: buildEnv, - redirectUrl: redirectUrl, - whiteLabel: W3AWhiteLabelData(appName: "Web3Auth Stub", defaultLanguage: .en, mode: .dark, theme: ["primary": "#123456"]))) + redirectUrl: redirectUrl, authBuildEnv: buildEnv, authConnectionConfig: [ + AuthConnectionConfig( + authConnectionId: "web3auth-auth0-email-passwordless-sapphire-devnet", + authConnection: .CUSTOM, + clientId: "d84f6xvbdV75VTGmHiMWfZLeSPk8M07C" + ) + ], web3AuthNetwork: web3AuthNetwork + )) _ = try await self.web3Auth? - .login(W3ALoginParams(loginProvider: .GOOGLE)) + .login(loginParams: LoginParams(authConnection: .GOOGLE)) await handleUserDetails() } catch let error { print(error) } } } + + func sfaLogin() { + Task.detached { [unowned self] in + do { + web3Auth = try await Web3Auth( + options: Web3AuthOptions( + clientId: "YOUR_CLIENT_ID", + redirectUrl: redirectUrl, + authBuildEnv: buildEnv, + web3AuthNetwork: .SAPPHIRE_MAINNET + )) + let idToken = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiZGV2bmV0dGVzdHVzZXJAdG9yLnVzIiwiaWF0IjoxNzQ5MDE0Njg5LjQyMzkwMSwiZW1haWwiOiJkZXZuZXR0ZXN0dXNlckB0b3IudXMiLCJpc3MiOiJ0b3J1cy1rZXktdGVzdCIsImFkbWluIjpmYWxzZSwibmFtZSI6ImRldm5ldHRlc3R1c2VyQHRvci51cyIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJhdWQiOiJ0b3J1cy1rZXktdGVzdCIsImV4cCI6MTc0OTAxNDgwOS40MjM5MDEsInN1YiI6ImVtYWlsfGRldm5ldHRlc3R1c2VyIn0.7Rp-tg1tjp93yyT3t9O6p311TvYYL10bKO3bXhsWmQeJVBAnrxVXmQAdE3-CABztFZ4PgvInbWJxrmVq8NR-Og" + let web3AuthResponse = try await self.web3Auth? + .connectTo(loginParams: LoginParams(authConnection: .GOOGLE, + authConnectionId: TEST_VERIFIER, + groupedAuthConnectionId: TEST_AGGREGRATE_VERIFIER, + idToken: idToken)) + print(web3AuthResponse as Any) + } catch let error { + print(error) + } + } + } } extension ViewModel { - func showResult(result: Web3AuthState) { + func showResult(result: Web3AuthResponse) { print(""" Signed in successfully! - Private key: \(result.privKey ?? "") - Ed25519 Private key: \(result.ed25519PrivKey ?? "") + Private key: \(result.privateKey ?? "") + Ed25519 Private key: \(result.ed25519PrivateKey ?? "") User info: Name: \(result.userInfo?.name ?? "") Profile image: \(result.userInfo?.profileImage ?? "N/A") - Type of login: \(result.userInfo?.typeOfLogin ?? "") + AuthConnection: \(result.userInfo?.authConnection ?? "") """) } }