Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import org.openedx.core.system.notifier.app.AppUpgradeEvent
import org.openedx.core.system.notifier.app.SignInEvent
import org.openedx.core.utils.Logger
import org.openedx.foundation.presentation.BaseViewModel
import org.openedx.foundation.presentation.UIMessage
import org.openedx.foundation.system.ResourceManager
import org.openedx.core.R as CoreRes

Expand Down Expand Up @@ -217,7 +218,23 @@ class SignInViewModel(
interactor.loginSocial(token, authType)
}.onFailure { error ->
logger.e { "Social login error: $error" }
onUnknownError()
if (error is EdxError.InvalidGrantException) {
// The social identity resolved on Google's side but is not linked to any
// account on this platform. The mobile token-exchange endpoint only signs in
// existing/linked users, so guide the user to register (mirrors iOS/web).
sendMessage(
UIMessage.SnackBarMessage(
resourceManager.getString(
R.string.auth_social_account_not_registered,
authType.methodName,
configuration.getPlatformName(),
)
)
)
_uiState.update { it.copy(showProgress = false) }
} else {
onUnknownError()
}
}.onSuccess {
logger.d { "Social login (${authType.methodName}) success" }
_uiState.update { it.copy(loginSuccess = true) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,20 @@ class GoogleAuthHelper(private val config: Config) {

private companion object {
const val TAG = "GoogleAuthHelper"
const val SCOPE = "oauth2: https://www.googleapis.com/auth/userinfo.email"

/**
* Scopes requested for the Google access token that is exchanged on the backend via
* `/oauth2/exchange_access_token/google-oauth2/`.
*
* Must match the scopes expected by the server-side social-auth `google-oauth2` backend
* (`DEFAULT_SCOPE = ["openid", "email", "profile"]`) and the iOS/web clients. Requesting
* only `userinfo.email` yields a token whose `userinfo` response lacks the profile/name
* claims, so first-time SSO account creation fails and the exchange returns
* `invalid_grant` ("access_token is not valid"). The `oauth2:` prefix is immediately
* followed by a space-separated scope list (no leading space).
*/
const val SCOPE = "oauth2:openid " +
"https://www.googleapis.com/auth/userinfo.email " +
"https://www.googleapis.com/auth/userinfo.profile"
}
}
1 change: 1 addition & 0 deletions auth/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<string name="auth_email_username" tools:ignore="MissingTranslation">Email or Username</string>
<string name="auth_invalid_email_username" tools:ignore="MissingTranslation">Invalid email or username</string>
<string name="auth_invalid_password">Password is too short</string>
<string name="auth_social_account_not_registered">This %1$s account is not linked with any %2$s account. Please register.</string>
<string name="auth_welcome_back">Welcome back! Sign in to access your courses.</string>
<string name="auth_show_optional_fields">Show optional fields</string>
<string name="auth_hide_optional_fields">Hide optional fields</string>
Expand Down
Loading