GetOOBConfirmationCodeRequest.swift 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  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. enum GetOOBConfirmationCodeRequestType: Int {
  16. /// Requests a password reset code.
  17. case passwordReset
  18. /// Requests an email verification code.
  19. case verifyEmail
  20. /// Requests an email sign-in link.
  21. case emailLink
  22. /// Requests a verify before update email.
  23. case verifyBeforeUpdateEmail
  24. var value: String {
  25. switch self {
  26. case .passwordReset:
  27. return kPasswordResetRequestTypeValue
  28. case .verifyEmail:
  29. return kVerifyEmailRequestTypeValue
  30. case .emailLink:
  31. return kEmailLinkSignInTypeValue
  32. case .verifyBeforeUpdateEmail:
  33. return kVerifyBeforeUpdateEmailRequestTypeValue
  34. }
  35. }
  36. }
  37. private let kGetOobConfirmationCodeEndpoint = "getOobConfirmationCode"
  38. /// The name of the required "requestType" property in the request.
  39. private let kRequestTypeKey = "requestType"
  40. /// The name of the "email" property in the request.
  41. private let kEmailKey = "email"
  42. /// The name of the "newEmail" property in the request.
  43. private let kNewEmailKey = "newEmail"
  44. /// The key for the "idToken" value in the request. This is actually the STS Access Token,
  45. /// despite its confusing (backwards compatible) parameter name.
  46. private let kIDTokenKey = "idToken"
  47. /// The key for the "continue URL" value in the request.
  48. private let kContinueURLKey = "continueUrl"
  49. /// The key for the "iOS Bundle Identifier" value in the request.
  50. private let kIosBundleIDKey = "iOSBundleId"
  51. /// The key for the "Android Package Name" value in the request.
  52. private let kAndroidPackageNameKey = "androidPackageName"
  53. /// The key for the request parameter indicating whether the android app should be installed or not.
  54. private let kAndroidInstallAppKey = "androidInstallApp"
  55. /// The key for the "minimum Android version supported" value in the request.
  56. private let kAndroidMinimumVersionKey = "androidMinimumVersion"
  57. /// The key for the request parameter indicating whether the action code can be handled in the app
  58. /// or not.
  59. private let kCanHandleCodeInAppKey = "canHandleCodeInApp"
  60. /// The key for the "dynamic link domain" value in the request.
  61. private let kDynamicLinkDomainKey = "dynamicLinkDomain"
  62. /// The key for the "link domain" value in the request.
  63. private let kLinkDomainKey = "linkDomain"
  64. /// The value for the "PASSWORD_RESET" request type.
  65. private let kPasswordResetRequestTypeValue = "PASSWORD_RESET"
  66. /// The value for the "EMAIL_SIGNIN" request type.
  67. private let kEmailLinkSignInTypeValue = "EMAIL_SIGNIN"
  68. /// The value for the "VERIFY_EMAIL" request type.
  69. private let kVerifyEmailRequestTypeValue = "VERIFY_EMAIL"
  70. /// The value for the "VERIFY_AND_CHANGE_EMAIL" request type.
  71. private let kVerifyBeforeUpdateEmailRequestTypeValue = "VERIFY_AND_CHANGE_EMAIL"
  72. /// The key for the tenant id value in the request.
  73. private let kTenantIDKey = "tenantId"
  74. /// The key for the "captchaResponse" value in the request.
  75. private let kCaptchaResponseKey = "captchaResp"
  76. /// The key for the "clientType" value in the request.
  77. private let kClientType = "clientType"
  78. /// The key for the "recaptchaVersion" value in the request.
  79. private let kRecaptchaVersion = "recaptchaVersion"
  80. protocol SuppressWarning {
  81. var dynamicLinkDomain: String? { get set }
  82. }
  83. extension ActionCodeSettings: SuppressWarning {}
  84. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  85. class GetOOBConfirmationCodeRequest: IdentityToolkitRequest, AuthRPCRequest {
  86. typealias Response = GetOOBConfirmationCodeResponse
  87. /// The types of OOB Confirmation Code to request.
  88. private let requestType: GetOOBConfirmationCodeRequestType
  89. /// The email of the user for password reset.
  90. let email: String?
  91. /// The new email to be updated for verifyBeforeUpdateEmail.
  92. private let updatedEmail: String?
  93. /// The STS Access Token of the authenticated user for email change.
  94. private let accessToken: String?
  95. /// This URL represents the state/Continue URL in the form of a universal link.
  96. let continueURL: String?
  97. /// The iOS bundle Identifier, if available.
  98. private let iOSBundleID: String?
  99. /// The Android package name, if available.
  100. private let androidPackageName: String?
  101. /// The minimum Android version supported, if available.
  102. private let androidMinimumVersion: String?
  103. /// Indicates whether or not the Android app should be installed if not already available.
  104. private let androidInstallApp: Bool
  105. /// Indicates whether the action code link will open the app directly or after being
  106. /// redirected from a Firebase owned web widget.
  107. let handleCodeInApp: Bool
  108. /// The Firebase Dynamic Link domain used for out of band code flow.
  109. private let dynamicLinkDomain: String?
  110. /// The Firebase Hosting domain used for out of band code flow.
  111. private(set) var linkDomain: String?
  112. /// Response to the captcha.
  113. var captchaResponse: String?
  114. /// The reCAPTCHA version.
  115. var recaptchaVersion: String?
  116. /// Designated initializer.
  117. /// - Parameter requestType: The types of OOB Confirmation Code to request.
  118. /// - Parameter email: The email of the user.
  119. /// - Parameter newEmail: The email of the user to be updated.
  120. /// - Parameter accessToken: The STS Access Token of the currently signed in user.
  121. /// - Parameter actionCodeSettings: An object of FIRActionCodeSettings which specifies action code
  122. /// settings to be applied to the OOB code request.
  123. /// - Parameter requestConfiguration: An object containing configurations to be added to the
  124. /// request.
  125. required init(requestType: GetOOBConfirmationCodeRequestType,
  126. email: String?,
  127. newEmail: String?,
  128. accessToken: String?,
  129. actionCodeSettings: ActionCodeSettings?,
  130. requestConfiguration: AuthRequestConfiguration) {
  131. self.requestType = requestType
  132. self.email = email
  133. updatedEmail = newEmail
  134. self.accessToken = accessToken
  135. continueURL = actionCodeSettings?.url?.absoluteString
  136. iOSBundleID = actionCodeSettings?.iOSBundleID
  137. androidPackageName = actionCodeSettings?.androidPackageName
  138. androidMinimumVersion = actionCodeSettings?.androidMinimumVersion
  139. androidInstallApp = actionCodeSettings?.androidInstallIfNotAvailable ?? false
  140. handleCodeInApp = actionCodeSettings?.handleCodeInApp ?? false
  141. dynamicLinkDomain =
  142. if let actionCodeSettings {
  143. (actionCodeSettings as SuppressWarning).dynamicLinkDomain
  144. } else {
  145. nil
  146. }
  147. linkDomain = actionCodeSettings?.linkDomain
  148. super.init(
  149. endpoint: kGetOobConfirmationCodeEndpoint,
  150. requestConfiguration: requestConfiguration
  151. )
  152. }
  153. static func passwordResetRequest(email: String,
  154. actionCodeSettings: ActionCodeSettings?,
  155. requestConfiguration: AuthRequestConfiguration) ->
  156. GetOOBConfirmationCodeRequest {
  157. Self(requestType: .passwordReset,
  158. email: email,
  159. newEmail: nil,
  160. accessToken: nil,
  161. actionCodeSettings: actionCodeSettings,
  162. requestConfiguration: requestConfiguration)
  163. }
  164. static func verifyEmailRequest(accessToken: String,
  165. actionCodeSettings: ActionCodeSettings?,
  166. requestConfiguration: AuthRequestConfiguration) ->
  167. GetOOBConfirmationCodeRequest {
  168. Self(requestType: .verifyEmail,
  169. email: nil,
  170. newEmail: nil,
  171. accessToken: accessToken,
  172. actionCodeSettings: actionCodeSettings,
  173. requestConfiguration: requestConfiguration)
  174. }
  175. static func signInWithEmailLinkRequest(_ email: String,
  176. actionCodeSettings: ActionCodeSettings?,
  177. requestConfiguration: AuthRequestConfiguration)
  178. -> Self {
  179. Self(requestType: .emailLink,
  180. email: email,
  181. newEmail: nil,
  182. accessToken: nil,
  183. actionCodeSettings: actionCodeSettings,
  184. requestConfiguration: requestConfiguration)
  185. }
  186. static func verifyBeforeUpdateEmail(accessToken: String,
  187. newEmail: String,
  188. actionCodeSettings: ActionCodeSettings?,
  189. requestConfiguration: AuthRequestConfiguration)
  190. -> Self {
  191. Self(requestType: .verifyBeforeUpdateEmail,
  192. email: nil,
  193. newEmail: newEmail,
  194. accessToken: accessToken,
  195. actionCodeSettings: actionCodeSettings,
  196. requestConfiguration: requestConfiguration)
  197. }
  198. var unencodedHTTPRequestBody: [String: AnyHashable]? {
  199. var body: [String: AnyHashable] = [
  200. kRequestTypeKey: requestType.value,
  201. ]
  202. // For password reset requests, we only need an email address in addition to the already
  203. // required fields.
  204. if case .passwordReset = requestType {
  205. body[kEmailKey] = email
  206. }
  207. // For verify email requests, we only need an STS Access Token in addition to the already
  208. // required fields.
  209. if case .verifyEmail = requestType {
  210. body[kIDTokenKey] = accessToken
  211. }
  212. // For email sign-in link requests, we only need an email address in addition to the already
  213. // required fields.
  214. if case .emailLink = requestType {
  215. body[kEmailKey] = email
  216. }
  217. // For email sign-in link requests, we only need an STS Access Token, a new email address in
  218. // addition to the already required fields.
  219. if case .verifyBeforeUpdateEmail = requestType {
  220. body[kNewEmailKey] = updatedEmail
  221. body[kIDTokenKey] = accessToken
  222. }
  223. if let continueURL = continueURL {
  224. body[kContinueURLKey] = continueURL
  225. }
  226. if let iOSBundleID = iOSBundleID {
  227. body[kIosBundleIDKey] = iOSBundleID
  228. }
  229. if let androidPackageName = androidPackageName {
  230. body[kAndroidPackageNameKey] = androidPackageName
  231. }
  232. if let androidMinimumVersion = androidMinimumVersion {
  233. body[kAndroidMinimumVersionKey] = androidMinimumVersion
  234. }
  235. if androidInstallApp {
  236. body[kAndroidInstallAppKey] = true
  237. }
  238. if handleCodeInApp {
  239. body[kCanHandleCodeInAppKey] = true
  240. }
  241. if let dynamicLinkDomain {
  242. body[kDynamicLinkDomainKey] = dynamicLinkDomain
  243. }
  244. if let linkDomain {
  245. body[kLinkDomainKey] = linkDomain
  246. }
  247. if let captchaResponse {
  248. body[kCaptchaResponseKey] = captchaResponse
  249. }
  250. body[kClientType] = clientType
  251. if let recaptchaVersion {
  252. body[kRecaptchaVersion] = recaptchaVersion
  253. }
  254. if let tenantID {
  255. body[kTenantIDKey] = tenantID
  256. }
  257. return body
  258. }
  259. func injectRecaptchaFields(recaptchaResponse: String?, recaptchaVersion: String) {
  260. captchaResponse = recaptchaResponse
  261. self.recaptchaVersion = recaptchaVersion
  262. }
  263. }