-
Notifications
You must be signed in to change notification settings - Fork 18
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
Add social authentication options including - Apple, Microsoft, Facebook, and Google #176
Changes from 37 commits
49e4674
87a826b
2a44cfc
9d068d1
ada811f
bf8ac9b
155455d
e51e380
3be7334
616612b
42a2ffa
b5189f9
1f07709
5324265
38d547e
4d675a9
5b36b8d
6e8f767
42b48a0
4f1c544
57a6c60
abeeaf5
2761c84
49189c7
98f5b99
a69d9b8
f2c960f
3381774
ab752e1
9085988
1f1d4de
9397651
d7dfc24
05cd91b
c7a01c6
10bf647
19bc1d0
05b5c80
c25bcf7
39ff6b3
9f000cb
a9816ef
5a0e838
c6f7f04
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,9 +9,13 @@ import Foundation | |
import Core | ||
import SwiftUI | ||
import Alamofire | ||
import AuthenticationServices | ||
import FacebookLogin | ||
import GoogleSignIn | ||
import MSAL | ||
|
||
public class SignInViewModel: ObservableObject { | ||
|
||
@Published private(set) var isShowProgress = false | ||
@Published private(set) var showError: Bool = false | ||
@Published private(set) var showAlert: Bool = false | ||
|
@@ -35,7 +39,7 @@ public class SignInViewModel: ObservableObject { | |
private let interactor: AuthInteractorProtocol | ||
private let analytics: AuthorizationAnalytics | ||
private let validator: Validator | ||
|
||
public init( | ||
interactor: AuthInteractorProtocol, | ||
router: AuthorizationRouter, | ||
|
@@ -49,15 +53,22 @@ public class SignInViewModel: ObservableObject { | |
self.analytics = analytics | ||
self.validator = validator | ||
} | ||
|
||
|
||
var socialLoginEnabled: Bool { | ||
config.appleSignIn.enabled || | ||
config.facebook.enabled || | ||
config.microsoft.enabled || | ||
config.google.enabled | ||
} | ||
|
||
@MainActor | ||
func login(username: String, password: String) async { | ||
guard validator.isValidUsername(username) else { | ||
errorMessage = AuthLocalization.Error.invalidEmailAddressOrUsername | ||
return | ||
} | ||
guard !password.isEmpty else { | ||
errorMessage = AuthLocalization.Error.invalidPasswordLength | ||
errorMessage = AuthLocalization.Error.invalidPasswordLenght | ||
return | ||
} | ||
|
||
|
@@ -68,27 +79,116 @@ public class SignInViewModel: ObservableObject { | |
analytics.userLogin(method: .password) | ||
router.showMainOrWhatsNewScreen() | ||
} catch let error { | ||
isShowProgress = false | ||
if error.isUpdateRequeiredError { | ||
router.showUpdateRequiredView(showAccountLink: false) | ||
} else if let validationError = error.validationError, | ||
let value = validationError.data?["error_description"] as? String { | ||
errorMessage = value | ||
} else if case APIError.invalidGrant = error { | ||
errorMessage = CoreLocalization.Error.invalidCredentials | ||
} else if error.isInternetError { | ||
errorMessage = CoreLocalization.Error.slowOrNoInternetConnection | ||
failure(error) | ||
} | ||
} | ||
|
||
@MainActor | ||
func sign(with result: Result<SocialAuthDetails, Error>) { | ||
result.success { social(result: $0) } | ||
result.failure { error in | ||
errorMessage = error.localizedDescription | ||
} | ||
} | ||
|
||
@MainActor | ||
private func social(result: SocialAuthDetails) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about renaming it to |
||
switch result { | ||
case .apple(let appleCredentials): | ||
appleLogin(appleCredentials, backend: result.backend) | ||
case .facebook: | ||
facebookLogin(backend: result.backend) | ||
case .google(let gIDSignInResult): | ||
googleLogin(gIDSignInResult, backend: result.backend) | ||
case .microsoft(_, let token): | ||
microsoftLogin(token, backend: result.backend) | ||
} | ||
} | ||
|
||
@MainActor | ||
private func appleLogin(_ credentials: AppleCredentials, backend: String) { | ||
socialLogin( | ||
externalToken: credentials.token, | ||
backend: backend, | ||
loginMethod: .socailAuth(.apple) | ||
) | ||
} | ||
|
||
@MainActor | ||
private func facebookLogin(backend: String) { | ||
guard let currentAccessToken = AccessToken.current?.tokenString else { | ||
return | ||
} | ||
socialLogin( | ||
externalToken: currentAccessToken, | ||
backend: backend, | ||
loginMethod: .socailAuth(.facebook) | ||
) | ||
} | ||
|
||
@MainActor | ||
private func googleLogin(_ result: GIDSignInResult, backend: String) { | ||
socialLogin( | ||
externalToken: result.user.accessToken.tokenString, | ||
backend: backend, | ||
loginMethod: .socailAuth(.google) | ||
) | ||
} | ||
|
||
@MainActor | ||
private func microsoftLogin(_ token: String, backend: String) { | ||
socialLogin( | ||
externalToken: token, | ||
backend: backend, | ||
loginMethod: .socailAuth(.microsoft) | ||
) | ||
} | ||
|
||
@MainActor | ||
private func socialLogin(externalToken: String, backend: String, loginMethod: LoginMethod) { | ||
Task { | ||
isShowProgress = true | ||
do { | ||
let user = try await interactor.login(externalToken: externalToken, backend: backend) | ||
analytics.setUserID("\(user.id)") | ||
analytics.userLogin(method: loginMethod) | ||
router.showMainOrWhatsNewScreen() | ||
} catch let error { | ||
failure(error, loginMethod: loginMethod) | ||
} | ||
} | ||
} | ||
|
||
@MainActor | ||
private func failure(_ error: Error, loginMethod: LoginMethod? = nil) { | ||
isShowProgress = false | ||
if let validationError = error.validationError, | ||
let value = validationError.data?["error_description"] as? String { | ||
if loginMethod != .password, validationError.statusCode == 400, let loginMethod = loginMethod { | ||
errorMessage = AuthLocalization.Error.authProvider( | ||
loginMethod.analyticsValue, | ||
config.platformName | ||
) | ||
} else if validationError.statusCode == 403 { | ||
errorMessage = AuthLocalization.Error.accountDisabled | ||
} else { | ||
errorMessage = CoreLocalization.Error.unknownError | ||
errorMessage = value | ||
} | ||
} else if case APIError.invalidGrant = error { | ||
errorMessage = CoreLocalization.Error.invalidCredentials | ||
} else if error.isInternetError { | ||
errorMessage = CoreLocalization.Error.slowOrNoInternetConnection | ||
} else { | ||
errorMessage = CoreLocalization.Error.unknownError | ||
} | ||
} | ||
|
||
func trackSignUpClicked() { | ||
analytics.signUpClicked() | ||
} | ||
|
||
func trackForgotPasswordClicked() { | ||
analytics.forgotPasswordClicked() | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -67,7 +67,17 @@ public struct SignUpView: View { | |
.font(Theme.Fonts.titleSmall) | ||
.foregroundColor(Theme.Colors.textPrimary) | ||
.padding(.bottom, 20) | ||
|
||
|
||
if viewModel.isThirdPartyAuthSuccess { | ||
Text(AuthLocalization.SignUp.successSigninLabel) | ||
.font(Theme.Fonts.titleMedium) | ||
.foregroundColor(Theme.Colors.textPrimary) | ||
Text(AuthLocalization.SignUp.successSigninSublabel) | ||
.font(Theme.Fonts.titleSmall) | ||
.foregroundColor(Theme.Colors.textSecondary) | ||
.padding(.bottom, 20) | ||
} | ||
|
||
let requiredFields = viewModel.fields.filter {$0.field.required} | ||
let nonRequiredFields = viewModel.fields.filter {!$0.field.required} | ||
|
||
|
@@ -98,15 +108,25 @@ public struct SignUpView: View { | |
}.frame(maxWidth: .infinity) | ||
} else { | ||
StyledButton(AuthLocalization.SignUp.createAccountBtn) { | ||
viewModel.isThirdPartyAuthSuccess = false | ||
Task { | ||
await viewModel.registerUser() | ||
} | ||
viewModel.trackCreateAccountClicked() | ||
} | ||
.padding(.top, 40) | ||
.padding(.bottom, 80) | ||
.frame(maxWidth: .infinity) | ||
} | ||
if viewModel.socialLoginEnabled, | ||
!requiredFields.isEmpty { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What are we achieving with this where clause? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. when we open sign up screen, make request to get fields and I show only when requiredFields come |
||
SocialAuthView( | ||
signType: .register, | ||
viewModel: .init( | ||
config: viewModel.config | ||
) { viewModel.register(with: $0) } | ||
) | ||
.padding(.bottom, 30) | ||
} | ||
Spacer() | ||
} | ||
.padding(.horizontal, 24) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about renaming it to
login(with .....)