Skip to content

Commit b0b7bfb

Browse files
committed
feat: Support for login and registration via a browser custom tab
This change adds support for logging in and registering a new account using the browser. This can be useful for cases where the only way to log into the instatance is via a custom third-party auth provider.
1 parent 591932d commit b0b7bfb

21 files changed

Lines changed: 238 additions & 33 deletions

File tree

app/src/main/AndroidManifest.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@
3636

3737
<category android:name="android.intent.category.LAUNCHER" />
3838
</intent-filter>
39+
<intent-filter>
40+
<action android:name="android.intent.action.VIEW" />
41+
<category android:name="android.intent.category.DEFAULT" />
42+
<category android:name="android.intent.category.BROWSABLE" />
43+
<data android:scheme="${applicationId}" />
44+
</intent-filter>
3945
</activity>
4046

4147
<provider

app/src/main/java/org/openedx/app/AppActivity.kt

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package org.openedx.app
22

33
import android.content.res.Configuration
44
import android.graphics.Color
5+
import android.net.Uri
56
import android.os.Bundle
67
import android.view.View
78
import android.view.WindowManager
@@ -50,6 +51,14 @@ class AppActivity : AppCompatActivity(), InsetHolder, WindowSizeHolder {
5051
private var _insetCutout = 0
5152

5253
private var _windowSize = WindowSize(WindowType.Compact, WindowType.Compact)
54+
private val authCode: String?
55+
get() {
56+
val data = intent?.data
57+
if (data is Uri && data.scheme == BuildConfig.APPLICATION_ID && data.host == "oauth2Callback") {
58+
return data.getQueryParameter("code")
59+
}
60+
return null
61+
}
5362

5463
override fun onSaveInstanceState(outState: Bundle) {
5564
outState.putInt(TOP_INSET, topInset)
@@ -112,10 +121,15 @@ class AppActivity : AppCompatActivity(), InsetHolder, WindowSizeHolder {
112121
if (savedInstanceState == null) {
113122
when {
114123
corePreferencesManager.user == null -> {
115-
if (viewModel.isLogistrationEnabled) {
124+
val authCode = authCode;
125+
if (viewModel.isLogistrationEnabled && authCode == null) {
116126
addFragment(LogistrationFragment())
117127
} else {
118-
addFragment(SignInFragment())
128+
val bundle = Bundle()
129+
bundle.putString("auth_code", authCode)
130+
val fragment = SignInFragment()
131+
fragment.arguments = bundle
132+
addFragment(fragment)
119133
}
120134
}
121135

app/src/main/java/org/openedx/app/di/AppModule.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import org.openedx.app.room.DATABASE_NAME
2020
import org.openedx.app.system.notifier.AppNotifier
2121
import org.openedx.auth.presentation.AuthAnalytics
2222
import org.openedx.auth.presentation.AuthRouter
23+
import org.openedx.auth.presentation.sso.BrowserAuthHelper
2324
import org.openedx.auth.presentation.sso.FacebookAuthHelper
2425
import org.openedx.auth.presentation.sso.GoogleAuthHelper
2526
import org.openedx.auth.presentation.sso.MicrosoftAuthHelper
@@ -153,4 +154,5 @@ val appModule = module {
153154
factory { FacebookAuthHelper() }
154155
factory { GoogleAuthHelper(get()) }
155156
factory { MicrosoftAuthHelper() }
157+
factory { BrowserAuthHelper(get()) }
156158
}

app/src/main/java/org/openedx/app/di/ScreenModule.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ val screenModule = module {
7474
get(),
7575
get(),
7676
get(),
77+
get(),
7778
courseId,
7879
)
7980
}

auth/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ android {
5555
dependencies {
5656
implementation project(path: ':core')
5757

58+
implementation 'androidx.browser:browser:1.7.0'
5859
implementation "androidx.credentials:credentials:1.2.0"
5960
implementation "androidx.credentials:credentials-play-services-auth:1.2.0"
6061
implementation "com.facebook.android:facebook-login:16.2.0"

auth/src/main/java/org/openedx/auth/data/api/AuthApi.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,14 @@ interface AuthApi {
3232
@Field("asymmetric_jwt") isAsymmetricJwt: Boolean = true,
3333
): AuthResponse
3434

35+
@FormUrlEncoded
36+
@POST(ApiConstants.URL_ACCESS_TOKEN)
37+
suspend fun getAccessTokenFromCode(
38+
@Field("grant_type") grantType: String,
39+
@Field("client_id") clientId: String,
40+
@Field("code") code: String,
41+
): AuthResponse
42+
3543
@FormUrlEncoded
3644
@POST(ApiConstants.URL_ACCESS_TOKEN)
3745
fun refreshAccessToken(

auth/src/main/java/org/openedx/auth/data/model/AuthType.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@ enum class AuthType(val postfix: String, val methodName: String) {
1313
GOOGLE(ApiConstants.AUTH_TYPE_GOOGLE, "Google"),
1414
FACEBOOK(ApiConstants.AUTH_TYPE_FB, "Facebook"),
1515
MICROSOFT(ApiConstants.AUTH_TYPE_MICROSOFT, "Microsoft"),
16+
BROWSER(ApiConstants.AUTH_TYPE_BROWSER, "Browser")
1617
}

auth/src/main/java/org/openedx/auth/data/repository/AuthRepository.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.openedx.auth.data.repository
22

3+
import android.util.Log
34
import org.openedx.auth.data.api.AuthApi
45
import org.openedx.auth.data.model.AuthType
56
import org.openedx.auth.data.model.ValidationFields
@@ -43,6 +44,14 @@ class AuthRepository(
4344
.processAuthResponse()
4445
}
4546

47+
suspend fun browserAuthCodeLogin(code: String) {
48+
api.getAccessTokenFromCode(
49+
grantType = ApiConstants.GRANT_TYPE_CODE,
50+
clientId = config.getOAuthClientId(),
51+
code = code,
52+
).mapToDomain().processAuthResponse()
53+
}
54+
4655
suspend fun getRegistrationFields(): List<RegistrationField> {
4756
return api.getRegistrationFields().fields?.map { it.mapToDomain() } ?: emptyList()
4857
}

auth/src/main/java/org/openedx/auth/domain/interactor/AuthInteractor.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ class AuthInteractor(private val repository: AuthRepository) {
1818
repository.socialLogin(token, authType)
1919
}
2020

21+
suspend fun loginAuthCode(authCode: String) {
22+
repository.browserAuthCodeLogin(authCode)
23+
}
24+
2125
suspend fun getRegistrationFields(): List<RegistrationField> {
2226
return repository.getRegistrationFields()
2327
}

auth/src/main/java/org/openedx/auth/presentation/logistration/LogistrationFragment.kt

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package org.openedx.auth.presentation.logistration
22

3+
import android.content.Intent
34
import android.content.res.Configuration
5+
import android.net.Uri
46
import android.os.Bundle
57
import android.view.LayoutInflater
68
import android.view.ViewGroup
@@ -41,18 +43,22 @@ import androidx.fragment.app.Fragment
4143
import org.koin.android.ext.android.inject
4244
import org.openedx.auth.R
4345
import org.openedx.auth.presentation.AuthRouter
46+
import org.openedx.core.config.Config
47+
import org.openedx.core.presentation.dialog.alert.ActionDialogFragment
4448
import org.openedx.core.ui.AuthButtonsPanel
4549
import org.openedx.core.ui.SearchBar
4650
import org.openedx.core.ui.displayCutoutForLandscape
4751
import org.openedx.core.ui.noRippleClickable
4852
import org.openedx.core.ui.theme.OpenEdXTheme
4953
import org.openedx.core.ui.theme.appColors
5054
import org.openedx.core.ui.theme.appTypography
55+
import org.openedx.core.utils.UrlUtils
5156
import org.openedx.core.R as coreR
5257

5358
class LogistrationFragment : Fragment() {
5459

5560
private val router: AuthRouter by inject()
61+
private val config: Config by inject()
5662

5763
override fun onCreateView(
5864
inflater: LayoutInflater,
@@ -68,7 +74,15 @@ class LogistrationFragment : Fragment() {
6874
router.navigateToSignIn(parentFragmentManager, courseId)
6975
},
7076
onRegisterClick = {
71-
router.navigateToSignUp(parentFragmentManager, courseId)
77+
if (config.isBrowserRegistrationEnabled()) {
78+
UrlUtils.openInBrowser(
79+
activity = context,
80+
apiHostUrl = config.getApiHostURL(),
81+
url = "/register",
82+
)
83+
} else {
84+
router.navigateToSignUp(parentFragmentManager, courseId)
85+
}
7286
},
7387
onSearchClick = { querySearch ->
7488
router.navigateToDiscoverCourses(parentFragmentManager, querySearch)

0 commit comments

Comments
 (0)