MultiFactor.swift 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  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. @objc(FIRMultiFactor) public class MultiFactor: NSObject, NSSecureCoding {
  22. @objc public var enrolledFactors: [MultiFactorInfo]?
  23. /** @fn getSessionWithCompletion:
  24. @brief Get a session for a second factor enrollment operation.
  25. @param completion A block with the session identifier for a second factor enrollment operation.
  26. This is used to identify the current user trying to enroll a second factor.
  27. */
  28. @objc(getSessionWithCompletion:)
  29. public func getSessionWithCompletion(_ completion: ((MultiFactorSession?, Error?) -> Void)?) {
  30. let session = MultiFactorSession.sessionForCurrentUser
  31. if let completion {
  32. completion(session, nil)
  33. }
  34. }
  35. /** @fn getSessionWithCompletion:
  36. @brief Get a session for a second factor enrollment operation.
  37. @param completion A block with the session identifier for a second factor enrollment operation.
  38. This is used to identify the current user trying to enroll a second factor.
  39. */
  40. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  41. public func session() async throws -> MultiFactorSession {
  42. return try await withCheckedThrowingContinuation { continuation in
  43. self.getSessionWithCompletion { session, error in
  44. if let session {
  45. continuation.resume(returning: session)
  46. } else {
  47. continuation.resume(throwing: error!)
  48. }
  49. }
  50. }
  51. }
  52. /** @fn enrollWithAssertion:displayName:completion:
  53. @brief Enrolls a second factor as identified by the `MultiFactorAssertion` parameter for the
  54. current user.
  55. @param displayName An optional display name associated with the multi factor to enroll.
  56. @param completion The block invoked when the request is complete, or fails.
  57. */
  58. @objc(enrollWithAssertion:displayName:completion:)
  59. public func enroll(with assertion: MultiFactorAssertion,
  60. displayName: String?,
  61. completion: ((Error?) -> Void)?) {
  62. let phoneAssertion = assertion as? PhoneMultiFactorAssertion
  63. let finalizeMFAPhoneRequestInfo = AuthProtoFinalizeMFAPhoneRequestInfo(
  64. sessionInfo: phoneAssertion?.authCredential?.verificationID,
  65. verificationCode: phoneAssertion?.authCredential?.verificationCode
  66. )
  67. guard let user = user else {
  68. fatalError("Internal Auth error: failed to get user enrolling in MultiFactor")
  69. }
  70. let request = FinalizeMFAEnrollmentRequest(
  71. idToken: self.user?.rawAccessToken(),
  72. displayName: displayName,
  73. verificationInfo: finalizeMFAPhoneRequestInfo,
  74. requestConfiguration: user.requestConfiguration
  75. )
  76. AuthBackend.post(withRequest: request) { rawResponse, error in
  77. if let error {
  78. if let completion {
  79. completion(error)
  80. }
  81. } else if let response = rawResponse as? FinalizeMFAEnrollmentResponse {
  82. user.auth?.completeSignIn(withAccessToken: response.idToken,
  83. accessTokenExpirationDate: nil,
  84. refreshToken: response.refreshToken,
  85. anonymous: false) { _, error in
  86. if error != nil {
  87. try? user.auth?.signOut()
  88. }
  89. if let completion {
  90. completion(error)
  91. }
  92. }
  93. }
  94. }
  95. }
  96. /** @fn enrollWithAssertion:displayName:completion:
  97. @brief Enrolls a second factor as identified by the `MultiFactorAssertion` parameter for the
  98. current user.
  99. @param displayName An optional display name associated with the multi factor to enroll.
  100. @param completion The block invoked when the request is complete, or fails.
  101. */
  102. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  103. public func enroll(with assertion: MultiFactorAssertion, displayName: String?) async throws {
  104. return try await withCheckedThrowingContinuation { continuation in
  105. self.enroll(with: assertion, displayName: displayName) { error in
  106. if let error {
  107. continuation.resume(throwing: error)
  108. } else {
  109. continuation.resume()
  110. }
  111. }
  112. }
  113. }
  114. /** @fn unenrollWithInfo:completion:
  115. @brief Unenroll the given multi factor.
  116. @param completion The block invoked when the request to send the verification email is complete,
  117. or fails.
  118. */
  119. @objc(unenrollWithInfo:completion:)
  120. public func unenroll(with factorInfo: MultiFactorInfo,
  121. completion: ((Error?) -> Void)?) {
  122. unenroll(withFactorUID: factorInfo.uid, completion: completion)
  123. }
  124. /** @fn unenrollWithInfo:completion:
  125. @brief Unenroll the given multi factor.
  126. @param completion The block invoked when the request to send the verification email is complete,
  127. or fails.
  128. */
  129. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  130. public func unenroll(with factorInfo: MultiFactorInfo) async throws {
  131. try await unenroll(withFactorUID: factorInfo.uid)
  132. }
  133. /** @fn unenrollWithFactorUID:completion:
  134. @brief Unenroll the given multi factor.
  135. @param completion The block invoked when the request to send the verification email is complete,
  136. or fails.
  137. */
  138. @objc(unenrollWithFactorUID:completion:)
  139. public func unenroll(withFactorUID factorUID: String,
  140. completion: ((Error?) -> Void)?) {
  141. guard let user = user else {
  142. fatalError("Internal Auth error: failed to get user unenrolling in MultiFactor")
  143. }
  144. let request = WithdrawMFARequest(idToken: user.rawAccessToken(),
  145. mfaEnrollmentID: factorUID,
  146. requestConfiguration: user.requestConfiguration)
  147. AuthBackend.post(withRequest: request) { rawResponse, error in
  148. if let error {
  149. if let completion {
  150. completion(error)
  151. }
  152. } else {
  153. guard let response = rawResponse as? WithdrawMFAResponse else {
  154. fatalError("TODO")
  155. }
  156. user.auth?.completeSignIn(withAccessToken: response.idToken,
  157. accessTokenExpirationDate: nil,
  158. refreshToken: response.refreshToken,
  159. anonymous: false) { signInUser, error in
  160. if let completion {
  161. completion(error)
  162. }
  163. }
  164. }
  165. }
  166. }
  167. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  168. public func unenroll(withFactorUID factorUID: String) async throws {
  169. return try await withCheckedThrowingContinuation { continuation in
  170. self.unenroll(withFactorUID: factorUID) { error in
  171. if let error {
  172. continuation.resume(throwing: error)
  173. } else {
  174. continuation.resume()
  175. }
  176. }
  177. }
  178. }
  179. weak var user: User?
  180. convenience init(withMFAEnrollments mfaEnrollments: [AuthProtoMFAEnrollment]) {
  181. self.init()
  182. var multiFactorInfoArray: [MultiFactorInfo] = []
  183. for enrollment in mfaEnrollments {
  184. let multiFactorInfo = PhoneMultiFactorInfo(proto: enrollment)
  185. multiFactorInfoArray.append(multiFactorInfo)
  186. }
  187. enrolledFactors = multiFactorInfoArray
  188. }
  189. override init() {}
  190. // MARK: - NSSecureCoding
  191. private let kEnrolledFactorsCodingKey = "enrolledFactors"
  192. public static var supportsSecureCoding: Bool {
  193. true
  194. }
  195. public func encode(with coder: NSCoder) {
  196. coder.encode(enrolledFactors, forKey: kEnrolledFactorsCodingKey)
  197. // Do not encode `user` weak property.
  198. }
  199. public required init?(coder: NSCoder) {
  200. let enrolledFactors = coder
  201. .decodeObject(forKey: kEnrolledFactorsCodingKey) as? [MultiFactorInfo]
  202. self.enrolledFactors = enrolledFactors
  203. // Do not decode `user` weak property.
  204. }
  205. }
  206. #endif