Skip to content
Open
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 @@ -33,11 +33,20 @@ import androidx.credentials.GetCredentialResponse
import androidx.credentials.GetPasswordOption
import androidx.credentials.GetPublicKeyCredentialOption
import androidx.credentials.GetRestoreCredentialOption
import androidx.credentials.SignalAllAcceptedCredentialIdsRequest
import androidx.credentials.SignalCurrentUserDetailsRequest
import androidx.credentials.SignalUnknownCredentialRequest
import androidx.credentials.exceptions.CreateCredentialException
import androidx.credentials.exceptions.GetCredentialCancellationException
import androidx.credentials.exceptions.publickeycredential.GetPublicKeyCredentialDomException
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import com.authentication.shrine.repository.AuthRepository.Companion.RP_ID_KEY
import com.authentication.shrine.repository.AuthRepository.Companion.USER_ID_KEY
import com.authentication.shrine.repository.AuthRepository.Companion.read
import com.authentication.shrine.repository.SERVER_CLIENT_ID
import com.google.android.libraries.identity.googleid.GetGoogleIdOption
import org.json.JSONArray
import org.json.JSONObject
import javax.inject.Inject

Expand All @@ -48,6 +57,7 @@ import javax.inject.Inject
*/
class CredentialManagerUtils @Inject constructor(
private val credentialManager: CredentialManager,
private val dataStore: DataStore<Preferences>,
) {

/**
Expand Down Expand Up @@ -108,7 +118,7 @@ class CredentialManagerUtils @Inject constructor(
.setServerClientId(SERVER_CLIENT_ID)
.setFilterByAuthorizedAccounts(false)
.build(),
)
),
)
result = credentialManager.getCredential(context, credentialRequest)
} catch (e: GetCredentialCancellationException) {
Expand Down Expand Up @@ -249,6 +259,52 @@ class CredentialManagerUtils @Inject constructor(
val clearRequest = ClearCredentialStateRequest(requestType = TYPE_CLEAR_RESTORE_CREDENTIAL)
credentialManager.clearCredentialState(clearRequest)
}

@SuppressLint("RestrictedApi")
suspend fun signalUnknown(
credentialId: String,
) {
credentialManager.signalCredentialState(
request = SignalUnknownCredentialRequest(
requestJson = JSONObject().apply {
put("rpId", dataStore.read(RP_ID_KEY))
put("credentialId", credentialId)
}.toString()
),
)
}

@SuppressLint("RestrictedApi")
suspend fun signalAcceptedIds(
credentialIds: List<String>,
) {
credentialManager.signalCredentialState(
request = SignalAllAcceptedCredentialIdsRequest(
requestJson = JSONObject().apply {
put("rpId", dataStore.read(RP_ID_KEY))
put("userId", dataStore.read(USER_ID_KEY))
put("allAcceptedCredentialIds", JSONArray(credentialIds))
}.toString()
),
)
}

@SuppressLint("RestrictedApi")
suspend fun signalUserDetails(
newName: String,
newDisplayName: String,
) {
credentialManager.signalCredentialState(
request = SignalCurrentUserDetailsRequest(
requestJson = JSONObject().apply {
put("rpId", dataStore.read(RP_ID_KEY))
put("userId", dataStore.read(USER_ID_KEY))
put("name", newName)
put("displayName", newDisplayName)
}.toString()
),
)
}
}

sealed class GenericCredentialManagerResponse {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,12 @@ object AppModule {
@Provides
fun providesCredentialManagerUtils(
credentialManager: CredentialManager,
dataStore: DataStore<Preferences>,
): CredentialManagerUtils {
return CredentialManagerUtils(credentialManager)
return CredentialManagerUtils(
credentialManager = credentialManager,
dataStore = dataStore,
)
}

@Singleton
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ interface AuthApiService {
*/
@POST("federation/options")
suspend fun getFederationOptions(
@Body urls: FederationOptionsRequest
@Body urls: FederationOptionsRequest,
): Response<GenericAuthResponse>

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,24 @@
/*
* Copyright 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.authentication.shrine.model

/**
* Represents the request body for getting federation options from the server.
* @param urls a list of urls to send for federated requests.
*/
data class FederationOptionsRequest(
val urls: List<String> = listOf("https://accounts.google.com")
)
val urls: List<String> = listOf("https://accounts.google.com"),
)
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,5 @@ data class PasskeyCredential(
val aaguid: String,
val registeredAt: Long,
val providerIcon: String,
val isSelected: Boolean = false,
)
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
/*
* Copyright 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.authentication.shrine.model

/**
Expand All @@ -7,5 +22,5 @@ package com.authentication.shrine.model
*/
data class SignInWithGoogleRequest(
val token: String,
val url: String = "https://accounts.google.com"
)
val url: String = "https://accounts.google.com",
)
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ class AuthRepository @Inject constructor(
val USERNAME = stringPreferencesKey("username")
val IS_SIGNED_IN_THROUGH_PASSKEYS = booleanPreferencesKey("is_signed_passkeys")
val SESSION_ID = stringPreferencesKey("session_id")
val RP_ID_KEY = stringPreferencesKey("rp_id_key")
val USER_ID_KEY = stringPreferencesKey("user_id_key")
val CRED_ID = stringPreferencesKey("cred_id")
val RESTORE_KEY_CREDENTIAL_ID = stringPreferencesKey("restore_key_credential_id")

// Value for restore credential AuthApiService parameter
Expand Down Expand Up @@ -151,7 +154,6 @@ class AuthRepository @Inject constructor(
}
}


/**
* Signs in with a password.
*
Expand Down Expand Up @@ -217,6 +219,7 @@ class AuthRepository @Inject constructor(
)
if (response.isSuccessful) {
dataStore.edit { prefs ->
prefs[RP_ID_KEY] = response.body()?.rp?.id ?: ""
response.getSessionId()?.also {
prefs[SESSION_ID] = it
}
Expand Down Expand Up @@ -287,6 +290,7 @@ class AuthRepository @Inject constructor(
)
if (apiResult.isSuccessful) {
dataStore.edit { prefs ->
prefs[CRED_ID] = rawId
if (credentialResponse is CreateRestoreCredentialResponse) {
prefs[RESTORE_KEY_CREDENTIAL_ID] = rawId
}
Expand Down Expand Up @@ -321,6 +325,7 @@ class AuthRepository @Inject constructor(
val response = authApiService.signInRequest()
if (response.isSuccessful) {
dataStore.edit { prefs ->
prefs[RP_ID_KEY] = response.body()?.rpId ?: ""
response.getSessionId()?.also {
prefs[SESSION_ID] = it
}
Expand Down Expand Up @@ -382,6 +387,7 @@ class AuthRepository @Inject constructor(
)
return if (apiResult.isSuccessful) {
dataStore.edit { prefs ->
prefs[CRED_ID] = credentialId
apiResult.getSessionId()?.also {
prefs[SESSION_ID] = it
}
Expand Down Expand Up @@ -421,15 +427,22 @@ class AuthRepository @Inject constructor(
*/
suspend fun signInWithFederatedTokenResponse(
sessionId: String,
<<<<<<< HEAD
credentialResponse: GetCredentialResponse
): AuthResult<Unit> {
return try {
val credential = credentialResponse.credential
=======
credentialResponse: GetCredentialResponse,
): Boolean {
val credential = credentialResponse.credential
try {
>>>>>>> b12d692 (Add spotless fixes)
if (credential is CustomCredential) {
val isSuccess = verifyIdToken(
sessionId,
GoogleIdTokenCredential
.createFrom(credential.data).idToken
.createFrom(credential.data).idToken,
)
if (isSuccess) {
AuthResult.Success(Unit)
Expand Down Expand Up @@ -545,6 +558,9 @@ class AuthRepository @Inject constructor(
cookie = sessionId.createCookieHeader(),
)
if (apiResult.isSuccessful) {
dataStore.edit { prefs ->
prefs[USER_ID_KEY] = apiResult.body()?.userId ?: ""
}
return apiResult.body()
} else if (apiResult.code() == 401) {
signOut()
Expand Down Expand Up @@ -634,7 +650,7 @@ class AuthRepository @Inject constructor(
suspend fun verifyIdToken(sessionId: String, token: String): Boolean {
val apiResult = authApiService.verifyIdToken(
cookie = sessionId.createCookieHeader(),
requestParams = SignInWithGoogleRequest(token = token)
requestParams = SignInWithGoogleRequest(token = token),
)

if (apiResult.isSuccessful) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ fun AuthenticationScreen(
isButtonEnabled = !uiState.isLoading,
)

<<<<<<< HEAD
// Sign in with Google image
Image(
painter = painterResource(id = R.drawable.siwg_button_light),
Expand All @@ -201,6 +202,19 @@ fun AuthenticationScreen(
onClick = onSignInWithSignInWithGoogleRequest)
)
}
=======
// Sign in with Google image
Image(
painter = painterResource(id = R.drawable.siwg_button_light),
contentDescription = stringResource(R.string.sign_in_with_google_button),
modifier = Modifier
.height(dimensionResource(R.dimen.siwg_button_height))
.clickable(
enabled = !uiState.isLoading,
onClick = onSignInWithSignInWithGoogleRequest,
),
)
>>>>>>> b12d692 (Add spotless fixes)
}

if (uiState.isLoading) {
Expand Down Expand Up @@ -229,7 +243,7 @@ fun AuthenticationScreen(
if (snackbarMessage != null) {
LaunchedEffect(snackbarMessage) {
snackbarHostState.showSnackbar(
message = snackbarMessage
message = snackbarMessage,
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ fun MainMenuScreen(
onSettingsButtonClicked: () -> Unit,
onHelpButtonClicked: () -> Unit,
navigateToLogin: () -> Unit,
navigateToUpdateProfile: () -> Unit,
viewModel: HomeViewModel,
modifier: Modifier = Modifier,
credentialManagerUtils: CredentialManagerUtils,
Expand All @@ -71,6 +72,7 @@ fun MainMenuScreen(
onHelpButtonClicked = onHelpButtonClicked,
navigateToLogin = navigateToLogin,
onSignOut = onSignOut,
navigateToUpdateProfile = navigateToUpdateProfile,
modifier = modifier,
)
}
Expand All @@ -93,6 +95,7 @@ fun MainMenuScreen(
onSettingsButtonClicked: () -> Unit,
onHelpButtonClicked: () -> Unit,
navigateToLogin: () -> Unit,
navigateToUpdateProfile: () -> Unit,
onSignOut: () -> Unit,
modifier: Modifier = Modifier,
) {
Expand Down Expand Up @@ -121,6 +124,7 @@ fun MainMenuScreen(
onHelpButtonClicked,
onSignOut,
navigateToLogin,
navigateToUpdateProfile,
)
}
}
Expand All @@ -142,6 +146,7 @@ private fun MainMenuButtonsList(
onHelpButtonClicked: () -> Unit,
onSignOut: () -> Unit,
navigateToLogin: () -> Unit,
navigateToUpdateProfile: () -> Unit,
modifier: Modifier = Modifier,
) {
Column(
Expand Down Expand Up @@ -172,6 +177,11 @@ private fun MainMenuButtonsList(
buttonText = stringResource(R.string.sign_out),
usePrimaryColor = false,
)

ShrineButton(
onClick = navigateToUpdateProfile,
buttonText = stringResource(R.string.update_profile),
)
}
}

Expand All @@ -188,6 +198,7 @@ fun PasskeysSignedPreview() {
onHelpButtonClicked = { },
navigateToLogin = { },
onSignOut = { },
navigateToUpdateProfile = { },
)
}
}
Loading