mirror of
https://github.com/outbackdingo/firezone.git
synced 2026-01-27 10:18:54 +00:00
fix(Android): add alert dialog for errors in auth flow (#4835)
fixes #4723
This commit is contained in:
committed by
GitHub
parent
61a2d3b78a
commit
8f977593a4
@@ -0,0 +1,71 @@
|
||||
/* Licensed under Apache 2.0 (C) 2024 Firezone, Inc. */
|
||||
package dev.firezone.android.features.customuri.notifications
|
||||
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Context.NOTIFICATION_SERVICE
|
||||
import android.content.Intent
|
||||
import android.util.Log
|
||||
import androidx.core.app.NotificationCompat
|
||||
import dev.firezone.android.R
|
||||
import dev.firezone.android.core.presentation.MainActivity
|
||||
|
||||
object CustomUriNotification {
|
||||
private const val CHANNEL_ID = "firezone-authentication-status"
|
||||
private const val CHANNEL_NAME = "firezone-authentication-status"
|
||||
private const val CHANNEL_DESCRIPTION = "Firezone authentication status"
|
||||
const val ID = 1338
|
||||
private const val TAG: String = "CustomUriNotification"
|
||||
|
||||
fun update(
|
||||
context: Context,
|
||||
status: StatusType,
|
||||
): NotificationCompat.Builder {
|
||||
Log.d(TAG, "update")
|
||||
val manager = context.getSystemService(NOTIFICATION_SERVICE) as NotificationManager
|
||||
|
||||
val chan =
|
||||
NotificationChannel(
|
||||
CHANNEL_ID,
|
||||
CHANNEL_NAME,
|
||||
NotificationManager.IMPORTANCE_DEFAULT,
|
||||
)
|
||||
chan.description = CHANNEL_DESCRIPTION
|
||||
manager.createNotificationChannel(chan)
|
||||
|
||||
val notificationBuilder =
|
||||
NotificationCompat.Builder(context, CHANNEL_ID)
|
||||
.setContentIntent(configIntent(context))
|
||||
return status.applySettings(notificationBuilder)
|
||||
}
|
||||
|
||||
private fun configIntent(context: Context): PendingIntent {
|
||||
Log.d(TAG, "configIntent")
|
||||
val intent = Intent(context, MainActivity::class.java)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
return PendingIntent.getActivity(
|
||||
context,
|
||||
0,
|
||||
intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
|
||||
)
|
||||
}
|
||||
|
||||
data class Error(val message: String) : StatusType() {
|
||||
override fun applySettings(builder: NotificationCompat.Builder) =
|
||||
builder.apply {
|
||||
setSmallIcon(R.drawable.ic_firezone_logo)
|
||||
setContentTitle("Authentication Error")
|
||||
setContentText(message)
|
||||
setStyle(NotificationCompat.BigTextStyle().bigText(message))
|
||||
setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
setAutoCancel(true)
|
||||
}
|
||||
}
|
||||
|
||||
sealed class StatusType {
|
||||
abstract fun applySettings(builder: NotificationCompat.Builder): NotificationCompat.Builder
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
/* Licensed under Apache 2.0 (C) 2024 Firezone, Inc. */
|
||||
package dev.firezone.android.features.customuri.ui
|
||||
|
||||
import android.app.NotificationManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.activity.viewModels
|
||||
@@ -9,6 +11,7 @@ import dagger.hilt.android.AndroidEntryPoint
|
||||
import dev.firezone.android.R
|
||||
import dev.firezone.android.core.presentation.MainActivity
|
||||
import dev.firezone.android.databinding.ActivityCustomUriHandlerBinding
|
||||
import dev.firezone.android.features.customuri.notifications.CustomUriNotification
|
||||
import dev.firezone.android.tunnel.TunnelService
|
||||
|
||||
@AndroidEntryPoint
|
||||
@@ -33,6 +36,10 @@ class CustomUriHandlerActivity : AppCompatActivity(R.layout.activity_custom_uri_
|
||||
Intent(this, MainActivity::class.java),
|
||||
)
|
||||
}
|
||||
is CustomUriViewModel.ViewAction.AuthFlowError -> {
|
||||
notifyError("Errors occurred during authentication:\n${action.errors.joinToString(separator = "\n")}")
|
||||
startActivity(Intent(this, MainActivity::class.java))
|
||||
}
|
||||
else -> {
|
||||
throw IllegalStateException("Unknown action: $action")
|
||||
}
|
||||
@@ -41,4 +48,10 @@ class CustomUriHandlerActivity : AppCompatActivity(R.layout.activity_custom_uri_
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
private fun notifyError(message: String) {
|
||||
val notification = CustomUriNotification.update(this, CustomUriNotification.Error(message)).build()
|
||||
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
manager.notify(CustomUriNotification.ID, notification)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ import dev.firezone.android.core.data.Repository
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.launch
|
||||
import java.lang.IllegalStateException
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
@@ -28,39 +27,47 @@ internal class CustomUriViewModel
|
||||
|
||||
fun parseCustomUri(intent: Intent) {
|
||||
viewModelScope.launch {
|
||||
val accumulatedErrors = mutableListOf<String>()
|
||||
val error = { msg: String ->
|
||||
accumulatedErrors += msg
|
||||
Firebase.crashlytics.log(msg)
|
||||
Log.e(TAG, msg)
|
||||
}
|
||||
|
||||
when (intent.data?.host) {
|
||||
PATH_CALLBACK -> {
|
||||
intent.data?.getQueryParameter(QUERY_ACTOR_NAME)?.let { actorName ->
|
||||
Log.d("CustomUriViewModel", "Found actor name: $actorName")
|
||||
Log.d(TAG, "Found actor name: $actorName")
|
||||
repo.saveActorName(actorName).collect()
|
||||
}
|
||||
intent.data?.getQueryParameter(QUERY_CLIENT_STATE)?.let { state ->
|
||||
if (repo.validateState(state).firstOrNull() == true) {
|
||||
Log.d("CustomUriViewModel", "Valid state parameter. Continuing to save state...")
|
||||
Log.d(TAG, "Valid state parameter. Continuing to save state...")
|
||||
} else {
|
||||
throw IllegalStateException("Invalid state parameter $state! Authentication will not succeed...")
|
||||
error("Invalid state parameter $state")
|
||||
}
|
||||
intent.data?.getQueryParameter(QUERY_CLIENT_AUTH_FRAGMENT)?.let { fragment ->
|
||||
if (fragment.isNotBlank()) {
|
||||
Log.d("CustomUriViewModel", "Found valid auth fragment in response")
|
||||
}
|
||||
intent.data?.getQueryParameter(QUERY_CLIENT_AUTH_FRAGMENT)?.let { fragment ->
|
||||
if (fragment.isNotBlank()) {
|
||||
Log.d(TAG, "Found valid auth fragment in response")
|
||||
|
||||
// Save token, then clear nonce and state since we don't
|
||||
// need to keep them around anymore
|
||||
repo.saveToken(fragment).collect()
|
||||
repo.clearNonce()
|
||||
repo.clearState()
|
||||
|
||||
actionMutableLiveData.postValue(ViewAction.AuthFlowComplete)
|
||||
} else {
|
||||
throw IllegalStateException("Invalid auth fragment $fragment! Authentication will not succeed...")
|
||||
}
|
||||
// Save token, then clear nonce and state since we don't
|
||||
// need to keep them around anymore
|
||||
repo.saveToken(fragment).collect()
|
||||
repo.clearNonce()
|
||||
repo.clearState()
|
||||
} else {
|
||||
error("Auth fragment was empty")
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
Firebase.crashlytics.log("Unknown path segment: ${intent.data?.lastPathSegment}")
|
||||
Log.e("CustomUriViewModel", "Unknown path segment: ${intent.data?.lastPathSegment}")
|
||||
}
|
||||
else -> error("Unknown path segment: ${intent.data?.lastPathSegment}")
|
||||
}
|
||||
if (accumulatedErrors.isNotEmpty()) {
|
||||
actionMutableLiveData.postValue(ViewAction.AuthFlowError(accumulatedErrors))
|
||||
} else {
|
||||
Log.d(TAG, "Auth flow complete")
|
||||
actionMutableLiveData.postValue(ViewAction.AuthFlowComplete)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,9 +77,15 @@ internal class CustomUriViewModel
|
||||
private const val QUERY_CLIENT_STATE = "state"
|
||||
private const val QUERY_CLIENT_AUTH_FRAGMENT = "fragment"
|
||||
private const val QUERY_ACTOR_NAME = "actor_name"
|
||||
|
||||
private const val TAG = "CustomUriViewModel"
|
||||
}
|
||||
|
||||
internal sealed class ViewAction {
|
||||
object AuthFlowComplete : ViewAction()
|
||||
data object AuthFlowComplete : ViewAction()
|
||||
|
||||
data class AuthFlowError(val errors: Iterable<String>) : ViewAction() {
|
||||
constructor(vararg errors: String) : this(errors.toList())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user