Skip to content

Commit b27231b

Browse files
committed
request validator protocol added
1 parent 9119b94 commit b27231b

5 files changed

Lines changed: 261 additions & 21 deletions

File tree

Auth0/Auth0Authentication.swift

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import Foundation
66

77
struct Auth0Authentication: Authentication {
8+
89
let clientId: String
910
let url: URL
1011
var telemetry: Telemetry
@@ -73,6 +74,94 @@ struct Auth0Authentication: Authentication {
7374
dpop: self.dpop)
7475
}
7576

77+
func login(withOTP otp: String, mfaToken: String) -> Request<Credentials, AuthenticationError> {
78+
let url = URL(string: "oauth/token", relativeTo: self.url)!
79+
80+
let payload: [String: Any] = [
81+
"otp": otp,
82+
"mfa_token": mfaToken,
83+
"grant_type": "http://auth0.com/oauth/grant-type/mfa-otp",
84+
"client_id": self.clientId
85+
]
86+
87+
return Request(session: session,
88+
url: url,
89+
method: "POST",
90+
handle: authenticationDecodable,
91+
parameters: payload,
92+
logger: self.logger,
93+
telemetry: self.telemetry,
94+
dpop: self.dpop)
95+
}
96+
97+
func login(withOOBCode oobCode: String, mfaToken: String, bindingCode: String?) -> Request<Credentials, AuthenticationError> {
98+
let url = URL(string: "oauth/token", relativeTo: self.url)!
99+
100+
var payload: [String: Any] = [
101+
"oob_code": oobCode,
102+
"mfa_token": mfaToken,
103+
"grant_type": "http://auth0.com/oauth/grant-type/mfa-oob",
104+
"client_id": self.clientId
105+
]
106+
107+
if let bindingCode = bindingCode {
108+
payload["binding_code"] = bindingCode
109+
}
110+
111+
return Request(session: session,
112+
url: url,
113+
method: "POST",
114+
handle: authenticationDecodable,
115+
parameters: payload,
116+
logger: self.logger,
117+
telemetry: self.telemetry,
118+
dpop: self.dpop)
119+
}
120+
121+
func login(withRecoveryCode recoveryCode: String, mfaToken: String) -> Request<Credentials, AuthenticationError> {
122+
let url = URL(string: "oauth/token", relativeTo: self.url)!
123+
124+
let payload: [String: Any] = [
125+
"recovery_code": recoveryCode,
126+
"mfa_token": mfaToken,
127+
"grant_type": "http://auth0.com/oauth/grant-type/mfa-recovery-code",
128+
"client_id": self.clientId
129+
]
130+
131+
return Request(session: session,
132+
url: url,
133+
method: "POST",
134+
handle: authenticationDecodable,
135+
parameters: payload,
136+
logger: self.logger,
137+
telemetry: self.telemetry,
138+
dpop: self.dpop)
139+
}
140+
141+
func multifactorChallenge(mfaToken: String, types: [String]?, authenticatorId: String?) -> Request<Challenge, AuthenticationError> {
142+
let url = URL(string: "mfa/challenge", relativeTo: self.url)!
143+
var payload: [String: String] = [
144+
"mfa_token": mfaToken,
145+
"client_id": self.clientId
146+
]
147+
148+
if let types = types {
149+
payload["challenge_type"] = types.joined(separator: " ")
150+
}
151+
152+
if let authenticatorId = authenticatorId {
153+
payload["authenticator_id"] = authenticatorId
154+
}
155+
156+
return Request(session: session,
157+
url: url,
158+
method: "POST",
159+
handle: authenticationDecodable,
160+
parameters: payload,
161+
logger: self.logger,
162+
telemetry: self.telemetry)
163+
}
164+
76165
func login(appleAuthorizationCode authorizationCode: String,
77166
fullName: PersonNameComponents?,
78167
profile: [String: Any]?,

Auth0/Authentication.swift

Lines changed: 144 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,140 @@ public protocol Authentication: SenderConstraining, Trackable, Loggable, Sendabl
165165
*/
166166
func login(usernameOrEmail username: String, password: String, realmOrConnection realm: String, audience: String?, scope: String) -> Request<Credentials, AuthenticationError>
167167

168-
/**
168+
/**
169+
Verifies multi-factor authentication (MFA) using a one-time password (OTP).
170+
171+
## Usage
172+
173+
```swift
174+
Auth0
175+
.authentication()
176+
.login(withOTP: "123456", mfaToken: "mfa-token")
177+
.start { result in
178+
switch result {
179+
case .success(let credentials):
180+
print("Obtained credentials: \(credentials)")
181+
case .failure(let error):
182+
print("Failed with: \(error)")
183+
}
184+
}
185+
```
186+
187+
- Parameters:
188+
- otp: One-time password supplied by a MFA authenticator.
189+
- mfaToken: Token returned when authentication fails with an ``AuthenticationError/isMultifactorRequired`` error due to MFA requirement.
190+
- Returns: A request that will yield Auth0 user's credentials.
191+
- Requires: The `http://auth0.com/oauth/grant-type/mfa-otp` grant. Check
192+
[our documentation](https://auth0.com/docs/get-started/applications/application-grant-types) for more information.
193+
194+
## See Also
195+
196+
- [Authentication API Endpoint](https://auth0.com/docs/api/authentication/muti-factor-authentication/verify-mfa-with-otp)
197+
*/
198+
func login(withOTP otp: String, mfaToken: String) -> Request<Credentials, AuthenticationError>
199+
200+
/// Verifies multi-factor authentication (MFA) using an out-of-band (OOB) challenge (either push notification, SMS
201+
/// or voice).
202+
///
203+
/// ## Usage
204+
///
205+
/// ```swift
206+
/// Auth0
207+
/// .authentication()
208+
/// .login(withOOBCode: "123456", mfaToken: "mfa-token")
209+
/// .start { result in
210+
/// switch result {
211+
/// case .success(let credentials):
212+
/// print("Obtained credentials: \(credentials)")
213+
/// case .failure(let error):
214+
/// print("Failed with: \(error)")
215+
/// }
216+
/// }
217+
/// ```
218+
///
219+
/// - Parameters:
220+
/// - oobCode: The OOB code received from the challenge request.
221+
/// - mfaToken: Token returned when authentication fails with an ``AuthenticationError/isMultifactorRequired`` error due to MFA requirement.
222+
/// - bindingCode: A code used to bind the side channel (used to deliver the challenge) with the main channel you are using to authenticate. This is usually an OTP-like code delivered as part of the challenge message.
223+
/// - Returns: A request that will yield Auth0 user's credentials.
224+
/// - Requires: The `http://auth0.com/oauth/grant-type/mfa-oob` grant. Check
225+
/// [our documentation](https://auth0.com/docs/get-started/applications/application-grant-types) for more information.
226+
///
227+
/// ## See Also
228+
///
229+
/// - [Authentication API Endpoint](https://auth0.com/docs/api/authentication/muti-factor-authentication/verify-with-out-of-band)
230+
func login(withOOBCode oobCode: String, mfaToken: String, bindingCode: String?) -> Request<Credentials, AuthenticationError>
231+
232+
/// Verifies multi-factor authentication (MFA) using a recovery code.
233+
/// Some multi-factor authentication (MFA) providers support using a recovery code to login. Use this method to
234+
/// authenticate when the user's enrolled device is unavailable, or the user cannot receive the challenge or accept
235+
/// it due to connectivity issues.
236+
///
237+
/// ## Usage
238+
///
239+
/// ```swift
240+
/// Auth0
241+
/// .authentication()
242+
/// .login(withRecoveryCode: "recovery-code", mfaToken: "mfa-token")
243+
/// .start { result in
244+
/// switch result {
245+
/// case .success(let credentials):
246+
/// print("Obtained credentials: \(credentials)")
247+
/// case .failure(let error):
248+
/// print("Failed with: \(error)")
249+
/// }
250+
/// }
251+
/// ```
252+
///
253+
/// - Parameters:
254+
/// - recoveryCode: Recovery code provided by the user.
255+
/// - mfaToken: Token returned when authentication fails with an ``AuthenticationError/isMultifactorRequired`` error due to MFA requirement.
256+
/// - Returns: A request that will yield Auth0 user's credentials. Might include a **recovery code**, which the
257+
/// application must display to the user to be stored securely for future use.
258+
/// - Requires: The `http://auth0.com/oauth/grant-type/mfa-recovery-code` grant. Check
259+
/// [our documentation](https://auth0.com/docs/get-started/applications/application-grant-types) for more information.
260+
///
261+
/// ## See Also
262+
///
263+
/// - ``Credentials/recoveryCode``
264+
/// - [Authentication API Endpoint](https://auth0.com/docs/api/authentication/muti-factor-authentication/verify-with-recovery-code)
265+
func login(withRecoveryCode recoveryCode: String, mfaToken: String) -> Request<Credentials, AuthenticationError>
266+
267+
/// Requests a challenge for multi-factor authentication (MFA) based on the challenge types supported by the
268+
/// application and user.
269+
///
270+
/// ## Usage
271+
///
272+
/// ```swift
273+
/// Auth0
274+
/// .authentication()
275+
/// .multifactorChallenge(mfaToken: "mfa-token", types: ["otp"])
276+
/// .start { result in
277+
/// switch result {
278+
/// case .success(let challenge):
279+
/// print("Obtained challenge: \(challenge)")
280+
/// case .failure(let error):
281+
/// print("Failed with: \(error)")
282+
/// }
283+
/// }
284+
/// ```
285+
///
286+
/// The challenge type is how the user will get the challenge and prove possession. Supported challenge types include:
287+
/// * `otp`: for one-time password (OTP)
288+
/// * `oob`: for SMS/voice messages or out-of-band (OOB)
289+
///
290+
/// - Parameters:
291+
/// - mfaToken: Token returned when authentication fails with an ``AuthenticationError/isMultifactorRequired`` error due to MFA requirement.
292+
/// - types: A list of the challenges types accepted by your application. Accepted challenge types are `oob` or `otp`. Excluding this parameter means that your application accepts all supported challenge types.
293+
/// - authenticatorId: The ID of the authenticator to challenge. You can get the ID by querying the list of available authenticators for the user.
294+
/// - Returns: A request that will yield a multi-factor challenge.
295+
///
296+
/// ## See Also
297+
///
298+
/// - [Authentication API Endpoint](https://auth0.com/docs/api/authentication/muti-factor-authentication/request-mfa-challenge)
299+
func multifactorChallenge(mfaToken: String, types: [String]?, authenticatorId: String?) -> Request<Challenge, AuthenticationError>
300+
301+
/**
169302
Logs a user in with their Sign In with Apple authorization code.
170303

171304
## Usage
@@ -200,7 +333,7 @@ public protocol Authentication: SenderConstraining, Trackable, Loggable, Sendabl
200333
- authorizationCode: Authorization Code retrieved from Apple Authorization.
201334
- fullName: The full name property returned with the Apple ID Credentials.
202335
- profile: Additional user profile data returned with the Apple ID Credentials.
203-
- audience: API Identifier that your application is requesting access to.
336+
- audience: API Identifier that your application is requesting access to.
204337
- scope: Space-separated list of requested scope values. Defaults to `openid profile email`.
205338
- Returns: A request that will yield Auth0 user's credentials.
206339

@@ -542,7 +675,7 @@ public protocol Authentication: SenderConstraining, Trackable, Loggable, Sendabl
542675
/// - Returns: A request that will yield Auth0 user's credentials.
543676
///
544677
/// ## See Also
545-
///
678+
///
546679
/// - [Authentication API Endpoint](https://auth0.com/docs/native-passkeys-api#authenticate-new-user)
547680
/// - [Native Passkeys for Mobile Applications](https://auth0.com/docs/native-passkeys-for-mobile-applications)
548681
/// - [Supporting passkeys](https://developer.apple.com/documentation/authenticationservices/supporting-passkeys#Register-a-new-account-on-a-service)
@@ -1035,6 +1168,14 @@ public extension Authentication {
10351168
return self.login(usernameOrEmail: username, password: password, realmOrConnection: realm, audience: audience, scope: scope)
10361169
}
10371170

1171+
func login(withOOBCode oobCode: String, mfaToken: String, bindingCode: String? = nil) -> Request<Credentials, AuthenticationError> {
1172+
return self.login(withOOBCode: oobCode, mfaToken: mfaToken, bindingCode: bindingCode)
1173+
}
1174+
1175+
func multifactorChallenge(mfaToken: String, types: [String]? = nil, authenticatorId: String? = nil) -> Request<Challenge, AuthenticationError> {
1176+
return self.multifactorChallenge(mfaToken: mfaToken, types: types, authenticatorId: authenticatorId)
1177+
}
1178+
10381179
func login(appleAuthorizationCode authorizationCode: String,
10391180
fullName: PersonNameComponents? = nil,
10401181
profile: [String: Any]? = nil,

Auth0/AuthenticationHandlers.swift

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -34,24 +34,6 @@ func authenticationObject<T: JSONObjectPayload>(from result: Result<ResponseValu
3434
}
3535
}
3636

37-
func listAuthenticatorsResponseMapper(factorsAllowed: [String], result: Result<ResponseValue, AuthenticationError>, callback: Request<[Authenticator], AuthenticationError>.Callback) {
38-
do {
39-
let response = try result.get()
40-
if let data = response.data {
41-
let decoder = JSONDecoder()
42-
let authenticators = try decoder.decode([Authenticator].self, from: data)
43-
let filteredAuthenticators = authenticators.filter { authenticator in factorsAllowed.isEmpty || factorsAllowed.contains { $0 == authenticator.type } }
44-
callback(.success(filteredAuthenticators))
45-
} else {
46-
callback(.failure(AuthenticationError(from: response)))
47-
}
48-
} catch let error as AuthenticationError {
49-
callback(.failure(error))
50-
} catch {
51-
callback(.failure(AuthenticationError(cause: error)))
52-
}
53-
}
54-
5537
func authenticationDatabaseUser(from result: Result<ResponseValue, AuthenticationError>,
5638
callback: Request<DatabaseUser, AuthenticationError>.Callback) {
5739
do {

Auth0/Request.swift

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public struct Request<T, E: Auth0APIError>: Requestable {
2727
let session: URLSession
2828
let url: URL
2929
let method: String
30+
let requestValidator: [RequestValidator]
3031
let handle: (Result<ResponseValue, E>, Callback) -> Void
3132
let parameters: [String: Any]
3233
let headers: [String: String]
@@ -37,6 +38,7 @@ public struct Request<T, E: Auth0APIError>: Requestable {
3738
init(session: URLSession,
3839
url: URL,
3940
method: String,
41+
requestValidator: [RequestValidator] = [],
4042
handle: @escaping (Result<ResponseValue, E>, Callback) -> Void,
4143
parameters: [String: Any] = [:],
4244
headers: [String: String] = [:],
@@ -47,6 +49,7 @@ public struct Request<T, E: Auth0APIError>: Requestable {
4749
self.url = url
4850
self.method = method
4951
self.handle = handle
52+
self.requestValidator = requestValidator
5053
self.parameters = parameters
5154
self.logger = logger
5255
self.telemetry = telemetry
@@ -91,6 +94,13 @@ public struct Request<T, E: Auth0APIError>: Requestable {
9194

9295
private func startDataTask(retryCount: Int, request: URLRequest, callback: @escaping Callback) {
9396
var request = request
97+
98+
do {
99+
try runClientValidation()
100+
} catch {
101+
handle(.failure(E(cause: error)), callback)
102+
return
103+
}
94104
do {
95105
if let dpop = dpop, try dpop.shouldGenerateProof(for: url, parameters: parameters) {
96106
let proof = try dpop.generateProof(for: request as URLRequest)
@@ -127,6 +137,12 @@ public struct Request<T, E: Auth0APIError>: Requestable {
127137
task.resume()
128138
}
129139

140+
func runClientValidation() throws {
141+
for validator in requestValidator {
142+
try validator.validate()
143+
}
144+
}
145+
130146
// MARK: - Request Modifiers
131147

132148
/**
@@ -168,6 +184,17 @@ public struct Request<T, E: Auth0APIError>: Requestable {
168184
telemetry: self.telemetry,
169185
dpop: self.dpop)
170186
}
187+
188+
public func requestValidators(_ extraValidators: [RequestValidator]) -> Self {
189+
var requestValidator = extraValidators
190+
requestValidator.append(contentsOf: self.requestValidator)
191+
return Request(session: session,
192+
url: url,
193+
method: method,
194+
handle: handle,
195+
logger: logger,
196+
telemetry: telemetry)
197+
}
171198
}
172199

173200
// MARK: - Combine

Auth0/Response.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ struct Response<E: Auth0APIError> {
2626
// Not using the custom initializer because data could be empty
2727
throw E(description: nil, statusCode: response.statusCode)
2828
}
29+
2930
return ResponseValue(value: response, data: data)
3031
}
3132

0 commit comments

Comments
 (0)