SignInWithCredentialTests.swift 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614
  1. // Copyright 2020 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. import Combine
  16. import XCTest
  17. import FirebaseAuth
  18. class SignInWithCredentialTests: XCTestCase {
  19. override class func setUp() {
  20. FirebaseApp.configureForTests()
  21. }
  22. override class func tearDown() {
  23. FirebaseApp.app()?.delete { success in
  24. if success {
  25. print("Shut down app successfully.")
  26. } else {
  27. print("💥 There was a problem when shutting down the app..")
  28. }
  29. }
  30. }
  31. override func setUp() {
  32. do {
  33. try Auth.auth().signOut()
  34. } catch {}
  35. }
  36. static let apiKey = Credentials.apiKey
  37. static let accessTokenTimeToLive: TimeInterval = 60 * 60
  38. static let refreshToken = "REFRESH_TOKEN"
  39. static let accessToken = "ACCESS_TOKEN"
  40. static let email = "johnnyappleseed@apple.com"
  41. static let password = "secret"
  42. static let localID = "LOCAL_ID"
  43. static let displayName = "Johnny Appleseed"
  44. static let passwordHash = "UkVEQUNURUQ="
  45. static let oAuthSessionID = "sessionID"
  46. static let oAuthRequestURI = "requestURI"
  47. static let googleID = "GOOGLE_ID"
  48. static let googleAccessToken = "GOOGLE_ACCESS_TOKEN"
  49. static let googleDisplayName = "Google Doe"
  50. static let googleEmail = "user@gmail.com"
  51. static let googleProfile: [String: String] = {
  52. [
  53. "iss": "https://accounts.google.com\\",
  54. "email": googleEmail,
  55. "given_name": "User",
  56. "family_name": "Doe",
  57. ]
  58. }()
  59. static let verificationCode = "12345678"
  60. static let verificationID = "55432"
  61. static let fakeEmailSignInlink =
  62. "https://test.app.goo.gl/?link=https://test.firebaseapp.com/__/auth/action?apiKey%3DtestAPIKey%26mode%3DsignIn%26oobCode%3Dtestoobcode%26continueUrl%3Dhttps://test.apps.com&ibi=com.test.com&ifl=https://test.firebaseapp.com/__/auth/action?apiKey%3DtestAPIKey%26mode%3DsignIn%26oobCode%3Dtestoobcode%26continueUrl%3Dhttps://test.apps.com"
  63. static let fakeOOBCode = "testoobcode"
  64. class MockEmailLinkSignInResponse: FIREmailLinkSignInResponse {
  65. override var idToken: String { SignInWithCredentialTests.accessToken }
  66. override var refreshToken: String { SignInWithCredentialTests.refreshToken }
  67. override var approximateExpirationDate: Date {
  68. Date(timeIntervalSinceNow: SignInWithCredentialTests.accessTokenTimeToLive)
  69. }
  70. }
  71. class MockVerifyPasswordResponse: FIRVerifyPasswordResponse {
  72. override var idToken: String? { SignInWithCredentialTests.accessToken }
  73. override var refreshToken: String? { SignInWithCredentialTests.refreshToken }
  74. override var approximateExpirationDate: Date? {
  75. Date(timeIntervalSinceNow: SignInWithCredentialTests.accessTokenTimeToLive)
  76. }
  77. }
  78. class MockVerifyAssertionResponse: FIRVerifyAssertionResponse {
  79. override var federatedID: String? { SignInWithCredentialTests.googleID }
  80. override var providerID: String? { GoogleAuthProviderID }
  81. override var localID: String? { SignInWithCredentialTests.localID }
  82. override var displayName: String? { SignInWithCredentialTests.displayName }
  83. override var username: String? { SignInWithCredentialTests.displayName }
  84. override var profile: [String: NSObject]? {
  85. SignInWithCredentialTests.googleProfile as [String: NSString]
  86. }
  87. override var idToken: String { SignInWithCredentialTests.accessToken }
  88. override var refreshToken: String { SignInWithCredentialTests.refreshToken }
  89. override var approximateExpirationDate: Date {
  90. Date(timeIntervalSinceNow: SignInWithCredentialTests.accessTokenTimeToLive)
  91. }
  92. }
  93. class MockVerifyPhoneNumberResponse: FIRVerifyPhoneNumberResponse {
  94. override var idToken: String? { SignInWithCredentialTests.accessToken }
  95. override var refreshToken: String? { SignInWithCredentialTests.refreshToken }
  96. override var approximateExpirationDate: Date? {
  97. Date(timeIntervalSinceNow: SignInWithCredentialTests.accessTokenTimeToLive)
  98. }
  99. }
  100. class MockGetAccountInfoResponseUser: FIRGetAccountInfoResponseUser {
  101. override var localID: String { SignInWithCredentialTests.localID }
  102. override var email: String { SignInWithCredentialTests.email }
  103. override var displayName: String { SignInWithCredentialTests.displayName }
  104. }
  105. class MockGetAccountInfoResponse: FIRGetAccountInfoResponse {
  106. override var users: [FIRGetAccountInfoResponseUser] {
  107. return [MockGetAccountInfoResponseUser(dictionary: [:])]
  108. }
  109. }
  110. class MockAuthBackend: AuthBackendImplementationMock {
  111. var emailLinkSignInCallback: Result<FIREmailLinkSignInResponse, Error> =
  112. .success(MockEmailLinkSignInResponse())
  113. override func emailLinkSignin(_ request: FIREmailLinkSignInRequest,
  114. callback: @escaping FIREmailLinkSigninResponseCallback) {
  115. XCTAssertEqual(request.apiKey, SignInWithCredentialTests.apiKey)
  116. XCTAssertEqual(request.email, SignInWithCredentialTests.email)
  117. XCTAssertEqual(request.oobCode, SignInWithCredentialTests.fakeOOBCode)
  118. switch emailLinkSignInCallback {
  119. case let .success(response):
  120. callback(response, nil)
  121. case let .failure(error):
  122. callback(nil, error)
  123. }
  124. }
  125. override func verifyPhoneNumber(_ request: FIRVerifyPhoneNumberRequest,
  126. callback: @escaping FIRVerifyPhoneNumberResponseCallback) {
  127. XCTAssertEqual(request.verificationCode, SignInWithCredentialTests.verificationCode)
  128. XCTAssertEqual(request.verificationID, SignInWithCredentialTests.verificationID)
  129. XCTAssertEqual(request.operation, FIRAuthOperationType.signUpOrSignIn)
  130. let response = MockVerifyPhoneNumberResponse()
  131. response.isNewUser = true
  132. callback(response, nil)
  133. }
  134. var verifyAssertionCallBack: Result<FIRVerifyAssertionResponse, Error> =
  135. .success(MockVerifyAssertionResponse())
  136. override func verifyAssertion(_ request: FIRVerifyAssertionRequest,
  137. callback: @escaping FIRVerifyAssertionResponseCallback) {
  138. XCTAssertEqual(request.apiKey, SignInWithCredentialTests.apiKey)
  139. XCTAssertEqual(request.providerID, GoogleAuthProviderID)
  140. XCTAssertTrue(request.returnSecureToken)
  141. switch verifyAssertionCallBack {
  142. case let .success(response):
  143. callback(response, nil)
  144. case let .failure(error):
  145. callback(nil, error)
  146. }
  147. }
  148. var verifyPasswordCallback: Result<FIRVerifyPasswordResponse, Error> =
  149. .success(MockVerifyPasswordResponse())
  150. override func verifyPassword(_ request: FIRVerifyPasswordRequest,
  151. callback: @escaping FIRVerifyPasswordResponseCallback) {
  152. XCTAssertEqual(request.apiKey, SignInWithCredentialTests.apiKey)
  153. XCTAssertEqual(request.email, SignInWithCredentialTests.email)
  154. XCTAssertEqual(request.password, SignInWithCredentialTests.password)
  155. XCTAssertTrue(request.returnSecureToken)
  156. switch verifyPasswordCallback {
  157. case let .success(response):
  158. callback(response, nil)
  159. case let .failure(error):
  160. callback(nil, error)
  161. }
  162. }
  163. override func getAccountInfo(_ request: FIRGetAccountInfoRequest,
  164. callback: @escaping FIRGetAccountInfoResponseCallback) {
  165. XCTAssertEqual(request.apiKey, SignInWithCredentialTests.apiKey)
  166. XCTAssertEqual(request.accessToken, SignInWithCredentialTests.accessToken)
  167. callback(MockGetAccountInfoResponse(), nil)
  168. }
  169. }
  170. func testSignInWithEmailCredentialSuccess() {
  171. // given
  172. FIRAuthBackend.setBackendImplementation(MockAuthBackend())
  173. var cancellables = Set<AnyCancellable>()
  174. let userSignInExpectation = expectation(description: "User signed in")
  175. let emailCredential = EmailAuthProvider.credential(
  176. withEmail: Self.email,
  177. password: Self.password
  178. )
  179. // when
  180. Auth.auth()
  181. .signIn(with: emailCredential)
  182. .sink { completion in
  183. switch completion {
  184. case .finished:
  185. print("Finished")
  186. case let .failure(error):
  187. XCTFail("💥 Something went wrong: \(error)")
  188. }
  189. } receiveValue: { authDataResult in
  190. let user = authDataResult.user
  191. XCTAssertNotNil(user)
  192. XCTAssertEqual(user.uid, Self.localID)
  193. XCTAssertEqual(user.displayName, Self.displayName)
  194. XCTAssertEqual(user.email, Self.email)
  195. XCTAssertFalse(user.isAnonymous)
  196. XCTAssertEqual(user.providerData.count, 0)
  197. userSignInExpectation.fulfill()
  198. }
  199. .store(in: &cancellables)
  200. // then
  201. wait(for: [userSignInExpectation], timeout: expectationTimeout)
  202. }
  203. func testSignInWithEmailCredentialFailure() {
  204. // given
  205. let authBackend = MockAuthBackend()
  206. authBackend
  207. .verifyPasswordCallback = .failure(FIRAuthErrorUtils.userDisabledError(withMessage: nil))
  208. FIRAuthBackend.setBackendImplementation(authBackend)
  209. var cancellables = Set<AnyCancellable>()
  210. let userSignInExpectation = expectation(description: "User disabled")
  211. let emailCredential = EmailAuthProvider.credential(
  212. withEmail: Self.email,
  213. password: Self.password
  214. )
  215. // when
  216. Auth.auth()
  217. .signIn(with: emailCredential)
  218. .sink { completion in
  219. if case let .failure(error as NSError) = completion {
  220. XCTAssertEqual(error.code, AuthErrorCode.userDisabled.rawValue)
  221. userSignInExpectation.fulfill()
  222. }
  223. } receiveValue: { authDataResult in
  224. XCTFail("💥 result unexpected")
  225. }
  226. .store(in: &cancellables)
  227. // then
  228. wait(for: [userSignInExpectation], timeout: expectationTimeout)
  229. }
  230. func testSignInWithEmailCredentialEmptyPassword() {
  231. // given
  232. FIRAuthBackend.setBackendImplementation(MockAuthBackend())
  233. var cancellables = Set<AnyCancellable>()
  234. let userSignInExpectation = expectation(description: "User wrong password")
  235. let emailCredential = EmailAuthProvider.credential(withEmail: Self.email, password: "")
  236. // when
  237. Auth.auth()
  238. .signIn(with: emailCredential)
  239. .sink { completion in
  240. if case let .failure(error as NSError) = completion {
  241. XCTAssertEqual(error.code, AuthErrorCode.wrongPassword.rawValue)
  242. userSignInExpectation.fulfill()
  243. }
  244. } receiveValue: { authDataResult in
  245. XCTFail("💥 result unexpected")
  246. }
  247. .store(in: &cancellables)
  248. // then
  249. wait(for: [userSignInExpectation], timeout: expectationTimeout)
  250. }
  251. func testSignInWithGoogleAccountExistsError() {
  252. // given
  253. let authBackend = MockAuthBackend()
  254. let mockVerifyAssertionResponse = MockVerifyAssertionResponse()
  255. mockVerifyAssertionResponse.needConfirmation = true
  256. authBackend.verifyAssertionCallBack = .success(mockVerifyAssertionResponse)
  257. FIRAuthBackend.setBackendImplementation(authBackend)
  258. var cancellables = Set<AnyCancellable>()
  259. let userSignInExpectation = expectation(description: "User Google exists")
  260. let googleCredential = GoogleAuthProvider.credential(
  261. withIDToken: Self.googleID,
  262. accessToken: Self.googleAccessToken
  263. )
  264. // when
  265. Auth.auth()
  266. .signIn(with: googleCredential)
  267. .sink { completion in
  268. if case let .failure(error as NSError) = completion {
  269. XCTAssertEqual(
  270. error.code,
  271. AuthErrorCode.accountExistsWithDifferentCredential.rawValue
  272. )
  273. userSignInExpectation.fulfill()
  274. }
  275. } receiveValue: { authDataResult in
  276. XCTFail("💥 result unexpected")
  277. }
  278. .store(in: &cancellables)
  279. // then
  280. wait(for: [userSignInExpectation], timeout: expectationTimeout)
  281. }
  282. func testSignInWithGoogleCredentialSuccess() {
  283. // given
  284. FIRAuthBackend.setBackendImplementation(MockAuthBackend())
  285. var cancellables = Set<AnyCancellable>()
  286. let userSignInExpectation = expectation(description: "User signed in")
  287. let googleCredential = GoogleAuthProvider.credential(
  288. withIDToken: Self.googleID,
  289. accessToken: Self.googleAccessToken
  290. )
  291. // when
  292. Auth.auth()
  293. .signIn(with: googleCredential)
  294. .sink { completion in
  295. switch completion {
  296. case .finished:
  297. print("Finished")
  298. case let .failure(error):
  299. XCTFail("💥 Something went wrong: \(error)")
  300. }
  301. } receiveValue: { authDataResult in
  302. let user = authDataResult.user
  303. XCTAssertNotNil(user)
  304. XCTAssertEqual(user.uid, Self.localID)
  305. XCTAssertEqual(user.displayName, Self.displayName)
  306. XCTAssertEqual(user.email, Self.email)
  307. XCTAssertFalse(user.isAnonymous)
  308. XCTAssertEqual(user.providerData.count, 0)
  309. userSignInExpectation.fulfill()
  310. }
  311. .store(in: &cancellables)
  312. // then
  313. wait(for: [userSignInExpectation], timeout: expectationTimeout)
  314. }
  315. func testSignInWithGoogleCredentialFailure() {
  316. // given
  317. let authBackend = MockAuthBackend()
  318. authBackend
  319. .verifyAssertionCallBack = .failure(FIRAuthErrorUtils
  320. .emailAlreadyInUseError(withEmail: nil))
  321. FIRAuthBackend.setBackendImplementation(authBackend)
  322. var cancellables = Set<AnyCancellable>()
  323. let userSignInExpectation = expectation(description: "User signed in")
  324. let googleCredential = GoogleAuthProvider.credential(
  325. withIDToken: Self.googleID,
  326. accessToken: Self.googleAccessToken
  327. )
  328. // when
  329. Auth.auth()
  330. .signIn(with: googleCredential)
  331. .sink { completion in
  332. if case let .failure(error as NSError) = completion {
  333. XCTAssertEqual(error.code, AuthErrorCode.emailAlreadyInUse.rawValue)
  334. userSignInExpectation.fulfill()
  335. }
  336. } receiveValue: { authDataResult in
  337. XCTFail("💥 result unexpected")
  338. }
  339. .store(in: &cancellables)
  340. // then
  341. wait(for: [userSignInExpectation], timeout: expectationTimeout)
  342. }
  343. func testSignInWithCredentialSuccess() {
  344. // given
  345. FIRAuthBackend.setBackendImplementation(MockAuthBackend())
  346. var cancellables = Set<AnyCancellable>()
  347. let userSignInExpectation = expectation(description: "User signed in")
  348. let googleCredential = GoogleAuthProvider.credential(
  349. withIDToken: Self.googleID,
  350. accessToken: Self.googleAccessToken
  351. )
  352. // when
  353. Auth.auth()
  354. .signIn(with: googleCredential)
  355. .sink { completion in
  356. switch completion {
  357. case .finished:
  358. print("Finished")
  359. case let .failure(error):
  360. XCTFail("💥 Something went wrong: \(error)")
  361. }
  362. } receiveValue: { authDataResult in
  363. let user = authDataResult.user
  364. XCTAssertNotNil(user)
  365. XCTAssertEqual(user.uid, Self.localID)
  366. XCTAssertEqual(user.displayName, Self.displayName)
  367. XCTAssertEqual(user.email, Self.email)
  368. XCTAssertFalse(user.isAnonymous)
  369. XCTAssertEqual(user.providerData.count, 0)
  370. XCTAssertEqual(authDataResult.additionalUserInfo?.username, Self.displayName)
  371. XCTAssertEqual(
  372. authDataResult.additionalUserInfo?.profile,
  373. Self.googleProfile as [String: NSString]
  374. )
  375. userSignInExpectation.fulfill()
  376. }
  377. .store(in: &cancellables)
  378. // then
  379. wait(for: [userSignInExpectation], timeout: expectationTimeout)
  380. }
  381. func testPhoneAuthSuccess() {
  382. // given
  383. FIRAuthBackend.setBackendImplementation(MockAuthBackend())
  384. var cancellables = Set<AnyCancellable>()
  385. let userSignInExpectation = expectation(description: "User signed in")
  386. let credential = PhoneAuthProvider.provider()
  387. .credential(withVerificationID: Self.verificationID,
  388. verificationCode: Self.verificationCode)
  389. // when
  390. Auth.auth()
  391. .signIn(with: credential)
  392. .sink { completion in
  393. switch completion {
  394. case .finished:
  395. print("Finished")
  396. case let .failure(error):
  397. XCTFail("💥 Something went wrong: \(error)")
  398. }
  399. } receiveValue: { authDataResult in
  400. let user = authDataResult.user
  401. XCTAssertNotNil(user)
  402. XCTAssertEqual(user.uid, Self.localID)
  403. XCTAssertEqual(user.displayName, Self.displayName)
  404. XCTAssertEqual(user.email, Self.email)
  405. XCTAssertFalse(user.isAnonymous)
  406. XCTAssertEqual(user.providerData.count, 0)
  407. userSignInExpectation.fulfill()
  408. }
  409. .store(in: &cancellables)
  410. // then
  411. wait(for: [userSignInExpectation], timeout: expectationTimeout)
  412. }
  413. func testPhoneAuthMissingVerificationCode() {
  414. // given
  415. FIRAuthBackend.setBackendImplementation(MockAuthBackend())
  416. var cancellables = Set<AnyCancellable>()
  417. let userSignInExpectation = expectation(description: "User missing verification code")
  418. let credential = PhoneAuthProvider.provider()
  419. .credential(withVerificationID: Self.verificationID, verificationCode: "")
  420. // when
  421. Auth.auth()
  422. .signIn(with: credential)
  423. .sink { completion in
  424. if case let .failure(error as NSError) = completion {
  425. XCTAssertEqual(error.code, AuthErrorCode.missingVerificationCode.rawValue)
  426. userSignInExpectation.fulfill()
  427. }
  428. } receiveValue: { authDataResult in
  429. XCTFail("💥 result unexpected")
  430. }
  431. .store(in: &cancellables)
  432. // then
  433. wait(for: [userSignInExpectation], timeout: expectationTimeout)
  434. }
  435. func testPhoneAuthMissingVerificationID() {
  436. // given
  437. FIRAuthBackend.setBackendImplementation(MockAuthBackend())
  438. var cancellables = Set<AnyCancellable>()
  439. let userSignInExpectation = expectation(description: "User missing verification ID")
  440. let credential = PhoneAuthProvider.provider()
  441. .credential(withVerificationID: "", verificationCode: Self.verificationCode)
  442. // when
  443. Auth.auth()
  444. .signIn(with: credential)
  445. .sink { completion in
  446. if case let .failure(error as NSError) = completion {
  447. XCTAssertEqual(error.code, AuthErrorCode.missingVerificationID.rawValue)
  448. userSignInExpectation.fulfill()
  449. }
  450. } receiveValue: { authDataResult in
  451. XCTFail("💥 result unexpected")
  452. }
  453. .store(in: &cancellables)
  454. // then
  455. wait(for: [userSignInExpectation], timeout: expectationTimeout)
  456. }
  457. func testSignInWithEmailLinkCredentialSuccess() {
  458. // given
  459. FIRAuthBackend.setBackendImplementation(MockAuthBackend())
  460. var cancellables = Set<AnyCancellable>()
  461. let userSignInExpectation = expectation(description: "User signed in")
  462. let emailCrendential = EmailAuthProvider.credential(
  463. withEmail: Self.email,
  464. link: Self.fakeEmailSignInlink
  465. )
  466. // when
  467. Auth.auth()
  468. .signIn(with: emailCrendential)
  469. .sink { completion in
  470. switch completion {
  471. case .finished:
  472. print("Finished")
  473. case let .failure(error):
  474. XCTFail("💥 Something went wrong: \(error)")
  475. }
  476. } receiveValue: { authDataResult in
  477. let user = authDataResult.user
  478. XCTAssertNotNil(user)
  479. XCTAssertEqual(user.refreshToken, Self.refreshToken)
  480. XCTAssertEqual(user.displayName, Self.displayName)
  481. XCTAssertEqual(user.email, Self.email)
  482. XCTAssertFalse(user.isAnonymous)
  483. userSignInExpectation.fulfill()
  484. }
  485. .store(in: &cancellables)
  486. // then
  487. wait(for: [userSignInExpectation], timeout: expectationTimeout)
  488. }
  489. func testSignInWithEmailLinkCredentialFailure() {
  490. // given
  491. let authBackend = MockAuthBackend()
  492. authBackend
  493. .emailLinkSignInCallback = .failure(FIRAuthErrorUtils.userDisabledError(withMessage: nil))
  494. FIRAuthBackend.setBackendImplementation(authBackend)
  495. var cancellables = Set<AnyCancellable>()
  496. let userSignInExpectation = expectation(description: "User disabled")
  497. let emailCrendential = EmailAuthProvider.credential(
  498. withEmail: Self.email,
  499. link: Self.fakeEmailSignInlink
  500. )
  501. // when
  502. Auth.auth()
  503. .signIn(with: emailCrendential)
  504. .sink { completion in
  505. if case let .failure(error as NSError) = completion {
  506. XCTAssertNotNil(error.userInfo[NSLocalizedDescriptionKey])
  507. XCTAssertEqual(error.code, AuthErrorCode.userDisabled.rawValue)
  508. userSignInExpectation.fulfill()
  509. }
  510. } receiveValue: { authDataResult in
  511. XCTFail("💥 result unexpected")
  512. }
  513. .store(in: &cancellables)
  514. // then
  515. wait(for: [userSignInExpectation], timeout: expectationTimeout)
  516. }
  517. }