Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
ae75f9e
Move RequestId to credentialsd_common::model
iinuwa Feb 21, 2026
1916f61
Make ViewRequest Clone
iinuwa Feb 21, 2026
e25b31e
Add FlowControlClient.
iinuwa Feb 21, 2026
22039c2
Inject flow control client at request time instead of at startup.
iinuwa Feb 21, 2026
6296892
wip: Introduce FlowObject pattern
iinuwa Feb 21, 2026
3b226d5
wip: Serialize BackendRequest, reorder generics on CredentialService,…
iinuwa Feb 24, 2026
ffe525b
wip: transfer responsibility to talk to UI Control service to Flow Co…
iinuwa Feb 26, 2026
d27bbd2
wip: daemon: move UiController out of CredentialService
iinuwa Feb 28, 2026
147db56
wip: make CredentialService async trait
iinuwa Apr 23, 2026
eeda352
wip: clean up flow control stuff
iinuwa Apr 23, 2026
994022e
squash
iinuwa Apr 23, 2026
a76c3d8
wip: ui: Receive events from frontend
iinuwa Apr 23, 2026
a02429e
ui: start portal impl
iinuwa Apr 23, 2026
722042e
serve credential portal backend and credentialsd-ui simultaneously
iinuwa Apr 23, 2026
77f9702
ui: change name of impl portal interface
iinuwa Apr 23, 2026
42c6b34
ui: send events to frontend
iinuwa Apr 23, 2026
9c6300a
Export docs
iinuwa Apr 23, 2026
135056f
wip: move backend request to top-level parameters
iinuwa Apr 23, 2026
5d175cb
wip: start flattening BackgroundEvent and BackendRequest
iinuwa Apr 24, 2026
f593ced
ui: update docs
iinuwa Apr 24, 2026
8379385
ui: add TODO to clean up request objects
iinuwa Apr 24, 2026
a57b2a2
ui: Add portal configuration file
iinuwa Apr 25, 2026
6c9d627
ui: Fix backend API to allow empty window handles
iinuwa May 8, 2026
be8f4a8
daemon: Delete irrelevant test
iinuwa May 9, 2026
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
4 changes: 2 additions & 2 deletions credentialsd-common/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ use std::pin::Pin;
use futures_lite::Stream;

use crate::{
model::Device,
server::{BackgroundEvent, RequestId},
model::{Device, RequestId},
server::BackgroundEvent,
};

/// Used for communication from trusted UI to credential service
Expand Down
51 changes: 49 additions & 2 deletions credentialsd-common/src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,17 @@ pub struct Device {

#[derive(Clone, Debug, Serialize, Deserialize, Type)]
pub enum Operation {
Create,
Get,
PublicKeyCreate,
PublicKeyGet,
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Type)]
pub struct PortalBackendOptions {
/// Top-level origin of the request if different from the origin.
pub top_origin: Optional<String>,

/// RP ID of the request. Required for WebAuthn/PublicKey requests.
pub rp_id: Optional<String>,
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Type)]
Expand Down Expand Up @@ -122,6 +131,9 @@ pub struct RequestingParty {
pub origin: String,
}

/// Identifier for a request to be used for cancellation.
pub type RequestId = u32;

// TODO: Move to credentialsd-ui
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ViewUpdate {
Expand Down Expand Up @@ -254,6 +266,41 @@ pub enum NfcState {
Failed(Error),
}

pub enum BackendRequest {
/// Start Hybrid discovery
StartHybridDiscovery,

/// Start NFC discovery
StartNfcDiscovery,

/// Start USB discovery
StartUsbDiscovery,

/// Send client PIN
EnterClientPin(String),

/// Select a credential by credential ID
SelectCredential(String),

CancelRequest,
}

impl std::fmt::Debug for BackendRequest {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::StartHybridDiscovery => write!(f, "StartHybridDiscovery"),
Self::StartNfcDiscovery => write!(f, "StartNfcDiscovery"),
Self::StartUsbDiscovery => write!(f, "StartUsbDiscovery"),
Self::EnterClientPin(_) => f
.debug_tuple("EnterClientPin")
.field(&"******".to_string())
.finish(),
Self::SelectCredential(arg0) => f.debug_tuple("SelectCredential").field(arg0).finish(),
Self::CancelRequest => write!(f, "CancelRequest"),
}
}
}

#[derive(Debug, Clone)]
pub enum Error {
/// Some unknown error with the authenticator occurred.
Expand Down
106 changes: 99 additions & 7 deletions credentialsd-common/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ use serde::{
};
use zvariant::{
self, Array, DeserializeDict, DynamicDeserialize, NoneValue, Optional, OwnedValue,
SerializeDict, Signature, Structure, StructureBuilder, Type, Value, signature::Fields,
SerializeDict, Signature, Str, Structure, StructureBuilder, Type, Value, signature::Fields,
};

use crate::model::{Device, Operation, RequestingApplication};
use crate::model::{BackendRequest, Device, Operation, RequestId, RequestingApplication};

const TAG_VALUE_SIGNATURE: &Signature = &Signature::Structure(Fields::Static {
fields: &[&Signature::U32, &Signature::Variant],
Expand Down Expand Up @@ -49,6 +49,13 @@ const BACKGROUND_EVENT_ERROR_CREDENTIAL_EXCLUDED: u32 = 0x80000006;
const BACKGROUND_EVENT_ERROR_PIN_ATTEMPTS_EXHAUSTED: u32 = 0x80000007;
const BACKGROUND_EVENT_ERROR_PIN_NOT_SET: u32 = 0x80000008;

const BACKEND_REQUEST_START_HYBRID_DISCOVERY: u32 = 0x01;
const BACKEND_REQUEST_START_USB_DISCOVERY: u32 = 0x02;
const BACKEND_REQUEST_START_NFC_DISCOVERY: u32 = 0x03;
const BACKEND_REQUEST_ENTER_CLIENT_PIN: u32 = 0x04;
const BACKEND_REQUEST_SELECT_CREDENTIAL: u32 = 0x05;
const BACKEND_REQUEST_CANCEL_REQUEST: u32 = 0x06;

/// Flattened enum BackgroundEvent for sending across D-Bus.
#[derive(Debug, Clone, PartialEq)]
pub enum BackgroundEvent {
Expand Down Expand Up @@ -267,6 +274,94 @@ impl<'de> Deserialize<'de> for BackgroundEvent {
}
}

impl Type for BackendRequest {
const SIGNATURE: &'static Signature = TAG_VALUE_SIGNATURE;
}

impl From<&BackendRequest> for Structure<'_> {
fn from(value: &BackendRequest) -> Self {
match value {
BackendRequest::StartHybridDiscovery => tag_value_to_struct(0x01, None),
BackendRequest::StartNfcDiscovery => tag_value_to_struct(0x02, None),
BackendRequest::StartUsbDiscovery => tag_value_to_struct(0x03, None),
BackendRequest::EnterClientPin(pin) => {
tag_value_to_struct(0x04, Some(Value::Str(pin.into())))
}
BackendRequest::SelectCredential(credential_id) => {
tag_value_to_struct(0x05, Some(Value::Str(credential_id.into())))
}
BackendRequest::CancelRequest => tag_value_to_struct(0x06, None),
}
}
}

impl TryFrom<&Structure<'_>> for BackendRequest {
type Error = zvariant::Error;

fn try_from(value: &Structure<'_>) -> Result<Self, Self::Error> {
let (tag, value) = parse_tag_value_struct(value)?;

match tag {
0x01 => Ok(BackendRequest::StartHybridDiscovery),
0x02 => Ok(BackendRequest::StartNfcDiscovery),
0x03 => Ok(BackendRequest::StartUsbDiscovery),
0x04 => {
let s: Str = value.downcast_ref()?;
if s.is_empty() {
return Err(zvariant::Error::invalid_length(
s.len(),
&"a non-empty string",
));
}
Ok(BackendRequest::EnterClientPin(s.as_str().to_string()))
}
0x05 => {
let s: Str = value.downcast_ref()?;
if s.is_empty() {
return Err(zvariant::Error::invalid_length(
s.len(),
&"a non-empty string",
));
}
Ok(BackendRequest::SelectCredential(s.as_str().to_string()))
}
0x06 => Ok(BackendRequest::CancelRequest),
_ => Err(zvariant::Error::Message(format!(
"Unknown BackendRequest tag : {tag}"
))),
}
}
}

impl Serialize for BackendRequest {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let structure: Structure = self.into();
structure.serialize(serializer)
}
}

impl<'de> Deserialize<'de> for BackendRequest {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let d = Structure::deserializer_for_signature(TAG_VALUE_SIGNATURE).map_err(|err| {
D::Error::custom(format!(
"could not create deserializer for tag-value struct: {err}"
))
})?;
let structure = d.deserialize(deserializer)?;
(&structure).try_into().map_err(|err| {
D::Error::custom(format!(
"could not deserialize structure into BackendRequest: {err}"
))
})
}
}

#[derive(Clone, Debug, DeserializeDict, Type)]
#[zvariant(signature = "dict")]
pub struct CreateCredentialRequest {
Expand Down Expand Up @@ -412,10 +507,7 @@ impl From<GetPublicKeyCredentialResponse> for GetCredentialResponse {
}
}

/// Identifier for a request to be used for cancellation.
pub type RequestId = u32;

#[derive(Serialize, Deserialize, Type)]
#[derive(Clone, Debug, Serialize, Deserialize, Type)]
pub struct ViewRequest {
pub operation: Operation,

Expand All @@ -435,7 +527,7 @@ pub struct ViewRequest {
pub window_handle: Optional<WindowHandle>,
}

#[derive(Type, PartialEq, Debug)]
#[derive(Clone, Debug, PartialEq, Type)]
#[zvariant(signature = "s")]
pub enum WindowHandle {
Wayland(String),
Expand Down
60 changes: 58 additions & 2 deletions credentialsd-ui/src/client.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
use async_std::stream::Stream;
use credentialsd_common::{client::FlowController, server::RequestId};
use async_std::{
channel::{Receiver, Sender},
stream::Stream,
sync::Mutex as AsyncMutex,
};
use credentialsd_common::{
client::FlowController,
model::{BackendRequest, RequestId},
server::BackgroundEvent,
};
use futures_lite::StreamExt;
use zbus::Connection;

Expand Down Expand Up @@ -118,3 +126,51 @@ impl FlowController for DbusCredentialClient {
Ok(())
}
}

#[derive(Debug)]
pub struct FlowControlClient {
pub tx: Sender<BackendRequest>,
pub rx: AsyncMutex<Option<Receiver<BackgroundEvent>>>,
}

impl FlowControlClient {
pub async fn discover_hybrid_authenticators(&self) -> Result<(), ()> {
self.send(BackendRequest::StartHybridDiscovery).await
}

pub async fn discover_nfc_authenticators(&mut self) -> Result<(), ()> {
self.send(BackendRequest::StartNfcDiscovery).await
}

pub async fn discover_usb_authenticators(&mut self) -> Result<(), ()> {
self.send(BackendRequest::StartUsbDiscovery).await
}

pub async fn enter_client_pin(&mut self, pin: String) -> Result<(), ()> {
self.send(BackendRequest::EnterClientPin(pin)).await
}

pub async fn select_credential(&self, credential_id: String) -> Result<(), ()> {
self.send(BackendRequest::SelectCredential(credential_id))
.await
}

pub async fn cancel_request(&self) -> Result<(), ()> {
self.send(BackendRequest::CancelRequest).await
}

/// Returns a channel for background events.
/// Can only be called once; returns an error if the subscription has already been taken.
pub async fn subscribe(&mut self) -> Result<Receiver<BackgroundEvent>, ()> {
self.rx.lock().await.take().ok_or_else(|| {
tracing::error!("Subscribe has already been called.");
})
}

async fn send(&self, request: BackendRequest) -> Result<(), ()> {
match self.tx.send(request).await {
Ok(_) => Ok(()),
Err(_) => Err(()),
}
}
}
Loading
Loading