From 9c4c15d5d00729072d3c02b853bd00e233bffd9a Mon Sep 17 00:00:00 2001 From: Radoslav Penev Date: Wed, 3 Sep 2025 16:37:57 +0300 Subject: [PATCH 1/2] Make cross device session optional for verification --- .../MIRACLTrust-Sources/MIRACLTrust.swift | 10 ++- .../Swift/VerificationIntegrationTests.swift | 74 ++++++++++++++++++- .../Helpers/Swift/VerificationTestCase.swift | 26 +++++++ .../MIRACLTrustTests/MIRACLTrustTests.swift | 67 ++++++++++++++++- 4 files changed, 173 insertions(+), 4 deletions(-) diff --git a/MIRACLTrust/MIRACLTrust-Sources/MIRACLTrust.swift b/MIRACLTrust/MIRACLTrust-Sources/MIRACLTrust.swift index cf6081a..bb3b56f 100644 --- a/MIRACLTrust/MIRACLTrust-Sources/MIRACLTrust.swift +++ b/MIRACLTrust/MIRACLTrust-Sources/MIRACLTrust.swift @@ -174,15 +174,21 @@ import Foundation /// - completionHandler: a closure called when the verification has been completed. It can contain a verification response object or an optional error object. @objc public func _sendVerificationEmail( userId: String, - crossDeviceSession: CrossDeviceSession, + crossDeviceSession: CrossDeviceSession? = nil, completionHandler: @escaping VerificationCompletionHandler ) { do { + var sessionType = SessionType.noSession + + if let crossDeviceSession { + sessionType = .crossDevice(sessionId: crossDeviceSession.sessionId) + } + let verificator = try Verificator( userId: userId, projectId: projectId, deviceName: deviceName, - sessionType: .crossDevice(sessionId: crossDeviceSession.sessionId), + sessionType: sessionType, miraclAPI: miraclAPI, completionHandler: completionHandler ) diff --git a/MIRACLTrust/MIRACLTrustIntegrationTests/Cases/Swift/VerificationIntegrationTests.swift b/MIRACLTrust/MIRACLTrustIntegrationTests/Cases/Swift/VerificationIntegrationTests.swift index 0452786..e7eb029 100644 --- a/MIRACLTrust/MIRACLTrustIntegrationTests/Cases/Swift/VerificationIntegrationTests.swift +++ b/MIRACLTrust/MIRACLTrustIntegrationTests/Cases/Swift/VerificationIntegrationTests.swift @@ -19,6 +19,7 @@ class VerificationIntegrationTests: XCTestCase { let registrationTestCase = RegistrationTestCase() let deviceName = "iOS Simulator" let sessionDetailsTestCase = SessionDetailsTestCase() + let crossDeviceSessionTestCase = CrossDeviceSessionCase() let api = PlatformAPIWrapper() let gmailService = GmailServiceTestWrapper() @@ -29,7 +30,7 @@ class VerificationIntegrationTests: XCTestCase { var configuration: Configuration? - func testVerification() async throws { + func testVerificationWithoutAuthenticationSession() async throws { let extendedMailAddress = "int+\(UUID().uuidString)@miracl.com" configuration = try Configuration @@ -67,6 +68,44 @@ class VerificationIntegrationTests: XCTestCase { XCTAssertEqual(activationTokenResponse?.projectId, projectId) } + func testVerificationWithoutCrossDeviceSession() async throws { + let extendedMailAddress = "int+\(UUID().uuidString)@miracl.com" + + configuration = try Configuration + .Builder( + projectId: projectId, + projectURL: projectURLDV + ).userStorage(userStorage: storage) + .build() + try MIRACLTrust.configure(with: XCTUnwrap(configuration)) + + let timestamp = Date() + let (verified, error) = verificationTestCase.sendVerificationEmailForCrossDeviceSession( + userId: extendedMailAddress + ) + + XCTAssertNotNil(verified) + XCTAssertNil(error) + + let verificationResult = try await gmailService.getVerificationURL(receiver: extendedMailAddress, timestamp: timestamp) + let verificationURL = try XCTUnwrap(verificationResult) + + let queryItems = try XCTUnwrap(URLComponents(url: verificationURL, resolvingAgainstBaseURL: false)?.queryItems) + + let userIdItem = try XCTUnwrap(queryItems.filter { item in + item.name == "user_id" + }.first) + XCTAssertEqual(userIdItem.value, extendedMailAddress) + + let (activationTokenResponse, activationTokenError) = try activationTokenTestCase.getActivationToken( + verificationURL: XCTUnwrap(verificationURL) + ) + + XCTAssertNil(activationTokenError) + XCTAssertNotNil(activationTokenResponse) + XCTAssertEqual(activationTokenResponse?.projectId, projectId) + } + func testVerificationWithMpinId() async throws { let extendedMailAddress = "int+\(UUID().uuidString)@miracl.com" @@ -197,6 +236,38 @@ class VerificationIntegrationTests: XCTestCase { XCTAssertEqual(response.accessId, accessId) } + func testVerificationWithCrossDeviceSessionDetails() async throws { + let extendedMailAddress = "int+\(UUID().uuidString)@miracl.com" + let accessId = try XCTUnwrap(api.getAccessId(projectId: projectId, projectURL: projectURLDV)) + let qrCode = "https://mcl.mpin.io#\(accessId)" + + configuration = try Configuration + .Builder(projectId: projectId, projectURL: projectURLDV) + .userStorage(userStorage: storage) + .build() + try MIRACLTrust.configure(with: XCTUnwrap(configuration)) + + let crossDeviceSession = try await crossDeviceSessionTestCase.getCrossDeviceSessionForQRCode(qrCode: qrCode) + + let timestamp = Date() + let (verified, error) = verificationTestCase.sendVerificationEmailForCrossDeviceSession( + userId: extendedMailAddress, + crossDeviceSession: crossDeviceSession + ) + XCTAssertNotNil(verified) + XCTAssertNil(error) + + let verificationURL = try await gmailService.getVerificationURL(receiver: extendedMailAddress, timestamp: timestamp) + + let (activationTokenResponse, activationTokenError) = try activationTokenTestCase.getActivationToken( + verificationURL: XCTUnwrap(verificationURL) + ) + + XCTAssertNil(activationTokenError) + let response = try XCTUnwrap(activationTokenResponse) + XCTAssertEqual(response.accessId, accessId) + } + func testEmailCodeVerification() async throws { let extendedMailAddress = "int+\(UUID().uuidString)@miracl.com" @@ -204,6 +275,7 @@ class VerificationIntegrationTests: XCTestCase { .Builder(projectId: projectIdECV, projectURL: projectURLECV) .userStorage(userStorage: storage) .build() + try MIRACLTrust.configure(with: XCTUnwrap(configuration)) let timestamp = Date() diff --git a/MIRACLTrust/MIRACLTrustIntegrationTests/Helpers/Swift/VerificationTestCase.swift b/MIRACLTrust/MIRACLTrustIntegrationTests/Helpers/Swift/VerificationTestCase.swift index ae335a3..37d3d2a 100644 --- a/MIRACLTrust/MIRACLTrustIntegrationTests/Helpers/Swift/VerificationTestCase.swift +++ b/MIRACLTrust/MIRACLTrustIntegrationTests/Helpers/Swift/VerificationTestCase.swift @@ -27,4 +27,30 @@ class VerificationTestCase { return (verificationResponse, returnedError) } + + func sendVerificationEmailForCrossDeviceSession( + userId: String, + crossDeviceSession: CrossDeviceSession? = nil + ) -> (VerificationResponse?, Error?) { + let waitForVerification = XCTestExpectation(description: "wait for verification") + + nonisolated(unsafe) var verificationResponse: VerificationResponse? + nonisolated(unsafe) var returnedError: Error? + + MIRACLTrust.getInstance()._sendVerificationEmail( + userId: userId, + crossDeviceSession: crossDeviceSession + ) { result, error in + verificationResponse = result + returnedError = error + waitForVerification.fulfill() + } + + let waitResult = XCTWaiter.wait(for: [waitForVerification], timeout: operationTimeout) + if waitResult != .completed { + XCTFail("Failed expectation") + } + + return (verificationResponse, returnedError) + } } diff --git a/MIRACLTrust/MIRACLTrustTests/MIRACLTrustTests.swift b/MIRACLTrust/MIRACLTrustTests/MIRACLTrustTests.swift index d4eba4d..7a7bbfe 100644 --- a/MIRACLTrust/MIRACLTrustTests/MIRACLTrustTests.swift +++ b/MIRACLTrust/MIRACLTrustTests/MIRACLTrustTests.swift @@ -118,6 +118,10 @@ class MIRACLTrustTests: XCTestCase { mockAPI.signingSessionCompleterError = nil mockAPI.signingSessionCompleterResultCall = .success mockAPI.signingSessionCompleterResponse = SigningSessionCompleterResponse(status: "signed") + + mockAPI.verificationResponse = VerificationRequestResponse(backoff: backoff, method: "link") + mockAPI.verificationResultCall = .success + mockAPI.verificationError = nil } func createMockCrypto() { @@ -174,6 +178,47 @@ class MIRACLTrustTests: XCTestCase { wait(for: [expectation], timeout: 20.0) } + func testSendVerificationEmailWithCrossDeviceSession() throws { + let completionHandlerExpectation = XCTestExpectation(description: "sendVerificationEmail with cross device session") + let crossDeviceSession = createCrossDeviceSession() + + MIRACLTrust.getInstance()._sendVerificationEmail(userId: randomString, crossDeviceSession: crossDeviceSession) { response, error in + XCTAssertNil(error) + XCTAssertNotNil(response) + + completionHandlerExpectation.fulfill() + } + + wait(for: [completionHandlerExpectation], timeout: 20.0) + } + + func testSendVerificationEmailWithAuthenticationSessionDetails() throws { + let completionHandlerExpectation = XCTestExpectation(description: "sendVerificationEmail with authentication session") + let authenticationSessionDetails = createSessionDetails() + + MIRACLTrust.getInstance().sendVerificationEmail(userId: randomString, authenticationSessionDetails: authenticationSessionDetails) { response, error in + XCTAssertNil(error) + XCTAssertNotNil(response) + + completionHandlerExpectation.fulfill() + } + + wait(for: [completionHandlerExpectation], timeout: 20.0) + } + + func testSendVerificationEmailWithoutCrossDeviceSession() throws { + let completionHandlerExpectation = XCTestExpectation(description: "sendVerificationEmail without cross device session") + + MIRACLTrust.getInstance()._sendVerificationEmail(userId: randomString) { response, error in + XCTAssertNil(error) + XCTAssertNotNil(response) + + completionHandlerExpectation.fulfill() + } + + wait(for: [completionHandlerExpectation], timeout: 20.0) + } + func testGetActivationToken() throws { let userId = "alice@miracl.com" let verificationURL = try XCTUnwrap(URL(string: "https://api.mpin.io/verification/confirmation?code=af1cc549573718409de44d8bf2e67a06&user_id=\(userId)")) @@ -977,7 +1022,7 @@ class MIRACLTrustTests: XCTestCase { func testGetNotExisitingUser() throws { projectId = randomString - var userDTO = createUserDTO() + let userDTO = createUserDTO() try mockUserStorage.add(user: userDTO) XCTAssertNil(MIRACLTrust.getInstance().getUser(by: randomString)) @@ -1068,4 +1113,24 @@ class MIRACLTrustTests: XCTestCase { expireTime: Date() ) } + + private func createCrossDeviceSession() -> CrossDeviceSession { + CrossDeviceSession( + userId: UUID().uuidString, + projectName: UUID().uuidString, + projectLogoURL: UUID().uuidString, + projectId: UUID().uuidString, + pinLength: 4, + verificationMethod: .standardEmail, + verificationURL: UUID().uuidString, + verificationCustomText: UUID().uuidString, + identityTypeLabel: UUID().uuidString, + quickCodeEnabled: true, + limitQuickCodeRegistration: false, + identityType: .alphanumeric, + sessionId: UUID().uuidString, + sessionDescription: "", + signingHash: "" + ) + } } From f78cb64c39afdec1f10b17e858b9c02e6aaa1ad0 Mon Sep 17 00:00:00 2001 From: Radoslav Penev Date: Thu, 4 Sep 2025 14:51:04 +0300 Subject: [PATCH 2/2] Bump version to 1.6.1 --- MIRACLTrust.podspec | 2 +- .../MIRACLTrust.xcodeproj/project.pbxproj | 20 +++++++++---------- README.md | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/MIRACLTrust.podspec b/MIRACLTrust.podspec index 97dfc6c..0b2c8d9 100644 --- a/MIRACLTrust.podspec +++ b/MIRACLTrust.podspec @@ -5,7 +5,7 @@ Pod::Spec.new do |s| s.name = "MIRACLTrust" s.summary = "MIRACL Trust SDK for iOS" s.requires_arc = true - s.version = "1.6.0" + s.version = "1.6.1" s.license = { :type => "Apache2", :file => "LICENSE" } s.author = { "MIRACL" => "operations@miracl.com" } s.homepage = "https://github.com/miracl/trust-sdk-ios" diff --git a/MIRACLTrust/MIRACLTrust.xcodeproj/project.pbxproj b/MIRACLTrust/MIRACLTrust.xcodeproj/project.pbxproj index 9ee7d01..a039431 100644 --- a/MIRACLTrust/MIRACLTrust.xcodeproj/project.pbxproj +++ b/MIRACLTrust/MIRACLTrust.xcodeproj/project.pbxproj @@ -1783,7 +1783,7 @@ GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 13.2; MARKETING_VERSION = "$(MIRACL_SDK_VERSION)"; - MIRACL_SDK_VERSION = 1.6.0; + MIRACL_SDK_VERSION = 1.6.1; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -1846,7 +1846,7 @@ GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 13.2; MARKETING_VERSION = "$(MIRACL_SDK_VERSION)"; - MIRACL_SDK_VERSION = 1.6.0; + MIRACL_SDK_VERSION = 1.6.1; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -1870,7 +1870,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MIRACL_SDK_VERSION = 1.6.0; + MIRACL_SDK_VERSION = 1.6.1; PRODUCT_BUNDLE_IDENTIFIER = "com.miracl.trust.sdk-ios.MIRACLTrustTests"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; @@ -1890,7 +1890,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MIRACL_SDK_VERSION = 1.6.0; + MIRACL_SDK_VERSION = 1.6.1; PRODUCT_BUNDLE_IDENTIFIER = "com.miracl.trust.sdk-ios.MIRACLTrustTests"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; @@ -1912,7 +1912,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MIRACL_SDK_VERSION = 1.6.0; + MIRACL_SDK_VERSION = 1.6.1; PRODUCT_BUNDLE_IDENTIFIER = com.radoslavpenev.MIRACLTrustIntegrationTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "MIRACLTrustIntegrationTests/Cases/MIRACLTrustIntegrationTests-Bridging-Header.h"; @@ -1937,7 +1937,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MIRACL_SDK_VERSION = 1.6.0; + MIRACL_SDK_VERSION = 1.6.1; PRODUCT_BUNDLE_IDENTIFIER = com.radoslavpenev.MIRACLTrustIntegrationTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "MIRACLTrustIntegrationTests/Cases/MIRACLTrustIntegrationTests-Bridging-Header.h"; @@ -1960,7 +1960,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MIRACL_SDK_VERSION = 1.6.0; + MIRACL_SDK_VERSION = 1.6.1; PRODUCT_BUNDLE_IDENTIFIER = "com.miracl.applclippoc.MIRACLTrust-Test-Host-App"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; @@ -1980,7 +1980,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MIRACL_SDK_VERSION = 1.6.0; + MIRACL_SDK_VERSION = 1.6.1; ONLY_ACTIVE_ARCH = NO; PRODUCT_BUNDLE_IDENTIFIER = "com.miracl.applclippoc.MIRACLTrust-Test-Host-App"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -2019,7 +2019,7 @@ "$(PROJECT_DIR)/MIRACLTrust-Sources/Crypto/src", ); MARKETING_VERSION = "$(MIRACL_SDK_VERSION)"; - MIRACL_SDK_VERSION = 1.6.0; + MIRACL_SDK_VERSION = 1.6.1; MODULEMAP_FILE = "$(SRCROOT)/MIRACLTrust-Sources/MIRACLTrust.modulemap"; ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = ( @@ -2068,7 +2068,7 @@ "$(PROJECT_DIR)/MIRACLTrust-Sources/Crypto/src", ); MARKETING_VERSION = "$(MIRACL_SDK_VERSION)"; - MIRACL_SDK_VERSION = 1.6.0; + MIRACL_SDK_VERSION = 1.6.1; MODULEMAP_FILE = "$(SRCROOT)/MIRACLTrust-Sources/MIRACLTrust.modulemap"; ONLY_ACTIVE_ARCH = NO; OTHER_CFLAGS = ( diff --git a/README.md b/README.md index db2c555..c869536 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ To integrate using Apple's Swift package manager, without Xcode integration, add the following as a dependency to your Package.swift: ```bash -.package(url: "https://github.com/miracl/trust-sdk-ios", .upToNextMajor(from: "1.6.0")) +.package(url: "https://github.com/miracl/trust-sdk-ios", .upToNextMajor(from: "1.6.1")) ``` In both cases after the package is downloaded, go to the