mirror of
https://github.com/Yubico/yubioath-flutter.git
synced 2024-12-23 18:22:39 +03:00
examples
This commit is contained in:
parent
a9a5532067
commit
ff8f2c8ce0
@ -32,6 +32,22 @@ import kotlin.coroutines.resume
|
|||||||
import kotlin.coroutines.resumeWithException
|
import kotlin.coroutines.resumeWithException
|
||||||
import kotlin.coroutines.suspendCoroutine
|
import kotlin.coroutines.suspendCoroutine
|
||||||
|
|
||||||
|
interface JsonSerializable {
|
||||||
|
fun toJson() : String
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed interface SessionState {
|
||||||
|
data object Empty : SessionState
|
||||||
|
data object Loading : SessionState
|
||||||
|
data class Value<T : JsonSerializable>(val data: T) : SessionState
|
||||||
|
}
|
||||||
|
|
||||||
|
data class ChannelData<T>(val data: T?, val isLoading: Boolean = false) {
|
||||||
|
companion object {
|
||||||
|
fun <T> loading() = ChannelData<T>(null, true)
|
||||||
|
fun <T> empty() = ChannelData<T>(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Observes a LiveData value, sending each change to Flutter via an EventChannel.
|
* Observes a LiveData value, sending each change to Flutter via an EventChannel.
|
||||||
*/
|
*/
|
||||||
@ -61,6 +77,83 @@ inline fun <reified T> LiveData<T>.streamTo(lifecycleOwner: LifecycleOwner, mess
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observes a Loadable LiveData value, sending each change to Flutter via an EventChannel.
|
||||||
|
*/
|
||||||
|
inline fun <reified T> LiveData<ChannelData<T>>.streamData(lifecycleOwner: LifecycleOwner, messenger: BinaryMessenger, channelName: String): Closeable {
|
||||||
|
val channel = EventChannel(messenger, channelName)
|
||||||
|
var sink: EventChannel.EventSink? = null
|
||||||
|
|
||||||
|
channel.setStreamHandler(object : EventChannel.StreamHandler {
|
||||||
|
override fun onListen(arguments: Any?, events: EventChannel.EventSink) {
|
||||||
|
sink = events
|
||||||
|
events.success(
|
||||||
|
value?.let {
|
||||||
|
if (it.isLoading) LOADING
|
||||||
|
else it.data?.let(jsonSerializer::encodeToString) ?: NULL
|
||||||
|
} ?: NULL
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCancel(arguments: Any?) {
|
||||||
|
sink = null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
val observer = Observer<ChannelData<T>> {
|
||||||
|
sink?.success(
|
||||||
|
if (it.isLoading) LOADING
|
||||||
|
else it.data?.let(jsonSerializer::encodeToString) ?: NULL
|
||||||
|
)
|
||||||
|
}
|
||||||
|
observe(lifecycleOwner, observer)
|
||||||
|
|
||||||
|
return Closeable {
|
||||||
|
removeObserver(observer)
|
||||||
|
channel.setStreamHandler(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun get(state: SessionState) : String = when (state) {
|
||||||
|
is SessionState.Empty -> NULL
|
||||||
|
is SessionState.Loading -> LOADING
|
||||||
|
is SessionState.Value<*> -> state.data.toJson()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observes a Loadable LiveData value, sending each change to Flutter via an EventChannel.
|
||||||
|
*/
|
||||||
|
inline fun <reified T : SessionState> LiveData<T>.streamState(lifecycleOwner: LifecycleOwner, messenger: BinaryMessenger, channelName: String): Closeable {
|
||||||
|
val channel = EventChannel(messenger, channelName)
|
||||||
|
var sink: EventChannel.EventSink? = null
|
||||||
|
|
||||||
|
channel.setStreamHandler(object : EventChannel.StreamHandler {
|
||||||
|
override fun onListen(arguments: Any?, events: EventChannel.EventSink) {
|
||||||
|
sink = events
|
||||||
|
events.success(
|
||||||
|
value?.let {
|
||||||
|
get(it)
|
||||||
|
} ?: NULL
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCancel(arguments: Any?) {
|
||||||
|
sink = null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
val observer = Observer<T> {
|
||||||
|
sink?.success(get(it))
|
||||||
|
}
|
||||||
|
observe(lifecycleOwner, observer)
|
||||||
|
|
||||||
|
return Closeable {
|
||||||
|
removeObserver(observer)
|
||||||
|
channel.setStreamHandler(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
typealias MethodHandler = suspend (method: String, args: Map<String, Any?>) -> String
|
typealias MethodHandler = suspend (method: String, args: Map<String, Any?>) -> String
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -20,6 +20,8 @@ import kotlinx.serialization.json.Json
|
|||||||
|
|
||||||
const val NULL = "null"
|
const val NULL = "null"
|
||||||
|
|
||||||
|
const val LOADING = """{ "loading": true }"""
|
||||||
|
|
||||||
val jsonSerializer = Json {
|
val jsonSerializer = Json {
|
||||||
// creates properties for default values
|
// creates properties for default values
|
||||||
encodeDefaults = true
|
encodeDefaults = true
|
||||||
|
@ -320,10 +320,10 @@ class MainActivity : FlutterFragmentActivity() {
|
|||||||
|
|
||||||
flutterStreams = listOf(
|
flutterStreams = listOf(
|
||||||
viewModel.deviceInfo.streamTo(this, messenger, "android.devices.deviceInfo"),
|
viewModel.deviceInfo.streamTo(this, messenger, "android.devices.deviceInfo"),
|
||||||
oathViewModel.sessionState.streamTo(this, messenger, "android.oath.sessionState"),
|
oathViewModel.sessionState.streamState(this, messenger, "android.oath.sessionState"),
|
||||||
oathViewModel.credentials.streamTo(this, messenger, "android.oath.credentials"),
|
oathViewModel.credentials.streamTo(this, messenger, "android.oath.credentials"),
|
||||||
fidoViewModel.sessionState.streamTo(this, messenger, "android.fido.sessionState"),
|
fidoViewModel.sessionState.streamData(this, messenger, "android.fido.sessionState"),
|
||||||
fidoViewModel.credentials.streamTo(this, messenger, "android.fido.credentials"),
|
fidoViewModel.credentials.streamData(this, messenger, "android.fido.credentials"),
|
||||||
fidoViewModel.resetState.streamTo(this, messenger, "android.fido.reset"),
|
fidoViewModel.resetState.streamTo(this, messenger, "android.fido.reset"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -140,7 +140,7 @@ class FidoManager(
|
|||||||
if (!deviceManager.isUsbKeyConnected()) {
|
if (!deviceManager.isUsbKeyConnected()) {
|
||||||
// for NFC connections require extra tap when switching context
|
// for NFC connections require extra tap when switching context
|
||||||
if (fidoViewModel.sessionState.value == null) {
|
if (fidoViewModel.sessionState.value == null) {
|
||||||
fidoViewModel.setSessionState(Session.uninitialized)
|
fidoViewModel.clearSessionState()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,8 +150,8 @@ class FidoManager(
|
|||||||
super.dispose()
|
super.dispose()
|
||||||
deviceManager.removeDeviceListener(this)
|
deviceManager.removeDeviceListener(this)
|
||||||
fidoChannel.setMethodCallHandler(null)
|
fidoChannel.setMethodCallHandler(null)
|
||||||
fidoViewModel.setSessionState(null)
|
fidoViewModel.clearSessionState()
|
||||||
fidoViewModel.updateCredentials(listOf())
|
fidoViewModel.clearCredentials()
|
||||||
coroutineScope.cancel()
|
coroutineScope.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -185,7 +185,7 @@ class FidoManager(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Clear any cached FIDO state
|
// Clear any cached FIDO state
|
||||||
fidoViewModel.setSessionState(null)
|
fidoViewModel.clearSessionState()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -198,7 +198,7 @@ class FidoManager(
|
|||||||
YubiKitFidoSession(connection as SmartCardConnection)
|
YubiKitFidoSession(connection as SmartCardConnection)
|
||||||
}
|
}
|
||||||
|
|
||||||
val previousSession = fidoViewModel.sessionState.value?.info
|
val previousSession = fidoViewModel.sessionState.value?.data?.info
|
||||||
val currentSession = SessionInfo(fidoSession.cachedInfo)
|
val currentSession = SessionInfo(fidoSession.cachedInfo)
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"Previous session: {}, current session: {}",
|
"Previous session: {}, current session: {}",
|
||||||
@ -253,6 +253,8 @@ class FidoManager(
|
|||||||
pin: CharArray
|
pin: CharArray
|
||||||
): String {
|
): String {
|
||||||
|
|
||||||
|
fidoViewModel.setSessionLoadingState()
|
||||||
|
|
||||||
val permissions = getPermissions(fidoSession)
|
val permissions = getPermissions(fidoSession)
|
||||||
|
|
||||||
if (permissions != 0) {
|
if (permissions != 0) {
|
||||||
@ -260,7 +262,6 @@ class FidoManager(
|
|||||||
val credentials = getCredentials(fidoSession, clientPin, token)
|
val credentials = getCredentials(fidoSession, clientPin, token)
|
||||||
logger.debug("Creds: {}", credentials)
|
logger.debug("Creds: {}", credentials)
|
||||||
fidoViewModel.updateCredentials(credentials)
|
fidoViewModel.updateCredentials(credentials)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
clientPin.getPinToken(pin, permissions, "yubico-authenticator.example.com")
|
clientPin.getPinToken(pin, permissions, "yubico-authenticator.example.com")
|
||||||
}
|
}
|
||||||
@ -285,7 +286,7 @@ class FidoManager(
|
|||||||
ctapException.ctapError == CtapException.ERR_PIN_AUTH_BLOCKED
|
ctapException.ctapError == CtapException.ERR_PIN_AUTH_BLOCKED
|
||||||
) {
|
) {
|
||||||
pinStore.setPin(null)
|
pinStore.setPin(null)
|
||||||
fidoViewModel.updateCredentials(emptyList())
|
fidoViewModel.clearCredentials()
|
||||||
val pinRetriesResult = clientPin.pinRetries
|
val pinRetriesResult = clientPin.pinRetries
|
||||||
JSONObject(
|
JSONObject(
|
||||||
mapOf(
|
mapOf(
|
||||||
@ -355,6 +356,9 @@ class FidoManager(
|
|||||||
pinUvAuthToken: ByteArray
|
pinUvAuthToken: ByteArray
|
||||||
): List<FidoCredential> =
|
): List<FidoCredential> =
|
||||||
try {
|
try {
|
||||||
|
|
||||||
|
fidoViewModel.setCredentialsLoadingState()
|
||||||
|
|
||||||
val credMan = CredentialManagement(fidoSession, clientPin.pinUvAuth, pinUvAuthToken)
|
val credMan = CredentialManagement(fidoSession, clientPin.pinUvAuth, pinUvAuthToken)
|
||||||
val rpIds = credMan.enumerateRps()
|
val rpIds = credMan.enumerateRps()
|
||||||
|
|
||||||
@ -390,7 +394,7 @@ class FidoManager(
|
|||||||
val credMan = CredentialManagement(fidoSession, clientPin.pinUvAuth, token)
|
val credMan = CredentialManagement(fidoSession, clientPin.pinUvAuth, token)
|
||||||
|
|
||||||
val credentialDescriptor =
|
val credentialDescriptor =
|
||||||
fidoViewModel.credentials.value?.firstOrNull {
|
fidoViewModel.credentials.value?.data?.firstOrNull {
|
||||||
it.credentialId == credentialId && it.rpId == rpId
|
it.credentialId == credentialId && it.rpId == rpId
|
||||||
}?.publicKeyCredentialDescriptor
|
}?.publicKeyCredentialDescriptor
|
||||||
|
|
||||||
@ -414,11 +418,11 @@ class FidoManager(
|
|||||||
|
|
||||||
override fun onDisconnected() {
|
override fun onDisconnected() {
|
||||||
if (!resetHelper.inProgress) {
|
if (!resetHelper.inProgress) {
|
||||||
fidoViewModel.setSessionState(null)
|
fidoViewModel.clearSessionState()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onTimeout() {
|
override fun onTimeout() {
|
||||||
fidoViewModel.setSessionState(null)
|
fidoViewModel.clearSessionState()
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -202,7 +202,7 @@ class FidoResetHelper(
|
|||||||
logger.debug("Calling FIDO reset")
|
logger.debug("Calling FIDO reset")
|
||||||
fidoSession.reset(resetCommandState)
|
fidoSession.reset(resetCommandState)
|
||||||
fidoViewModel.setSessionState(Session(fidoSession.info, true))
|
fidoViewModel.setSessionState(Session(fidoSession.info, true))
|
||||||
fidoViewModel.updateCredentials(emptyList())
|
fidoViewModel.clearCredentials()
|
||||||
pinStore.setPin(null)
|
pinStore.setPin(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,28 +19,45 @@ package com.yubico.authenticator.fido
|
|||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
|
import com.yubico.authenticator.ChannelData
|
||||||
import com.yubico.authenticator.fido.data.FidoCredential
|
import com.yubico.authenticator.fido.data.FidoCredential
|
||||||
import com.yubico.authenticator.fido.data.Session
|
import com.yubico.authenticator.fido.data.Session
|
||||||
|
|
||||||
class FidoViewModel : ViewModel() {
|
class FidoViewModel : ViewModel() {
|
||||||
private val _sessionState = MutableLiveData<Session?>(null)
|
private val _sessionState = MutableLiveData<ChannelData<Session?>>()
|
||||||
val sessionState: LiveData<Session?> = _sessionState
|
val sessionState: LiveData<ChannelData<Session?>> = _sessionState
|
||||||
|
|
||||||
fun setSessionState(sessionState: Session?) {
|
fun setSessionState(sessionState: Session?) {
|
||||||
_sessionState.postValue(sessionState)
|
_sessionState.postValue(ChannelData(sessionState))
|
||||||
}
|
}
|
||||||
|
|
||||||
private val _credentials = MutableLiveData<List<FidoCredential>>()
|
fun clearSessionState() {
|
||||||
val credentials: LiveData<List<FidoCredential>> = _credentials
|
_sessionState.postValue(ChannelData.empty())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setSessionLoadingState() {
|
||||||
|
_sessionState.postValue(ChannelData.loading())
|
||||||
|
}
|
||||||
|
|
||||||
|
private val _credentials = MutableLiveData<ChannelData<List<FidoCredential>?>>()
|
||||||
|
val credentials: LiveData<ChannelData<List<FidoCredential>?>> = _credentials
|
||||||
|
|
||||||
|
fun setCredentialsLoadingState() {
|
||||||
|
_credentials.postValue(ChannelData.loading())
|
||||||
|
}
|
||||||
|
|
||||||
fun updateCredentials(credentials: List<FidoCredential>) {
|
fun updateCredentials(credentials: List<FidoCredential>) {
|
||||||
_credentials.postValue(credentials)
|
_credentials.postValue(ChannelData(credentials))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearCredentials() {
|
||||||
|
_credentials.postValue(ChannelData.empty())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeCredential(rpId: String, credentialId: String) {
|
fun removeCredential(rpId: String, credentialId: String) {
|
||||||
_credentials.postValue(_credentials.value?.filter {
|
_credentials.postValue(ChannelData(_credentials.value?.data?.filter {
|
||||||
it.credentialId != credentialId || it.rpId != rpId
|
it.credentialId != credentialId || it.rpId != rpId
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
private val _resetState = MutableLiveData(FidoResetState.Remove.value)
|
private val _resetState = MutableLiveData(FidoResetState.Remove.value)
|
||||||
|
@ -86,30 +86,9 @@ data class SessionInfo(
|
|||||||
data class Session(
|
data class Session(
|
||||||
@SerialName("info")
|
@SerialName("info")
|
||||||
val info: SessionInfo,
|
val info: SessionInfo,
|
||||||
val unlocked: Boolean,
|
val unlocked: Boolean
|
||||||
val initialized: Boolean
|
|
||||||
) {
|
) {
|
||||||
constructor(infoData: InfoData, unlocked: Boolean) : this(
|
constructor(infoData: InfoData, unlocked: Boolean) : this(
|
||||||
SessionInfo(infoData), unlocked, true
|
SessionInfo(infoData), unlocked
|
||||||
)
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
val uninitialized = Session(
|
|
||||||
SessionInfo(
|
|
||||||
Options(
|
|
||||||
clientPin = false,
|
|
||||||
credMgmt = false,
|
|
||||||
credentialMgmtPreview = false,
|
|
||||||
bioEnroll = null,
|
|
||||||
alwaysUv = false
|
|
||||||
),
|
|
||||||
aaguid = ByteArray(0),
|
|
||||||
minPinLength = 0,
|
|
||||||
forcePinChange = false
|
|
||||||
),
|
|
||||||
unlocked = false,
|
|
||||||
initialized = false
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
@ -203,8 +203,8 @@ class OathManager(
|
|||||||
|
|
||||||
if (!deviceManager.isUsbKeyConnected()) {
|
if (!deviceManager.isUsbKeyConnected()) {
|
||||||
// for NFC connections require extra tap when switching context
|
// for NFC connections require extra tap when switching context
|
||||||
if (oathViewModel.sessionState.value == null) {
|
if (oathViewModel.sessionState.value is SessionState.Empty) {
|
||||||
oathViewModel.setSessionState(Session.uninitialized)
|
oathViewModel.setSessionState(null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -223,7 +223,7 @@ class OathManager(
|
|||||||
try {
|
try {
|
||||||
device.withConnection<SmartCardConnection, Unit> { connection ->
|
device.withConnection<SmartCardConnection, Unit> { connection ->
|
||||||
val session = getOathSession(connection)
|
val session = getOathSession(connection)
|
||||||
val previousId = oathViewModel.sessionState.value?.deviceId
|
val previousId = oathViewModel.currentSession()?.deviceId
|
||||||
if (session.deviceId == previousId && device is NfcYubiKeyDevice) {
|
if (session.deviceId == previousId && device is NfcYubiKeyDevice) {
|
||||||
// Run any pending action
|
// Run any pending action
|
||||||
pendingAction?.let { action ->
|
pendingAction?.let { action ->
|
||||||
@ -468,7 +468,7 @@ class OathManager(
|
|||||||
private fun forgetPassword(): String {
|
private fun forgetPassword(): String {
|
||||||
keyManager.clearAll()
|
keyManager.clearAll()
|
||||||
logger.debug("Cleared all keys.")
|
logger.debug("Cleared all keys.")
|
||||||
oathViewModel.sessionState.value?.let {
|
oathViewModel.currentSession()?.let {
|
||||||
oathViewModel.setSessionState(
|
oathViewModel.setSessionState(
|
||||||
it.copy(
|
it.copy(
|
||||||
isLocked = it.isAccessKeySet,
|
isLocked = it.isAccessKeySet,
|
||||||
|
@ -19,25 +19,36 @@ package com.yubico.authenticator.oath
|
|||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
|
import com.yubico.authenticator.SessionState
|
||||||
|
import com.yubico.authenticator.JsonSerializable
|
||||||
import com.yubico.authenticator.oath.data.Code
|
import com.yubico.authenticator.oath.data.Code
|
||||||
import com.yubico.authenticator.oath.data.Credential
|
import com.yubico.authenticator.oath.data.Credential
|
||||||
import com.yubico.authenticator.oath.data.CredentialWithCode
|
import com.yubico.authenticator.oath.data.CredentialWithCode
|
||||||
import com.yubico.authenticator.oath.data.Session
|
import com.yubico.authenticator.oath.data.Session
|
||||||
|
|
||||||
class OathViewModel: ViewModel() {
|
class OathViewModel: ViewModel() {
|
||||||
private val _sessionState = MutableLiveData<Session?>()
|
|
||||||
val sessionState: LiveData<Session?> = _sessionState
|
private val _sessionState = MutableLiveData<SessionState>()
|
||||||
|
val sessionState: LiveData<SessionState> = _sessionState
|
||||||
|
|
||||||
|
fun currentSession() : Session? = (sessionState.value as? SessionState.Value<*>)?.data as Session?
|
||||||
|
|
||||||
// Sets session and credentials after performing OATH reset
|
// Sets session and credentials after performing OATH reset
|
||||||
// Note: we cannot use [setSessionState] because resetting OATH changes deviceId
|
// Note: we cannot use [setSessionState] because resetting OATH changes deviceId
|
||||||
fun resetOathSession(sessionState: Session, credentials: Map<Credential, Code?>) {
|
fun resetOathSession(sessionState: Session, credentials: Map<Credential, Code?>) {
|
||||||
_sessionState.postValue(sessionState)
|
_sessionState.postValue(SessionState.Value(sessionState))
|
||||||
updateCredentials(credentials)
|
updateCredentials(credentials)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setSessionState(sessionState: Session?) {
|
fun setSessionState(sessionState: Session?) {
|
||||||
val oldDeviceId = _sessionState.value?.deviceId
|
val oldDeviceId = currentSession()?.deviceId
|
||||||
_sessionState.postValue(sessionState)
|
|
||||||
|
if (sessionState == null) {
|
||||||
|
_sessionState.postValue(SessionState.Empty)
|
||||||
|
} else {
|
||||||
|
_sessionState.postValue(SessionState.Value(sessionState))
|
||||||
|
}
|
||||||
|
|
||||||
if(oldDeviceId != sessionState?.deviceId) {
|
if(oldDeviceId != sessionState?.deviceId) {
|
||||||
_credentials.postValue(null)
|
_credentials.postValue(null)
|
||||||
}
|
}
|
||||||
@ -59,7 +70,7 @@ class OathViewModel: ViewModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun addCredential(credential: Credential, code: Code?): CredentialWithCode {
|
fun addCredential(credential: Credential, code: Code?): CredentialWithCode {
|
||||||
require(credential.deviceId == _sessionState.value?.deviceId) {
|
require(credential.deviceId == currentSession()?.deviceId) {
|
||||||
"Cannot add credential for different deviceId"
|
"Cannot add credential for different deviceId"
|
||||||
}
|
}
|
||||||
return CredentialWithCode(credential, code).also {
|
return CredentialWithCode(credential, code).also {
|
||||||
|
@ -16,10 +16,13 @@
|
|||||||
|
|
||||||
package com.yubico.authenticator.oath.data
|
package com.yubico.authenticator.oath.data
|
||||||
|
|
||||||
|
import com.yubico.authenticator.JsonSerializable
|
||||||
import com.yubico.authenticator.device.Version
|
import com.yubico.authenticator.device.Version
|
||||||
|
import com.yubico.authenticator.jsonSerializer
|
||||||
|
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.encodeToString
|
||||||
|
|
||||||
typealias YubiKitOathSession = com.yubico.yubikit.oath.OathSession
|
typealias YubiKitOathSession = com.yubico.yubikit.oath.OathSession
|
||||||
|
|
||||||
@ -34,9 +37,8 @@ data class Session(
|
|||||||
@SerialName("remembered")
|
@SerialName("remembered")
|
||||||
val isRemembered: Boolean,
|
val isRemembered: Boolean,
|
||||||
@SerialName("locked")
|
@SerialName("locked")
|
||||||
val isLocked: Boolean,
|
val isLocked: Boolean
|
||||||
val initialized: Boolean
|
) : JsonSerializable {
|
||||||
) {
|
|
||||||
@SerialName("keystore")
|
@SerialName("keystore")
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
val keystoreState: String = "unknown"
|
val keystoreState: String = "unknown"
|
||||||
@ -51,18 +53,10 @@ data class Session(
|
|||||||
),
|
),
|
||||||
oathSession.isAccessKeySet,
|
oathSession.isAccessKeySet,
|
||||||
isRemembered,
|
isRemembered,
|
||||||
oathSession.isLocked,
|
oathSession.isLocked
|
||||||
initialized = true
|
|
||||||
)
|
)
|
||||||
|
|
||||||
companion object {
|
override fun toJson(): String {
|
||||||
val uninitialized = Session(
|
return jsonSerializer.encodeToString(this)
|
||||||
deviceId = "",
|
|
||||||
version = Version(0, 0, 0),
|
|
||||||
isAccessKeySet = false,
|
|
||||||
isRemembered = false,
|
|
||||||
isLocked = false,
|
|
||||||
initialized = false
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -33,17 +33,19 @@ final _log = Logger('android.fido.state');
|
|||||||
const _methods = MethodChannel('android.fido.methods');
|
const _methods = MethodChannel('android.fido.methods');
|
||||||
|
|
||||||
final androidFidoStateProvider = AsyncNotifierProvider.autoDispose
|
final androidFidoStateProvider = AsyncNotifierProvider.autoDispose
|
||||||
.family<FidoStateNotifier, FidoState, DevicePath>(_FidoStateNotifier.new);
|
.family<FidoStateNotifier, FidoState?, DevicePath>(_FidoStateNotifier.new);
|
||||||
|
|
||||||
class _FidoStateNotifier extends FidoStateNotifier {
|
class _FidoStateNotifier extends FidoStateNotifier {
|
||||||
final _events = const EventChannel('android.fido.sessionState');
|
final _events = const EventChannel('android.fido.sessionState');
|
||||||
late StreamSubscription _sub;
|
late StreamSubscription _sub;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FutureOr<FidoState> build(DevicePath devicePath) async {
|
FutureOr<FidoState?> build(DevicePath devicePath) async {
|
||||||
_sub = _events.receiveBroadcastStream().listen((event) {
|
_sub = _events.receiveBroadcastStream().listen((event) {
|
||||||
final json = jsonDecode(event);
|
final json = jsonDecode(event);
|
||||||
if (json == null) {
|
if (json == null) {
|
||||||
|
state = const AsyncValue.data(null);
|
||||||
|
} else if (json.containsKey('loading') && json['loading'] == true) {
|
||||||
state = const AsyncValue.loading();
|
state = const AsyncValue.loading();
|
||||||
} else {
|
} else {
|
||||||
final fidoState = FidoState.fromJson(json);
|
final fidoState = FidoState.fromJson(json);
|
||||||
@ -55,7 +57,7 @@ class _FidoStateNotifier extends FidoStateNotifier {
|
|||||||
|
|
||||||
ref.onDispose(_sub.cancel);
|
ref.onDispose(_sub.cancel);
|
||||||
|
|
||||||
return Completer<FidoState>().future;
|
return Completer<FidoState?>().future;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -177,7 +179,7 @@ class _FidoFingerprintsNotifier extends FidoFingerprintsNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final androidCredentialProvider = AsyncNotifierProvider.autoDispose
|
final androidCredentialProvider = AsyncNotifierProvider.autoDispose
|
||||||
.family<FidoCredentialsNotifier, List<FidoCredential>, DevicePath>(
|
.family<FidoCredentialsNotifier, List<FidoCredential>?, DevicePath>(
|
||||||
_FidoCredentialsNotifier.new);
|
_FidoCredentialsNotifier.new);
|
||||||
|
|
||||||
class _FidoCredentialsNotifier extends FidoCredentialsNotifier {
|
class _FidoCredentialsNotifier extends FidoCredentialsNotifier {
|
||||||
@ -185,10 +187,12 @@ class _FidoCredentialsNotifier extends FidoCredentialsNotifier {
|
|||||||
late StreamSubscription _sub;
|
late StreamSubscription _sub;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FutureOr<List<FidoCredential>> build(DevicePath devicePath) async {
|
FutureOr<List<FidoCredential>?> build(DevicePath devicePath) async {
|
||||||
_sub = _events.receiveBroadcastStream().listen((event) {
|
_sub = _events.receiveBroadcastStream().listen((event) {
|
||||||
final json = jsonDecode(event);
|
final json = jsonDecode(event);
|
||||||
if (json == null) {
|
if (json == null) {
|
||||||
|
state = const AsyncValue.data(null);
|
||||||
|
} else if (json[0] is Map && json[0]['loading'] == true) {
|
||||||
state = const AsyncValue.loading();
|
state = const AsyncValue.loading();
|
||||||
} else {
|
} else {
|
||||||
List<FidoCredential> newState = List.from(
|
List<FidoCredential> newState = List.from(
|
||||||
@ -200,7 +204,7 @@ class _FidoCredentialsNotifier extends FidoCredentialsNotifier {
|
|||||||
});
|
});
|
||||||
|
|
||||||
ref.onDispose(_sub.cancel);
|
ref.onDispose(_sub.cancel);
|
||||||
return Completer<List<FidoCredential>>().future;
|
return Completer<List<FidoCredential>?>().future;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -24,6 +24,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
|
||||||
|
import '../../app/error_data_empty.dart';
|
||||||
import '../../app/logging.dart';
|
import '../../app/logging.dart';
|
||||||
import '../../app/models.dart';
|
import '../../app/models.dart';
|
||||||
import '../../app/state.dart';
|
import '../../app/state.dart';
|
||||||
@ -51,6 +52,8 @@ class _AndroidOathStateNotifier extends OathStateNotifier {
|
|||||||
_sub = _events.receiveBroadcastStream().listen((event) {
|
_sub = _events.receiveBroadcastStream().listen((event) {
|
||||||
final json = jsonDecode(event);
|
final json = jsonDecode(event);
|
||||||
if (json == null) {
|
if (json == null) {
|
||||||
|
state = AsyncValue.error(const ErrorDataEmpty(), StackTrace.current);
|
||||||
|
} else if (json[0] is Map && json[0]['loading'] == true) {
|
||||||
state = const AsyncValue.loading();
|
state = const AsyncValue.loading();
|
||||||
} else {
|
} else {
|
||||||
final oathState = OathState.fromJson(json);
|
final oathState = OathState.fromJson(json);
|
||||||
|
3
lib/app/error_data_empty.dart
Normal file
3
lib/app/error_data_empty.dart
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
class ErrorDataEmpty {
|
||||||
|
const ErrorDataEmpty();
|
||||||
|
}
|
@ -47,14 +47,14 @@ final _sessionProvider =
|
|||||||
);
|
);
|
||||||
|
|
||||||
final desktopFidoState = AsyncNotifierProvider.autoDispose
|
final desktopFidoState = AsyncNotifierProvider.autoDispose
|
||||||
.family<FidoStateNotifier, FidoState, DevicePath>(
|
.family<FidoStateNotifier, FidoState?, DevicePath>(
|
||||||
_DesktopFidoStateNotifier.new);
|
_DesktopFidoStateNotifier.new);
|
||||||
|
|
||||||
class _DesktopFidoStateNotifier extends FidoStateNotifier {
|
class _DesktopFidoStateNotifier extends FidoStateNotifier {
|
||||||
late RpcNodeSession _session;
|
late RpcNodeSession _session;
|
||||||
late StateController<String?> _pinController;
|
late StateController<String?> _pinController;
|
||||||
|
|
||||||
FutureOr<FidoState> _build(DevicePath devicePath) async {
|
FutureOr<FidoState?> _build(DevicePath devicePath) async {
|
||||||
var result = await _session.command('get');
|
var result = await _session.command('get');
|
||||||
FidoState fidoState = FidoState.fromJson(result['data']);
|
FidoState fidoState = FidoState.fromJson(result['data']);
|
||||||
if (fidoState.hasPin && !fidoState.unlocked) {
|
if (fidoState.hasPin && !fidoState.unlocked) {
|
||||||
@ -71,7 +71,7 @@ class _DesktopFidoStateNotifier extends FidoStateNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FutureOr<FidoState> build(DevicePath devicePath) async {
|
FutureOr<FidoState?> build(DevicePath devicePath) async {
|
||||||
_session = ref.watch(_sessionProvider(devicePath));
|
_session = ref.watch(_sessionProvider(devicePath));
|
||||||
if (Platform.isWindows) {
|
if (Platform.isWindows) {
|
||||||
// Make sure to rebuild if isAdmin changes
|
// Make sure to rebuild if isAdmin changes
|
||||||
@ -269,7 +269,7 @@ class _DesktopFidoFingerprintsNotifier extends FidoFingerprintsNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final desktopCredentialProvider = AsyncNotifierProvider.autoDispose
|
final desktopCredentialProvider = AsyncNotifierProvider.autoDispose
|
||||||
.family<FidoCredentialsNotifier, List<FidoCredential>, DevicePath>(
|
.family<FidoCredentialsNotifier, List<FidoCredential>?, DevicePath>(
|
||||||
_DesktopFidoCredentialsNotifier.new);
|
_DesktopFidoCredentialsNotifier.new);
|
||||||
|
|
||||||
class _DesktopFidoCredentialsNotifier extends FidoCredentialsNotifier {
|
class _DesktopFidoCredentialsNotifier extends FidoCredentialsNotifier {
|
||||||
|
@ -27,8 +27,7 @@ class FidoState with _$FidoState {
|
|||||||
|
|
||||||
factory FidoState(
|
factory FidoState(
|
||||||
{required Map<String, dynamic> info,
|
{required Map<String, dynamic> info,
|
||||||
required bool unlocked,
|
required bool unlocked}) = _FidoState;
|
||||||
@Default(true) bool initialized}) = _FidoState;
|
|
||||||
|
|
||||||
factory FidoState.fromJson(Map<String, dynamic> json) =>
|
factory FidoState.fromJson(Map<String, dynamic> json) =>
|
||||||
_$FidoStateFromJson(json);
|
_$FidoStateFromJson(json);
|
||||||
|
@ -22,7 +22,6 @@ FidoState _$FidoStateFromJson(Map<String, dynamic> json) {
|
|||||||
mixin _$FidoState {
|
mixin _$FidoState {
|
||||||
Map<String, dynamic> get info => throw _privateConstructorUsedError;
|
Map<String, dynamic> get info => throw _privateConstructorUsedError;
|
||||||
bool get unlocked => throw _privateConstructorUsedError;
|
bool get unlocked => throw _privateConstructorUsedError;
|
||||||
bool get initialized => throw _privateConstructorUsedError;
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
@ -35,7 +34,7 @@ abstract class $FidoStateCopyWith<$Res> {
|
|||||||
factory $FidoStateCopyWith(FidoState value, $Res Function(FidoState) then) =
|
factory $FidoStateCopyWith(FidoState value, $Res Function(FidoState) then) =
|
||||||
_$FidoStateCopyWithImpl<$Res, FidoState>;
|
_$FidoStateCopyWithImpl<$Res, FidoState>;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({Map<String, dynamic> info, bool unlocked, bool initialized});
|
$Res call({Map<String, dynamic> info, bool unlocked});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@ -53,7 +52,6 @@ class _$FidoStateCopyWithImpl<$Res, $Val extends FidoState>
|
|||||||
$Res call({
|
$Res call({
|
||||||
Object? info = null,
|
Object? info = null,
|
||||||
Object? unlocked = null,
|
Object? unlocked = null,
|
||||||
Object? initialized = null,
|
|
||||||
}) {
|
}) {
|
||||||
return _then(_value.copyWith(
|
return _then(_value.copyWith(
|
||||||
info: null == info
|
info: null == info
|
||||||
@ -64,10 +62,6 @@ class _$FidoStateCopyWithImpl<$Res, $Val extends FidoState>
|
|||||||
? _value.unlocked
|
? _value.unlocked
|
||||||
: unlocked // ignore: cast_nullable_to_non_nullable
|
: unlocked // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,
|
as bool,
|
||||||
initialized: null == initialized
|
|
||||||
? _value.initialized
|
|
||||||
: initialized // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool,
|
|
||||||
) as $Val);
|
) as $Val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -80,7 +74,7 @@ abstract class _$$FidoStateImplCopyWith<$Res>
|
|||||||
__$$FidoStateImplCopyWithImpl<$Res>;
|
__$$FidoStateImplCopyWithImpl<$Res>;
|
||||||
@override
|
@override
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({Map<String, dynamic> info, bool unlocked, bool initialized});
|
$Res call({Map<String, dynamic> info, bool unlocked});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@ -96,7 +90,6 @@ class __$$FidoStateImplCopyWithImpl<$Res>
|
|||||||
$Res call({
|
$Res call({
|
||||||
Object? info = null,
|
Object? info = null,
|
||||||
Object? unlocked = null,
|
Object? unlocked = null,
|
||||||
Object? initialized = null,
|
|
||||||
}) {
|
}) {
|
||||||
return _then(_$FidoStateImpl(
|
return _then(_$FidoStateImpl(
|
||||||
info: null == info
|
info: null == info
|
||||||
@ -107,10 +100,6 @@ class __$$FidoStateImplCopyWithImpl<$Res>
|
|||||||
? _value.unlocked
|
? _value.unlocked
|
||||||
: unlocked // ignore: cast_nullable_to_non_nullable
|
: unlocked // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,
|
as bool,
|
||||||
initialized: null == initialized
|
|
||||||
? _value.initialized
|
|
||||||
: initialized // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool,
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -119,9 +108,7 @@ class __$$FidoStateImplCopyWithImpl<$Res>
|
|||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class _$FidoStateImpl extends _FidoState {
|
class _$FidoStateImpl extends _FidoState {
|
||||||
_$FidoStateImpl(
|
_$FidoStateImpl(
|
||||||
{required final Map<String, dynamic> info,
|
{required final Map<String, dynamic> info, required this.unlocked})
|
||||||
required this.unlocked,
|
|
||||||
this.initialized = true})
|
|
||||||
: _info = info,
|
: _info = info,
|
||||||
super._();
|
super._();
|
||||||
|
|
||||||
@ -138,13 +125,10 @@ class _$FidoStateImpl extends _FidoState {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
final bool unlocked;
|
final bool unlocked;
|
||||||
@override
|
|
||||||
@JsonKey()
|
|
||||||
final bool initialized;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'FidoState(info: $info, unlocked: $unlocked, initialized: $initialized)';
|
return 'FidoState(info: $info, unlocked: $unlocked)';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -154,15 +138,13 @@ class _$FidoStateImpl extends _FidoState {
|
|||||||
other is _$FidoStateImpl &&
|
other is _$FidoStateImpl &&
|
||||||
const DeepCollectionEquality().equals(other._info, _info) &&
|
const DeepCollectionEquality().equals(other._info, _info) &&
|
||||||
(identical(other.unlocked, unlocked) ||
|
(identical(other.unlocked, unlocked) ||
|
||||||
other.unlocked == unlocked) &&
|
other.unlocked == unlocked));
|
||||||
(identical(other.initialized, initialized) ||
|
|
||||||
other.initialized == initialized));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,
|
int get hashCode => Object.hash(
|
||||||
const DeepCollectionEquality().hash(_info), unlocked, initialized);
|
runtimeType, const DeepCollectionEquality().hash(_info), unlocked);
|
||||||
|
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
@override
|
@override
|
||||||
@ -181,8 +163,7 @@ class _$FidoStateImpl extends _FidoState {
|
|||||||
abstract class _FidoState extends FidoState {
|
abstract class _FidoState extends FidoState {
|
||||||
factory _FidoState(
|
factory _FidoState(
|
||||||
{required final Map<String, dynamic> info,
|
{required final Map<String, dynamic> info,
|
||||||
required final bool unlocked,
|
required final bool unlocked}) = _$FidoStateImpl;
|
||||||
final bool initialized}) = _$FidoStateImpl;
|
|
||||||
_FidoState._() : super._();
|
_FidoState._() : super._();
|
||||||
|
|
||||||
factory _FidoState.fromJson(Map<String, dynamic> json) =
|
factory _FidoState.fromJson(Map<String, dynamic> json) =
|
||||||
@ -193,8 +174,6 @@ abstract class _FidoState extends FidoState {
|
|||||||
@override
|
@override
|
||||||
bool get unlocked;
|
bool get unlocked;
|
||||||
@override
|
@override
|
||||||
bool get initialized;
|
|
||||||
@override
|
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
_$$FidoStateImplCopyWith<_$FidoStateImpl> get copyWith =>
|
_$$FidoStateImplCopyWith<_$FidoStateImpl> get copyWith =>
|
||||||
throw _privateConstructorUsedError;
|
throw _privateConstructorUsedError;
|
||||||
|
@ -10,14 +10,12 @@ _$FidoStateImpl _$$FidoStateImplFromJson(Map<String, dynamic> json) =>
|
|||||||
_$FidoStateImpl(
|
_$FidoStateImpl(
|
||||||
info: json['info'] as Map<String, dynamic>,
|
info: json['info'] as Map<String, dynamic>,
|
||||||
unlocked: json['unlocked'] as bool,
|
unlocked: json['unlocked'] as bool,
|
||||||
initialized: json['initialized'] as bool? ?? true,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$$FidoStateImplToJson(_$FidoStateImpl instance) =>
|
Map<String, dynamic> _$$FidoStateImplToJson(_$FidoStateImpl instance) =>
|
||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
'info': instance.info,
|
'info': instance.info,
|
||||||
'unlocked': instance.unlocked,
|
'unlocked': instance.unlocked,
|
||||||
'initialized': instance.initialized,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
_$FingerprintImpl _$$FingerprintImplFromJson(Map<String, dynamic> json) =>
|
_$FingerprintImpl _$$FingerprintImplFromJson(Map<String, dynamic> json) =>
|
||||||
|
@ -21,11 +21,11 @@ import '../core/state.dart';
|
|||||||
import 'models.dart';
|
import 'models.dart';
|
||||||
|
|
||||||
final fidoStateProvider = AsyncNotifierProvider.autoDispose
|
final fidoStateProvider = AsyncNotifierProvider.autoDispose
|
||||||
.family<FidoStateNotifier, FidoState, DevicePath>(
|
.family<FidoStateNotifier, FidoState?, DevicePath>(
|
||||||
() => throw UnimplementedError(),
|
() => throw UnimplementedError(),
|
||||||
);
|
);
|
||||||
|
|
||||||
abstract class FidoStateNotifier extends ApplicationStateNotifier<FidoState> {
|
abstract class FidoStateNotifier extends ApplicationStateNotifier<FidoState?> {
|
||||||
Stream<InteractionEvent> reset();
|
Stream<InteractionEvent> reset();
|
||||||
Future<PinResult> setPin(String newPin, {String? oldPin});
|
Future<PinResult> setPin(String newPin, {String? oldPin});
|
||||||
Future<PinResult> unlock(String pin);
|
Future<PinResult> unlock(String pin);
|
||||||
@ -44,11 +44,11 @@ abstract class FidoFingerprintsNotifier
|
|||||||
}
|
}
|
||||||
|
|
||||||
final credentialProvider = AsyncNotifierProvider.autoDispose
|
final credentialProvider = AsyncNotifierProvider.autoDispose
|
||||||
.family<FidoCredentialsNotifier, List<FidoCredential>, DevicePath>(
|
.family<FidoCredentialsNotifier, List<FidoCredential>?, DevicePath>(
|
||||||
() => throw UnimplementedError(),
|
() => throw UnimplementedError(),
|
||||||
);
|
);
|
||||||
|
|
||||||
abstract class FidoCredentialsNotifier
|
abstract class FidoCredentialsNotifier
|
||||||
extends AutoDisposeFamilyAsyncNotifier<List<FidoCredential>, DevicePath> {
|
extends AutoDisposeFamilyAsyncNotifier<List<FidoCredential>?, DevicePath> {
|
||||||
Future<void> deleteCredential(FidoCredential credential);
|
Future<void> deleteCredential(FidoCredential credential);
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@ import '../../app/views/app_failure_page.dart';
|
|||||||
import '../../app/views/app_list_item.dart';
|
import '../../app/views/app_list_item.dart';
|
||||||
import '../../app/views/app_page.dart';
|
import '../../app/views/app_page.dart';
|
||||||
import '../../app/views/message_page.dart';
|
import '../../app/views/message_page.dart';
|
||||||
|
import '../../app/views/message_page_not_initialized.dart';
|
||||||
import '../../core/state.dart';
|
import '../../core/state.dart';
|
||||||
import '../../management/models.dart';
|
import '../../management/models.dart';
|
||||||
import '../../widgets/list_title.dart';
|
import '../../widgets/list_title.dart';
|
||||||
@ -73,7 +74,9 @@ class FingerprintsScreen extends ConsumerWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
data: (fidoState) {
|
data: (fidoState) {
|
||||||
return fidoState.unlocked
|
return fidoState == null
|
||||||
|
? MessagePageNotInitialized(title: l10n.s_fingerprints)
|
||||||
|
: fidoState.unlocked
|
||||||
? _FidoUnlockedPage(deviceData.node, fidoState)
|
? _FidoUnlockedPage(deviceData.node, fidoState)
|
||||||
: _FidoLockedPage(deviceData.node, fidoState);
|
: _FidoLockedPage(deviceData.node, fidoState);
|
||||||
});
|
});
|
||||||
|
@ -76,11 +76,11 @@ class PasskeysScreen extends ConsumerWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
data: (fidoState) {
|
data: (fidoState) {
|
||||||
return fidoState.initialized
|
return fidoState == null
|
||||||
? fidoState.unlocked
|
? MessagePageNotInitialized(title: l10n.s_passkeys)
|
||||||
|
: fidoState.unlocked
|
||||||
? _FidoUnlockedPage(deviceData.node, fidoState)
|
? _FidoUnlockedPage(deviceData.node, fidoState)
|
||||||
: _FidoLockedPage(deviceData.node, fidoState)
|
: _FidoLockedPage(deviceData.node, fidoState);
|
||||||
: MessagePageNotInitialized(title: l10n.s_passkeys);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -235,6 +235,10 @@ class _FidoUnlockedPageState extends ConsumerState<_FidoUnlockedPage> {
|
|||||||
}
|
}
|
||||||
final credentials = data.value;
|
final credentials = data.value;
|
||||||
|
|
||||||
|
if (credentials == null) {
|
||||||
|
return _buildLoadingPage(context);
|
||||||
|
}
|
||||||
|
|
||||||
if (credentials.isEmpty) {
|
if (credentials.isEmpty) {
|
||||||
return MessagePage(
|
return MessagePage(
|
||||||
title: l10n.s_passkeys,
|
title: l10n.s_passkeys,
|
||||||
|
@ -105,15 +105,11 @@ class OathPair with _$OathPair {
|
|||||||
class OathState with _$OathState {
|
class OathState with _$OathState {
|
||||||
const OathState._();
|
const OathState._();
|
||||||
|
|
||||||
factory OathState(
|
factory OathState(String deviceId, Version version,
|
||||||
String deviceId,
|
{required bool hasKey,
|
||||||
Version version, {
|
|
||||||
required bool hasKey,
|
|
||||||
required bool remembered,
|
required bool remembered,
|
||||||
required bool locked,
|
required bool locked,
|
||||||
required KeystoreState keystore,
|
required KeystoreState keystore}) = _OathState;
|
||||||
@Default(true) bool initialized,
|
|
||||||
}) = _OathState;
|
|
||||||
|
|
||||||
int? get capacity =>
|
int? get capacity =>
|
||||||
version.isAtLeast(4) ? (version.isAtLeast(5, 7) ? 64 : 32) : null;
|
version.isAtLeast(4) ? (version.isAtLeast(5, 7) ? 64 : 32) : null;
|
||||||
|
@ -639,7 +639,6 @@ mixin _$OathState {
|
|||||||
bool get remembered => throw _privateConstructorUsedError;
|
bool get remembered => throw _privateConstructorUsedError;
|
||||||
bool get locked => throw _privateConstructorUsedError;
|
bool get locked => throw _privateConstructorUsedError;
|
||||||
KeystoreState get keystore => throw _privateConstructorUsedError;
|
KeystoreState get keystore => throw _privateConstructorUsedError;
|
||||||
bool get initialized => throw _privateConstructorUsedError;
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
@ -658,8 +657,7 @@ abstract class $OathStateCopyWith<$Res> {
|
|||||||
bool hasKey,
|
bool hasKey,
|
||||||
bool remembered,
|
bool remembered,
|
||||||
bool locked,
|
bool locked,
|
||||||
KeystoreState keystore,
|
KeystoreState keystore});
|
||||||
bool initialized});
|
|
||||||
|
|
||||||
$VersionCopyWith<$Res> get version;
|
$VersionCopyWith<$Res> get version;
|
||||||
}
|
}
|
||||||
@ -683,7 +681,6 @@ class _$OathStateCopyWithImpl<$Res, $Val extends OathState>
|
|||||||
Object? remembered = null,
|
Object? remembered = null,
|
||||||
Object? locked = null,
|
Object? locked = null,
|
||||||
Object? keystore = null,
|
Object? keystore = null,
|
||||||
Object? initialized = null,
|
|
||||||
}) {
|
}) {
|
||||||
return _then(_value.copyWith(
|
return _then(_value.copyWith(
|
||||||
deviceId: null == deviceId
|
deviceId: null == deviceId
|
||||||
@ -710,10 +707,6 @@ class _$OathStateCopyWithImpl<$Res, $Val extends OathState>
|
|||||||
? _value.keystore
|
? _value.keystore
|
||||||
: keystore // ignore: cast_nullable_to_non_nullable
|
: keystore // ignore: cast_nullable_to_non_nullable
|
||||||
as KeystoreState,
|
as KeystoreState,
|
||||||
initialized: null == initialized
|
|
||||||
? _value.initialized
|
|
||||||
: initialized // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool,
|
|
||||||
) as $Val);
|
) as $Val);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -740,8 +733,7 @@ abstract class _$$OathStateImplCopyWith<$Res>
|
|||||||
bool hasKey,
|
bool hasKey,
|
||||||
bool remembered,
|
bool remembered,
|
||||||
bool locked,
|
bool locked,
|
||||||
KeystoreState keystore,
|
KeystoreState keystore});
|
||||||
bool initialized});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
$VersionCopyWith<$Res> get version;
|
$VersionCopyWith<$Res> get version;
|
||||||
@ -764,7 +756,6 @@ class __$$OathStateImplCopyWithImpl<$Res>
|
|||||||
Object? remembered = null,
|
Object? remembered = null,
|
||||||
Object? locked = null,
|
Object? locked = null,
|
||||||
Object? keystore = null,
|
Object? keystore = null,
|
||||||
Object? initialized = null,
|
|
||||||
}) {
|
}) {
|
||||||
return _then(_$OathStateImpl(
|
return _then(_$OathStateImpl(
|
||||||
null == deviceId
|
null == deviceId
|
||||||
@ -791,10 +782,6 @@ class __$$OathStateImplCopyWithImpl<$Res>
|
|||||||
? _value.keystore
|
? _value.keystore
|
||||||
: keystore // ignore: cast_nullable_to_non_nullable
|
: keystore // ignore: cast_nullable_to_non_nullable
|
||||||
as KeystoreState,
|
as KeystoreState,
|
||||||
initialized: null == initialized
|
|
||||||
? _value.initialized
|
|
||||||
: initialized // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool,
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -806,8 +793,7 @@ class _$OathStateImpl extends _OathState {
|
|||||||
{required this.hasKey,
|
{required this.hasKey,
|
||||||
required this.remembered,
|
required this.remembered,
|
||||||
required this.locked,
|
required this.locked,
|
||||||
required this.keystore,
|
required this.keystore})
|
||||||
this.initialized = true})
|
|
||||||
: super._();
|
: super._();
|
||||||
|
|
||||||
factory _$OathStateImpl.fromJson(Map<String, dynamic> json) =>
|
factory _$OathStateImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
@ -825,13 +811,10 @@ class _$OathStateImpl extends _OathState {
|
|||||||
final bool locked;
|
final bool locked;
|
||||||
@override
|
@override
|
||||||
final KeystoreState keystore;
|
final KeystoreState keystore;
|
||||||
@override
|
|
||||||
@JsonKey()
|
|
||||||
final bool initialized;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'OathState(deviceId: $deviceId, version: $version, hasKey: $hasKey, remembered: $remembered, locked: $locked, keystore: $keystore, initialized: $initialized)';
|
return 'OathState(deviceId: $deviceId, version: $version, hasKey: $hasKey, remembered: $remembered, locked: $locked, keystore: $keystore)';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -847,15 +830,13 @@ class _$OathStateImpl extends _OathState {
|
|||||||
other.remembered == remembered) &&
|
other.remembered == remembered) &&
|
||||||
(identical(other.locked, locked) || other.locked == locked) &&
|
(identical(other.locked, locked) || other.locked == locked) &&
|
||||||
(identical(other.keystore, keystore) ||
|
(identical(other.keystore, keystore) ||
|
||||||
other.keystore == keystore) &&
|
other.keystore == keystore));
|
||||||
(identical(other.initialized, initialized) ||
|
|
||||||
other.initialized == initialized));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType, deviceId, version, hasKey,
|
int get hashCode => Object.hash(
|
||||||
remembered, locked, keystore, initialized);
|
runtimeType, deviceId, version, hasKey, remembered, locked, keystore);
|
||||||
|
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
@override
|
@override
|
||||||
@ -876,8 +857,7 @@ abstract class _OathState extends OathState {
|
|||||||
{required final bool hasKey,
|
{required final bool hasKey,
|
||||||
required final bool remembered,
|
required final bool remembered,
|
||||||
required final bool locked,
|
required final bool locked,
|
||||||
required final KeystoreState keystore,
|
required final KeystoreState keystore}) = _$OathStateImpl;
|
||||||
final bool initialized}) = _$OathStateImpl;
|
|
||||||
_OathState._() : super._();
|
_OathState._() : super._();
|
||||||
|
|
||||||
factory _OathState.fromJson(Map<String, dynamic> json) =
|
factory _OathState.fromJson(Map<String, dynamic> json) =
|
||||||
@ -896,8 +876,6 @@ abstract class _OathState extends OathState {
|
|||||||
@override
|
@override
|
||||||
KeystoreState get keystore;
|
KeystoreState get keystore;
|
||||||
@override
|
@override
|
||||||
bool get initialized;
|
|
||||||
@override
|
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
_$$OathStateImplCopyWith<_$OathStateImpl> get copyWith =>
|
_$$OathStateImplCopyWith<_$OathStateImpl> get copyWith =>
|
||||||
throw _privateConstructorUsedError;
|
throw _privateConstructorUsedError;
|
||||||
|
@ -70,7 +70,6 @@ _$OathStateImpl _$$OathStateImplFromJson(Map<String, dynamic> json) =>
|
|||||||
remembered: json['remembered'] as bool,
|
remembered: json['remembered'] as bool,
|
||||||
locked: json['locked'] as bool,
|
locked: json['locked'] as bool,
|
||||||
keystore: $enumDecode(_$KeystoreStateEnumMap, json['keystore']),
|
keystore: $enumDecode(_$KeystoreStateEnumMap, json['keystore']),
|
||||||
initialized: json['initialized'] as bool? ?? true,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$$OathStateImplToJson(_$OathStateImpl instance) =>
|
Map<String, dynamic> _$$OathStateImplToJson(_$OathStateImpl instance) =>
|
||||||
@ -81,7 +80,6 @@ Map<String, dynamic> _$$OathStateImplToJson(_$OathStateImpl instance) =>
|
|||||||
'remembered': instance.remembered,
|
'remembered': instance.remembered,
|
||||||
'locked': instance.locked,
|
'locked': instance.locked,
|
||||||
'keystore': _$KeystoreStateEnumMap[instance.keystore]!,
|
'keystore': _$KeystoreStateEnumMap[instance.keystore]!,
|
||||||
'initialized': instance.initialized,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const _$KeystoreStateEnumMap = {
|
const _$KeystoreStateEnumMap = {
|
||||||
|
@ -23,6 +23,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
|||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
|
||||||
|
import '../../app/error_data_empty.dart';
|
||||||
import '../../app/message.dart';
|
import '../../app/message.dart';
|
||||||
import '../../app/models.dart';
|
import '../../app/models.dart';
|
||||||
import '../../app/shortcuts.dart';
|
import '../../app/shortcuts.dart';
|
||||||
@ -64,15 +65,14 @@ class OathScreen extends ConsumerWidget {
|
|||||||
graphic: CircularProgressIndicator(),
|
graphic: CircularProgressIndicator(),
|
||||||
delayedContent: true,
|
delayedContent: true,
|
||||||
),
|
),
|
||||||
error: (error, _) => AppFailurePage(
|
error: (error, _) => error is ErrorDataEmpty
|
||||||
|
? MessagePageNotInitialized(title: l10n.s_accounts)
|
||||||
|
: AppFailurePage(
|
||||||
cause: error,
|
cause: error,
|
||||||
),
|
),
|
||||||
data: (oathState) => oathState.initialized
|
data: (oathState) => oathState.locked
|
||||||
? oathState.locked
|
|
||||||
? _LockedView(devicePath, oathState)
|
? _LockedView(devicePath, oathState)
|
||||||
: _UnlockedView(devicePath, oathState)
|
: _UnlockedView(devicePath, oathState));
|
||||||
: MessagePageNotInitialized(title: l10n.s_accounts),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user