Skip to content

Commit b5a5a84

Browse files
committed
Implement new session API
1 parent 0dd6de9 commit b5a5a84

File tree

51 files changed

+2873
-307
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+2873
-307
lines changed

.github/workflows/ci.yml

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,18 +35,26 @@ jobs:
3535
steps:
3636
- name: Checkout
3737
uses: actions/checkout@v4
38+
- name: Select Xcode versions
39+
uses: maxim-lobanov/setup-xcode@v1
40+
with:
41+
xcode-version: '16.4'
3842
- name: Run unit tests
3943
run: |
4044
cd MIRACLTrust
4145
xcrun simctl erase "iPhone 16"
42-
xcodebuild clean test -scheme "MIRACLTrust-iOS" -destination "platform=iOS Simulator,name=iPhone 16,OS=18.1" -quiet
46+
xcodebuild clean test -scheme "MIRACLTrust-iOS" -destination "platform=iOS Simulator,name=iPhone 16,OS=18.6" -quiet
4347
4448
integration-test:
4549
runs-on: macos-latest
4650
needs: test
4751
steps:
4852
- name: Checkout
4953
uses: actions/checkout@v4
54+
- name: Select Xcode versions
55+
uses: maxim-lobanov/setup-xcode@v1
56+
with:
57+
xcode-version: '16.4'
5058
- name: Run integration tests
5159
env:
5260
TEST_ENVIRONMENT_URL: ${{ vars.TEST_ENVIRONMENT_URL }}
@@ -62,7 +70,7 @@ jobs:
6270
run: |
6371
cd MIRACLTrust
6472
xcrun simctl erase "iPhone 16"
65-
xcodebuild clean test -scheme MIRACLTrust-IntegrationTests -destination "platform=iOS Simulator,name=iPhone 16,OS=18.1" -resultBundlePath reports/MIRACLTrust-IntegrationTests -quiet
73+
xcodebuild clean test -scheme MIRACLTrust-IntegrationTests -destination "platform=iOS Simulator,name=iPhone 16,OS=18.6" -resultBundlePath reports/MIRACLTrust-IntegrationTests -quiet
6674
- name: Upload artifacts
6775
uses: actions/upload-artifact@v4
6876
if: failure()
@@ -76,6 +84,10 @@ jobs:
7684
steps:
7785
- name: Checkout
7886
uses: actions/checkout@v4
87+
- name: Select Xcode versions
88+
uses: maxim-lobanov/setup-xcode@v1
89+
with:
90+
xcode-version: '16.4'
7991
- name: Build SPM package
8092
run: xcodebuild -scheme MIRACLTrust -destination "generic/platform=iOS Simulator" -quiet
8193
- name: Build iOS Simulator framework version

MIRACLTrust/MIRACLTrust-Sources/Authentication Session Management/CodeStatusRequestBody.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ struct CodeStatusRequestBody: Codable {
22
var wid = ""
33
var status = ""
44
var userId: String?
5+
var signature: String?
56
}

MIRACLTrust/MIRACLTrust-Sources/Authentication/AuthenticationError.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ public enum AuthenticationError: Error {
3434

3535
/// Pin code includes invalid symbols or pin length does not match.
3636
case invalidPin
37+
38+
/// Invalid or expired cross-device session.
39+
case invalidCrossDeviceSession
3740
}
3841

3942
extension AuthenticationError: Equatable {
@@ -68,6 +71,8 @@ extension AuthenticationError: LocalizedError {
6871
description = NSLocalizedString("\(AuthenticationError.unsuccessfulAuthentication)", comment: "")
6972
case let .authenticationFail(error):
7073
description = NSLocalizedString("\(AuthenticationError.authenticationFail(error))", comment: "")
74+
case .invalidCrossDeviceSession:
75+
description = NSLocalizedString("\(AuthenticationError.invalidCrossDeviceSession)", comment: "")
7176
}
7277
return description
7378
}
@@ -98,6 +103,8 @@ extension AuthenticationError: CustomNSError {
98103
return 10
99104
case .invalidPin:
100105
return 11
106+
case .invalidCrossDeviceSession:
107+
return 12
101108
}
102109
}
103110

MIRACLTrust/MIRACLTrust-Sources/Authentication/Authenticator.swift

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ let INVALID_AUTHENTICATION_SESSION = "INVALID_AUTHENTICATION_SESSION"
1515
struct Authenticator: Sendable, AuthenticatorBlueprint {
1616
let user: User
1717
let didRequestPinHandler: PinRequestHandler
18-
let accessId: String?
18+
let sessionType: SessionType
1919
let scope: [String]
2020
let miraclAPI: APIBlueprint
2121
let crypto: CryptoBlueprint
@@ -26,7 +26,7 @@ struct Authenticator: Sendable, AuthenticatorBlueprint {
2626
var completionHandler: AuthenticateCompletionHandler
2727

2828
init(user: User,
29-
accessId: String?,
29+
sessionType: SessionType,
3030
crypto: CryptoBlueprint = MIRACLTrust.getInstance().crypto,
3131
deviceName: String = MIRACLTrust.getInstance().deviceName,
3232
api: APIBlueprint = MIRACLTrust.getInstance().miraclAPI,
@@ -36,7 +36,7 @@ struct Authenticator: Sendable, AuthenticatorBlueprint {
3636
didRequestPinHandler: @escaping PinRequestHandler,
3737
completionHandler: @escaping AuthenticateCompletionHandler) throws {
3838
self.user = user
39-
self.accessId = accessId?.trimmingCharacters(in: .whitespacesAndNewlines)
39+
self.sessionType = sessionType
4040
self.didRequestPinHandler = didRequestPinHandler
4141
self.completionHandler = completionHandler
4242
miraclAPI = api
@@ -63,7 +63,7 @@ struct Authenticator: Sendable, AuthenticatorBlueprint {
6363
}
6464

6565
private func updateCodeStatus() {
66-
guard let accessId = accessId else {
66+
guard let sessionIdentifier = sessionType.getSessionIdentifier() else {
6767
return
6868
}
6969

@@ -73,7 +73,7 @@ struct Authenticator: Sendable, AuthenticatorBlueprint {
7373
)
7474

7575
miraclAPI.updateCodeStatus(
76-
accessId: accessId,
76+
accessId: sessionIdentifier,
7777
userId: user.userId,
7878
completionHandler: { _, _, _ in }
7979
)
@@ -191,7 +191,7 @@ struct Authenticator: Sendable, AuthenticatorBlueprint {
191191
func serverPass2(vBytes: Data, pinCode: String) {
192192
miraclAPI.pass2(
193193
for: user.mpinId.hex,
194-
accessId: accessId,
194+
accessId: sessionType.getSessionIdentifier(),
195195
vValue: vBytes.hex
196196
) { apiCallResult, response, error in
197197
if apiCallResult == .failed, let error = error {
@@ -230,7 +230,14 @@ struct Authenticator: Sendable, AuthenticatorBlueprint {
230230
callCompletionHandler(with: AuthenticationError.revoked)
231231
return
232232
case INVALID_AUTH_SESSION, INVALID_AUTHENTICATION_SESSION:
233-
callCompletionHandler(with: AuthenticationError.invalidAuthenticationSession)
233+
switch sessionType {
234+
case .crossDevice:
235+
callCompletionHandler(with: AuthenticationError.invalidCrossDeviceSession)
236+
case .legacy:
237+
callCompletionHandler(with: AuthenticationError.invalidAuthenticationSession)
238+
case .noSession:
239+
callCompletionHandler(with: AuthenticationError.invalidAuthenticationSession)
240+
}
234241
return
235242
case INVALID_AUTH, UNSUCCESSFUL_AUTHENTICATION:
236243
callCompletionHandler(with: AuthenticationError.unsuccessfulAuthentication)
@@ -322,7 +329,7 @@ struct Authenticator: Sendable, AuthenticatorBlueprint {
322329

323330
let updatedAuthenticator = try Authenticator(
324331
user: updatedUser,
325-
accessId: accessId,
332+
sessionType: sessionType,
326333
crypto: crypto,
327334
deviceName: deviceName,
328335
api: miraclAPI,

MIRACLTrust/MIRACLTrust-Sources/Constants.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ public typealias PinRequestHandler = @MainActor @Sendable (@escaping ProcessPinH
55
public typealias RegistrationCompletionHandler = @MainActor @Sendable (User?, Error?) -> Void
66
public typealias AuthenticationCompletionHandler = @MainActor @Sendable (Bool, Error?) -> Void
77
public typealias SigningCompletionHandler = @MainActor @Sendable (SigningResult?, Error?) -> Void
8+
public typealias CrossDeviceSigningCompletionHandler = @MainActor @Sendable (Bool, Error?) -> Void
89
public typealias VerificationCompletionHandler = @MainActor @Sendable (VerificationResponse?, Error?) -> Void
910
public typealias ActivationTokenCompletionHandler = @MainActor @Sendable (ActivationTokenResponse?, Error?) -> Void
1011
public typealias QuickCodeCompletionHandler = @MainActor @Sendable (QuickCode?, Error?) -> Void
@@ -18,3 +19,6 @@ public let MIRACL_API_URL = URL(string: "https://api.mpin.io")!
1819

1920
typealias AuthenticateCompletionHandler = @MainActor @Sendable (AuthenticateResponse?, Error?) -> Void
2021
typealias APIRequestCompletionHandler<T> = @Sendable (APICallResult, T?, Error?) -> Void
22+
23+
public typealias CrossDeviceSessionCompletionHandler = @MainActor @Sendable (CrossDeviceSession?, Error?) -> Void
24+
public typealias CrossDeviceSessionAborterCompletionHandler = @MainActor @Sendable (Bool, Error?) -> Void
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import Foundation
2+
3+
/// An object representing details for an operation (authentication or signing) started on another device.
4+
@objcMembers
5+
@objc public final class CrossDeviceSession: NSObject, Sendable {
6+
/// User ID entered by the user when session is started.
7+
public let userId: String
8+
9+
/// Name of the project in MIRACL Trust platform.
10+
public let projectName: String
11+
12+
/// URL of the project logo.
13+
public let projectLogoURL: String
14+
15+
/// Project ID setting for the application in MIRACL Trust platform.
16+
public let projectId: String
17+
18+
/// PIN length that needs to be entered from the user.
19+
public let pinLength: Int
20+
21+
/// Indicates method of user verification.
22+
public let verificationMethod: VerificationMethod
23+
24+
/// URL for verification in case of custom verification method.
25+
public let verificationURL: String
26+
27+
/// Custom text specified in the MIRACL Trust portal for the custom verification.
28+
public let verificationCustomText: String
29+
30+
/// Label of the identity which will be used for identity verification.
31+
public let identityTypeLabel: String
32+
33+
/// Indicates whether [QuickCode](https://miracl.com/resources/docs/guides/built-in-user-verification/quickcode/) is enabled for the project or not.
34+
public let quickCodeEnabled: Bool
35+
36+
/// Indicates whether registration with QuickCode is allowed for identities registered also with QuickCode.
37+
public let limitQuickCodeRegistration: Bool
38+
39+
/// Identity type which will be used for identity verification.
40+
public let identityType: IdentityType
41+
42+
/// Identifier of the session.
43+
public let sessionId: String
44+
45+
/// Description of the operation that needs to be done.
46+
public let sessionDescription: String
47+
48+
/// Hash of the transaction that needs to be signed if any.
49+
public let signingHash: String
50+
51+
public init(
52+
userId: String,
53+
projectName: String,
54+
projectLogoURL: String,
55+
projectId: String,
56+
pinLength: Int,
57+
verificationMethod: VerificationMethod,
58+
verificationURL: String,
59+
verificationCustomText: String,
60+
identityTypeLabel: String,
61+
quickCodeEnabled: Bool,
62+
limitQuickCodeRegistration: Bool,
63+
identityType: IdentityType,
64+
sessionId: String,
65+
sessionDescription: String,
66+
signingHash: String
67+
) {
68+
self.userId = userId
69+
self.projectName = projectName
70+
self.projectLogoURL = projectLogoURL
71+
self.projectId = projectId
72+
self.pinLength = pinLength
73+
self.verificationMethod = verificationMethod
74+
self.verificationURL = verificationURL
75+
self.verificationCustomText = verificationCustomText
76+
self.identityTypeLabel = identityTypeLabel
77+
self.quickCodeEnabled = quickCodeEnabled
78+
self.limitQuickCodeRegistration = limitQuickCodeRegistration
79+
self.identityType = identityType
80+
self.sessionId = sessionId
81+
self.sessionDescription = sessionDescription
82+
self.signingHash = signingHash
83+
}
84+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import Foundation
2+
3+
struct CrossDeviceSessionAborter: Sendable {
4+
let sessionId: String
5+
let miraclAPI: APIBlueprint
6+
let completionHandler: CrossDeviceSessionAborterCompletionHandler
7+
8+
init(
9+
sessionId: String,
10+
miraclAPI: APIBlueprint = MIRACLTrust.getInstance().miraclAPI,
11+
completionHandler: @escaping CrossDeviceSessionAborterCompletionHandler
12+
) throws {
13+
self.sessionId = sessionId.trimmingCharacters(in: .whitespacesAndNewlines)
14+
self.miraclAPI = miraclAPI
15+
self.completionHandler = completionHandler
16+
17+
try validateInput()
18+
}
19+
20+
func abort() {
21+
miraclAPI.abortSession(accessId: sessionId) { result, _, error in
22+
if let error {
23+
if case let APIError.apiClientError(clientErrorData: clientErrorData, requestId: _, message: _, requestURL: _) = error, let clientErrorData, clientErrorData.code == INVALID_REQUEST_PARAMETERS, let context = clientErrorData.context, context["params"] == "id" {
24+
callCompletionHandler(
25+
isAborted: false,
26+
error: CrossDeviceSessionError.invalidCrossDeviceSession
27+
)
28+
return
29+
}
30+
31+
callCompletionHandler(
32+
isAborted: false,
33+
error: CrossDeviceSessionError.abortCrossDeviceSessionFail(error)
34+
)
35+
return
36+
}
37+
38+
if result == .failed {
39+
callCompletionHandler(
40+
isAborted: false,
41+
error: CrossDeviceSessionError.abortCrossDeviceSessionFail(error)
42+
)
43+
return
44+
}
45+
46+
callCompletionHandler(
47+
isAborted: true,
48+
error: nil
49+
)
50+
}
51+
}
52+
53+
// MARK: Private
54+
55+
private func callCompletionHandler(
56+
isAborted: Bool,
57+
error: Error?
58+
) {
59+
DispatchQueue.main.async {
60+
completionHandler(isAborted, error)
61+
}
62+
}
63+
64+
private func validateInput() throws {
65+
if sessionId.isEmpty {
66+
throw CrossDeviceSessionError.invalidCrossDeviceSession
67+
}
68+
}
69+
}

0 commit comments

Comments
 (0)