MultiFactor.swift 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. // Copyright 2023 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. import Foundation
  15. #if os(iOS)
  16. /** @class FIRMultiFactor
  17. @brief The interface defining the multi factor related properties and operations pertaining to a
  18. user.
  19. This class is available on iOS only.
  20. */
  21. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  22. @objc(FIRMultiFactor) public class MultiFactor: NSObject, NSSecureCoding {
  23. @objc public var enrolledFactors: [MultiFactorInfo]?
  24. /** @fn getSessionWithCompletion:
  25. @brief Get a session for a second factor enrollment operation.
  26. @param completion A block with the session identifier for a second factor enrollment operation.
  27. This is used to identify the current user trying to enroll a second factor.
  28. */
  29. @objc(getSessionWithCompletion:)
  30. public func getSessionWithCompletion(_ completion: ((MultiFactorSession?, Error?) -> Void)?) {
  31. let session = MultiFactorSession.sessionForCurrentUser
  32. if let completion {
  33. completion(session, nil)
  34. }
  35. }
  36. /** @fn getSessionWithCompletion:
  37. @brief Get a session for a second factor enrollment operation.
  38. @param completion A block with the session identifier for a second factor enrollment operation.
  39. This is used to identify the current user trying to enroll a second factor.
  40. */
  41. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  42. public func session() async throws -> MultiFactorSession {
  43. return try await withCheckedThrowingContinuation { continuation in
  44. self.getSessionWithCompletion { session, error in
  45. if let session {
  46. continuation.resume(returning: session)
  47. } else {
  48. continuation.resume(throwing: error!)
  49. }
  50. }
  51. }
  52. }
  53. /** @fn enrollWithAssertion:displayName:completion:
  54. @brief Enrolls a second factor as identified by the `MultiFactorAssertion` parameter for the
  55. current user.
  56. @param displayName An optional display name associated with the multi factor to enroll.
  57. @param completion The block invoked when the request is complete, or fails.
  58. */
  59. @objc(enrollWithAssertion:displayName:completion:)
  60. public func enroll(with assertion: MultiFactorAssertion,
  61. displayName: String?,
  62. completion: ((Error?) -> Void)?) {
  63. let phoneAssertion = assertion as? PhoneMultiFactorAssertion
  64. guard let credential = phoneAssertion?.authCredential else {
  65. fatalError("Internal Error: Missing credential")
  66. }
  67. switch credential.credentialKind {
  68. case .phoneNumber: fatalError("Internal Error: Missing verificationCode")
  69. case let .verification(verificationID, code):
  70. let finalizeMFAPhoneRequestInfo =
  71. AuthProtoFinalizeMFAPhoneRequestInfo(sessionInfo: verificationID, verificationCode: code)
  72. guard let user = user else {
  73. fatalError("Internal Auth error: failed to get user enrolling in MultiFactor")
  74. }
  75. let request = FinalizeMFAEnrollmentRequest(
  76. idToken: self.user?.rawAccessToken(),
  77. displayName: displayName,
  78. verificationInfo: finalizeMFAPhoneRequestInfo,
  79. requestConfiguration: user.requestConfiguration
  80. )
  81. AuthBackend.post(with: request) { rawResponse, error in
  82. if let error {
  83. if let completion {
  84. completion(error)
  85. }
  86. } else if let response = rawResponse {
  87. user.auth?.completeSignIn(withAccessToken: response.idToken,
  88. accessTokenExpirationDate: nil,
  89. refreshToken: response.refreshToken,
  90. anonymous: false) { _, error in
  91. if error != nil {
  92. try? user.auth?.signOut()
  93. }
  94. if let completion {
  95. completion(error)
  96. }
  97. }
  98. }
  99. }
  100. }
  101. }
  102. /** @fn enrollWithAssertion:displayName:completion:
  103. @brief Enrolls a second factor as identified by the `MultiFactorAssertion` parameter for the
  104. current user.
  105. @param displayName An optional display name associated with the multi factor to enroll.
  106. @param completion The block invoked when the request is complete, or fails.
  107. */
  108. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  109. public func enroll(with assertion: MultiFactorAssertion, displayName: String?) async throws {
  110. return try await withCheckedThrowingContinuation { continuation in
  111. self.enroll(with: assertion, displayName: displayName) { error in
  112. if let error {
  113. continuation.resume(throwing: error)
  114. } else {
  115. continuation.resume()
  116. }
  117. }
  118. }
  119. }
  120. /** @fn unenrollWithInfo:completion:
  121. @brief Unenroll the given multi factor.
  122. @param completion The block invoked when the request to send the verification email is complete,
  123. or fails.
  124. */
  125. @objc(unenrollWithInfo:completion:)
  126. public func unenroll(with factorInfo: MultiFactorInfo,
  127. completion: ((Error?) -> Void)?) {
  128. unenroll(withFactorUID: factorInfo.uid, completion: completion)
  129. }
  130. /** @fn unenrollWithInfo:completion:
  131. @brief Unenroll the given multi factor.
  132. @param completion The block invoked when the request to send the verification email is complete,
  133. or fails.
  134. */
  135. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  136. public func unenroll(with factorInfo: MultiFactorInfo) async throws {
  137. try await unenroll(withFactorUID: factorInfo.uid)
  138. }
  139. /** @fn unenrollWithFactorUID:completion:
  140. @brief Unenroll the given multi factor.
  141. @param completion The block invoked when the request to send the verification email is complete,
  142. or fails.
  143. */
  144. @objc(unenrollWithFactorUID:completion:)
  145. public func unenroll(withFactorUID factorUID: String,
  146. completion: ((Error?) -> Void)?) {
  147. guard let user = user else {
  148. fatalError("Internal Auth error: failed to get user unenrolling in MultiFactor")
  149. }
  150. let request = WithdrawMFARequest(idToken: user.rawAccessToken(),
  151. mfaEnrollmentID: factorUID,
  152. requestConfiguration: user.requestConfiguration)
  153. AuthBackend.post(with: request) { rawResponse, error in
  154. if let error {
  155. if let completion {
  156. completion(error)
  157. }
  158. } else {
  159. guard let response = rawResponse else {
  160. fatalError("TODO")
  161. }
  162. user.auth?.completeSignIn(withAccessToken: response.idToken,
  163. accessTokenExpirationDate: nil,
  164. refreshToken: response.refreshToken,
  165. anonymous: false) { signInUser, error in
  166. if let completion {
  167. completion(error)
  168. }
  169. }
  170. }
  171. }
  172. }
  173. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  174. public func unenroll(withFactorUID factorUID: String) async throws {
  175. return try await withCheckedThrowingContinuation { continuation in
  176. self.unenroll(withFactorUID: factorUID) { error in
  177. if let error {
  178. continuation.resume(throwing: error)
  179. } else {
  180. continuation.resume()
  181. }
  182. }
  183. }
  184. }
  185. weak var user: User?
  186. convenience init(withMFAEnrollments mfaEnrollments: [AuthProtoMFAEnrollment]) {
  187. self.init()
  188. var multiFactorInfoArray: [MultiFactorInfo] = []
  189. for enrollment in mfaEnrollments {
  190. let multiFactorInfo = PhoneMultiFactorInfo(proto: enrollment)
  191. multiFactorInfoArray.append(multiFactorInfo)
  192. }
  193. enrolledFactors = multiFactorInfoArray
  194. }
  195. override init() {}
  196. // MARK: - NSSecureCoding
  197. private let kEnrolledFactorsCodingKey = "enrolledFactors"
  198. public static var supportsSecureCoding: Bool {
  199. true
  200. }
  201. public func encode(with coder: NSCoder) {
  202. coder.encode(enrolledFactors, forKey: kEnrolledFactorsCodingKey)
  203. // Do not encode `user` weak property.
  204. }
  205. public required init?(coder: NSCoder) {
  206. let enrolledFactors = coder
  207. .decodeObject(forKey: kEnrolledFactorsCodingKey) as? [MultiFactorInfo]
  208. self.enrolledFactors = enrolledFactors
  209. // Do not decode `user` weak property.
  210. }
  211. }
  212. #endif