Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[auth-swift] Swift implementation of link anon to email password #12083

Merged
merged 3 commits into from
Nov 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions FirebaseAuth/Sources/Swift/Auth/Auth.swift
Original file line number Diff line number Diff line change
Expand Up @@ -944,6 +944,7 @@ extension Auth: AuthInterop {
let request = SignUpNewUserRequest(email: email,
password: password,
displayName: nil,
idToken: nil,
requestConfiguration: self.requestConfiguration)

#if os(iOS)
Expand Down Expand Up @@ -2439,8 +2440,8 @@ extension Auth: AuthInterop {
}
}

private func injectRecaptcha<T: AuthRPCRequest>(request: T,
action: AuthRecaptchaAction) async throws -> T
func injectRecaptcha<T: AuthRPCRequest>(request: T,
action: AuthRecaptchaAction) async throws -> T
.Response {
let recaptchaVerifier = AuthRecaptchaVerifier.shared(auth: self)
if recaptchaVerifier.enablementStatus(forProvider: AuthRecaptchaProvider.password) {
Expand All @@ -2461,7 +2462,7 @@ extension Auth: AuthInterop {
try await recaptchaVerifier.injectRecaptchaFields(
request: request,
provider: AuthRecaptchaProvider.password,
action: AuthRecaptchaAction.signInWithPassword
action: action
)
} else {
throw error
Expand Down
16 changes: 16 additions & 0 deletions FirebaseAuth/Sources/Swift/Backend/RPC/SignUpNewUserRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ private let kPasswordKey = "password"
*/
private let kDisplayNameKey = "displayName"

/** @var kIDToken
@brief The key for the "kIDToken" value in the request.
*/
private let kIDToken = "idToken"

/** @var kCaptchaResponseKey
@brief The key for the "captchaResponse" value in the request.
*/
Expand Down Expand Up @@ -77,6 +82,12 @@ class SignUpNewUserRequest: IdentityToolkitRequest, AuthRPCRequest {
@brief The password inputed by the user.
*/
private(set) var displayName: String?

/** @property idToken
@brief The idToken of the user.
*/
private(set) var idToken: String?

/** @property captchaResponse
@brief Response to the captcha.
*/
Expand Down Expand Up @@ -105,10 +116,12 @@ class SignUpNewUserRequest: IdentityToolkitRequest, AuthRPCRequest {
init(email: String?,
password: String?,
displayName: String?,
idToken: String?,
requestConfiguration: AuthRequestConfiguration) {
self.email = email
self.password = password
self.displayName = displayName
self.idToken = idToken
super.init(endpoint: kSignupNewUserEndpoint, requestConfiguration: requestConfiguration)
}

Expand All @@ -123,6 +136,9 @@ class SignUpNewUserRequest: IdentityToolkitRequest, AuthRPCRequest {
if let displayName {
postBody[kDisplayNameKey] = displayName
}
if let idToken {
postBody[kIDToken] = idToken
}
if let captchaResponse {
postBody[kCaptchaResponseKey] = captchaResponse
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,10 @@ import Foundation
override public class var supportsSecureCoding: Bool { return secureCodingWorkaround }

public required init?(coder: NSCoder) {
guard let phoneNumber = coder.decodeObject(forKey: kPhoneNumberCodingKey) as? String else {
guard let phoneNumber = coder.decodeObject(forKey: kPhoneNumberCodingKey) as? NSString else {
return nil
}
self.phoneNumber = phoneNumber
self.phoneNumber = phoneNumber as String
super.init(coder: coder)
}

Expand Down
96 changes: 84 additions & 12 deletions FirebaseAuth/Sources/Swift/User/User.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1676,6 +1676,88 @@ extension User: NSSecureCoding {}
}
#endif

private func link(withEmail email: String,
password: String,
authResult: AuthDataResult,
_ completion: ((AuthDataResult?, Error?) -> Void)?) {
internalGetToken { accessToken, error in
guard let requestConfiguration = self.auth?.requestConfiguration else {
fatalError("Internal auth error: missing auth on User")
}
let request = SignUpNewUserRequest(email: email,
password: password,
displayName: nil,
idToken: accessToken,
requestConfiguration: requestConfiguration)
Task {
do {
#if os(iOS)
guard let auth = self.auth else {
fatalError("Internal Auth error: missing auth instance on user")
}
let response = try await auth.injectRecaptcha(request: request,
action: AuthRecaptchaAction
.signUpPassword)
#else
let response = try await AuthBackend.call(with: request)
#endif
guard let refreshToken = response.refreshToken,
let idToken = response.idToken else {
fatalError("Internal auth error: Invalid SignUpNewUserResponse")
}
// Update the new token and refresh user info again.
self.tokenService = SecureTokenService(
withRequestConfiguration: self.requestConfiguration,
accessToken: idToken,
accessTokenExpirationDate: response.approximateExpirationDate,
refreshToken: refreshToken
)

self.internalGetToken { accessToken, error in
if let error {
User.callInMainThreadWithAuthDataResultAndError(callback: completion,
complete: nil, result: nil,
error: error)
return
}
guard let accessToken else {
fatalError("Internal Auth Error: nil accessToken")
}
let getAccountInfoRequest = GetAccountInfoRequest(
accessToken: accessToken,
requestConfiguration: self.requestConfiguration
)
Task {
do {
let response = try await AuthBackend.call(with: getAccountInfoRequest)
self.isAnonymous = false
self.update(withGetAccountInfoResponse: response)
if let keychainError = self.updateKeychain() {
User.callInMainThreadWithAuthDataResultAndError(callback: completion,
complete: nil, result: nil,
error: keychainError)
return
}
User.callInMainThreadWithAuthDataResultAndError(callback: completion,
complete: nil,
result: authResult)
} catch {
self.signOutIfTokenIsInvalid(withError: error)
User.callInMainThreadWithAuthDataResultAndError(callback: completion,
complete: nil, result: nil,
error: error)
}
}
}
} catch {
self.signOutIfTokenIsInvalid(withError: error)
User.callInMainThreadWithAuthDataResultAndError(callback: completion,
complete: nil, result: nil, error: error)
}
}
}
}

private func link(withEmailCredential emailCredential: EmailAuthCredential,
completion: ((AuthDataResult?, Error?) -> Void)?) {
if hasEmailPasswordCredential {
Expand All @@ -1689,18 +1771,8 @@ extension User: NSSecureCoding {}
}
switch emailCredential.emailType {
case let .password(password):
updateEmail(email: emailCredential.email, password: password) { error in
if let error {
User.callInMainThreadWithAuthDataResultAndError(callback: completion,
result: nil,
error: error)
} else {
let result = AuthDataResult(withUser: self, additionalUserInfo: nil)
User.callInMainThreadWithAuthDataResultAndError(callback: completion,
result: result,
error: nil)
}
}
let result = AuthDataResult(withUser: self, additionalUserInfo: nil)
link(withEmail: emailCredential.email, password: password, authResult: result, completion)
case let .link(link):
internalGetToken { accessToken, error in
var queryItems = AuthWebUtils.parseURL(link)
Expand Down
1 change: 1 addition & 0 deletions FirebaseAuth/Tests/Unit/SignUpNewUserTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ class SignUpNewUserTests: RPCBaseTests {
return SignUpNewUserRequest(email: kTestEmail,
password: kTestPassword,
displayName: kTestDisplayName,
idToken: nil,
requestConfiguration: makeRequestConfiguration())
}
}
20 changes: 16 additions & 4 deletions FirebaseAuth/Tests/Unit/UserTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1136,10 +1136,15 @@ class UserTests: RPCBaseTests {
signInWithFacebookCredential { user in
XCTAssertNotNil(user)
do {
self.setFakeGetAccountProvider(withProviderID: EmailAuthProvider.id)
self.rpcIssuer.respondBlock = {
let request = self.rpcIssuer?.request as? SignUpNewUserRequest
XCTAssertNotNil(request)
XCTAssertEqual(request?.email, self.kEmail)
XCTAssertEqual(request?.password, self.kFakePassword)
XCTAssertNil(request?.displayName)
try self.rpcIssuer?.respond(withJSON: ["idToken": RPCBaseTests.kFakeAccessToken,
"refreshToken": self.kRefreshToken])
self.setFakeGetAccountProvider(withProviderID: EmailAuthProvider.id)
}
let emailCredential = EmailAuthProvider.credential(withEmail: self.kEmail,
password: self.kFakePassword)
Expand Down Expand Up @@ -1172,10 +1177,14 @@ class UserTests: RPCBaseTests {
signInWithFacebookCredential { user in
XCTAssertNotNil(user)
do {
self.setFakeGetAccountProvider(withProviderID: EmailAuthProvider.id)
self.rpcIssuer.respondBlock = {
let request = self.rpcIssuer?.request as? SignUpNewUserRequest
XCTAssertNotNil(request)
XCTAssertEqual(request?.email, self.kEmail)
XCTAssertEqual(request?.password, self.kFakePassword)
try self.rpcIssuer?.respond(withJSON: ["idToken": RPCBaseTests.kFakeAccessToken,
"refreshToken": self.kRefreshToken])
self.setFakeGetAccountProvider(withProviderID: EmailAuthProvider.id)
}
let emailCredential = EmailAuthProvider.credential(withEmail: self.kEmail,
password: self.kFakePassword)
Expand Down Expand Up @@ -1210,8 +1219,11 @@ class UserTests: RPCBaseTests {
signInWithFacebookCredential { user in
XCTAssertNotNil(user)
do {
self.setFakeGetAccountProvider(withProviderID: EmailAuthProvider.id)
self.rpcIssuer.respondBlock = {
let request = self.rpcIssuer?.request as? SignUpNewUserRequest
XCTAssertNotNil(request)
XCTAssertEqual(request?.email, self.kEmail)
XCTAssertEqual(request?.password, self.kFakePassword)
try self.rpcIssuer?.respond(serverErrorMessage: "TOO_MANY_ATTEMPTS_TRY_LATER")
}
let emailCredential = EmailAuthProvider.credential(withEmail: self.kEmail,
Expand Down Expand Up @@ -1240,8 +1252,8 @@ class UserTests: RPCBaseTests {
signInWithFacebookCredential { user in
XCTAssertNotNil(user)
do {
self.setFakeGetAccountProvider(withProviderID: EmailAuthProvider.id)
self.rpcIssuer.respondBlock = {
XCTAssertNotNil(self.rpcIssuer?.request as? SignUpNewUserRequest)
try self.rpcIssuer?.respond(serverErrorMessage: "TOKEN_EXPIRED")
}
let emailCredential = EmailAuthProvider.credential(withEmail: self.kEmail,
Expand Down