SignInWithCredentialTests.swift 20 KB

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