Skip to content
Draft
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
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions credentialsd-common/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub trait FlowController {
Output = Result<Pin<Box<dyn Stream<Item = BackgroundEvent> + Send + 'static>>, ()>,
> + Send;
fn enter_client_pin(&mut self, pin: String) -> impl Future<Output = Result<(), ()>> + Send;
fn set_device_pin(&mut self, pin: String) -> impl Future<Output = Result<(), ()>> + Send;
fn select_credential(
&self,
credential_id: String,
Expand Down
42 changes: 39 additions & 3 deletions credentialsd-common/src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,9 +132,11 @@ pub enum ViewUpdate {
UsbNeedsPin { attempts_left: Option<u32> },
UsbNeedsUserVerification { attempts_left: Option<u32> },
UsbNeedsUserPresence,
UsbPinNotSet { error: Option<PinNotSetError> },

NfcNeedsPin { attempts_left: Option<u32> },
NfcNeedsUserVerification { attempts_left: Option<u32> },
NfcPinNotSet { error: Option<PinNotSetError> },

HybridNeedsQrCode(String),
HybridConnecting,
Expand All @@ -145,6 +147,35 @@ pub enum ViewUpdate {
Failed(String),
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PinNotSetError {
/// PIN too short
PinTooShort,
/// PIN too long
PinTooLong,
/// PIN violates PinPolicy
PinPolicyViolation,
}

impl PinNotSetError {
pub fn to_string(&self) -> String {
match self {
PinNotSetError::PinTooShort => String::from("Pin too short"),
PinNotSetError::PinTooLong => String::from("Pin too long"),
PinNotSetError::PinPolicyViolation => String::from("Pin policy violation"),
}
}

pub fn from_string(error: &str) -> Option<PinNotSetError> {
match error {
"Pin too short" => Some(PinNotSetError::PinTooShort),
"Pin too long" => Some(PinNotSetError::PinTooLong),
"Pin policy violation" => Some(PinNotSetError::PinPolicyViolation),
_ => None,
}
}
}

#[derive(Clone, Debug, Default)]
pub enum HybridState {
/// Default state, not listening for hybrid transport.
Expand Down Expand Up @@ -192,6 +223,11 @@ pub enum UsbState {
attempts_left: Option<u32>,
},

/// The device needs the PIN to be entered.
PinNotSet {
error: Option<PinNotSetError>,
},

/// The device needs on-device user verification.
NeedsUserVerification {
attempts_left: Option<u32>,
Expand Down Expand Up @@ -231,6 +267,9 @@ pub enum NfcState {
/// The device needs the PIN to be entered.
NeedsPin { attempts_left: Option<u32> },

/// The device needs the PIN to be entered.
PinNotSet { error: Option<PinNotSetError> },

/// The device needs on-device user verification.
NeedsUserVerification { attempts_left: Option<u32> },

Expand Down Expand Up @@ -271,8 +310,6 @@ pub enum Error {
/// Note that this is different than exhausting the PIN count that fully
/// locks out the device.
PinAttemptsExhausted,
/// The RP requires user verification, but the device has no PIN/Biometrics set.
PinNotSet,
// TODO: We may want to hide the details on this variant from the public API.
/// Something went wrong with the credential service itself, not the authenticator.
Internal(String),
Expand All @@ -284,7 +321,6 @@ impl Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::AuthenticatorError => f.write_str("AuthenticatorError"),
Self::PinNotSet => f.write_str("PinNotSet"),
Self::NoCredentials => f.write_str("NoCredentials"),
Self::CredentialExcluded => f.write_str("CredentialExcluded"),
Self::PinAttemptsExhausted => f.write_str("PinAttemptsExhausted"),
Expand Down
49 changes: 45 additions & 4 deletions credentialsd-common/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use zvariant::{
SerializeDict, Signature, Structure, StructureBuilder, Type, Value, signature::Fields,
};

use crate::model::{BackgroundEvent, Device, Operation, RequestingApplication};
use crate::model::{BackgroundEvent, Operation, PinNotSetError, RequestingApplication};

const TAG_VALUE_SIGNATURE: &Signature = &Signature::Structure(Fields::Static {
fields: &[&Signature::U8, &Signature::Variant],
Expand Down Expand Up @@ -182,7 +182,6 @@ impl TryFrom<&Value<'_>> for crate::model::Error {
let err_code: &str = value.downcast_ref()?;
let err = match err_code {
"AuthenticatorError" => crate::model::Error::AuthenticatorError,
"PinNotSet" => crate::model::Error::PinNotSet,
"NoCredentials" => crate::model::Error::NoCredentials,
"CredentialExcluded" => crate::model::Error::CredentialExcluded,
"PinAttemptsExhausted" => crate::model::Error::PinAttemptsExhausted,
Expand Down Expand Up @@ -346,6 +345,21 @@ impl From<&crate::model::UsbState> for Structure<'_> {
let value = Value::<'_>::from(error.to_string());
(0x0A, Some(value))
}
crate::model::UsbState::PinNotSet { error } => {
let value = error.as_ref().map(|x| {
Value::<'_>::from({
let this = &x;
match this {
PinNotSetError::PinTooShort => String::from("Pin too short"),
PinNotSetError::PinTooLong => String::from("Pin too long"),
PinNotSetError::PinPolicyViolation => {
String::from("Pin policy violation")
}
}
})
});
(0x0B, value)
}
};
tag_value_to_struct(tag, value)
}
Expand Down Expand Up @@ -400,14 +414,20 @@ impl TryFrom<&Structure<'_>> for crate::model::UsbState {
let err_code: &str = value.downcast_ref()?;
let err = match err_code {
"AuthenticatorError" => crate::model::Error::AuthenticatorError,
"PinNotSet" => crate::model::Error::PinNotSet,
"NoCredentials" => crate::model::Error::NoCredentials,
"CredentialExcluded" => crate::model::Error::CredentialExcluded,
"PinAttemptsExhausted" => crate::model::Error::PinAttemptsExhausted,
s => crate::model::Error::Internal(String::from(s)),
};
Ok(Self::Failed(err))
}
0x0B => {
let error = value
.downcast_ref::<&str>()
.ok()
.and_then(PinNotSetError::from_string);
Ok(Self::PinNotSet { error })
}
_ => Err(zvariant::Error::IncorrectType),
}
}
Expand Down Expand Up @@ -471,6 +491,21 @@ impl From<&crate::model::NfcState> for Structure<'_> {
let value = Value::<'_>::from(error.to_string());
(0x0A, Some(value))
}
crate::model::NfcState::PinNotSet { error } => {
let value = error.as_ref().map(|x| {
Value::<'_>::from({
let this = &x;
match this {
PinNotSetError::PinTooShort => String::from("Pin too short"),
PinNotSetError::PinTooLong => String::from("Pin too long"),
PinNotSetError::PinPolicyViolation => {
String::from("Pin policy violation")
}
}
})
});
(0x0B, value)
}
};
tag_value_to_struct(tag, value)
}
Expand Down Expand Up @@ -523,14 +558,20 @@ impl TryFrom<&Structure<'_>> for crate::model::NfcState {
let err_code: &str = value.downcast_ref()?;
let err = match err_code {
"AuthenticatorError" => crate::model::Error::AuthenticatorError,
"PinNotSet" => crate::model::Error::PinNotSet,
"NoCredentials" => crate::model::Error::NoCredentials,
"CredentialExcluded" => crate::model::Error::CredentialExcluded,
"PinAttemptsExhausted" => crate::model::Error::PinAttemptsExhausted,
s => crate::model::Error::Internal(String::from(s)),
};
Ok(Self::Failed(err))
}
0x0B => {
let error = value
.downcast_ref::<&str>()
.ok()
.and_then(PinNotSetError::from_string);
Ok(Self::PinNotSet { error })
}
_ => Err(zvariant::Error::IncorrectType),
}
}
Expand Down
85 changes: 85 additions & 0 deletions credentialsd-ui/data/resources/ui/window.ui
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,66 @@
</object>
</child>

<child>
<object class="GtkStackPage">
<property name="name">set_new_pin</property>
<property name="title" translatable="yes">Set a PIN</property>
<property name="child">
<object class="GtkBox">
<property name="orientation">vertical</property>

<child>
<object class="GtkLabel">
<property name="label" translatable="yes">Please choose a new PIN for your device.</property>
<property name="wrap">true</property>
</object>
</child>

<child>
<object class="GtkPasswordEntry" id="new_pin_primary_entry">
<property name="placeholder-text" translatable="yes">New PIN</property>
<signal name="changed" handler="handle_setting_pin_change" swapped="true"/>
</object>
</child>

<child>
<object class="GtkPasswordEntry" id="new_pin_confirm_entry">
<property name="placeholder-text" translatable="yes">Confirm PIN</property>
<signal name="changed" handler="handle_setting_pin_change" swapped="true"/>
</object>
</child>

<child>
<object class="GtkBox">
<property name="halign">end</property>
<property name="spacing">6</property>
<child>
<object class="GtkButton" id="new_pin_btn_close_window">
<property name="label" translatable="yes">Close</property>
<signal name="clicked" handler="handle_close_window" swapped="true"/>
</object>
</child>
<child>
<object class="GtkButton" id="new_pin_btn_continue">
<property name="label" translatable="yes">Continue</property>
<binding name="sensitive">
<lookup name="pin_fields_match">
<lookup name="view-model">CredentialsUiWindow</lookup>
</lookup>
</binding>
<style>
<class name="suggested-action"/>
</style>
<signal name="clicked" handler="handle_commit_new_pin" swapped="true"/>
</object>
</child>
</object>
</child>
</object>
</property>
</object>
</child>

<child>
<object class="GtkStackPage">
<property name="name">completed</property>
Expand Down Expand Up @@ -244,6 +304,31 @@
<property name="label" translatable="yes">Something went wrong while retrieving a credential. Please try again later or use a different authenticator.</property>
</object>
</child>
<child>
<object class="GtkBox">
<property name="halign">end</property>
<property name="spacing">6</property>
<binding name="visible">
<lookup name="start_setting_new_pin_visible">
<lookup name="view-model">
CredentialsUiWindow
</lookup>
</lookup>
</binding>
<child>
<object class="GtkButton" id="failed_close_window">
<property name="label" translatable="yes">Close</property>
<signal name="clicked" handler="handle_close_window" swapped="true"/>
</object>
</child>
<child>
<object class="GtkButton" id="start_setting_new_pin">
<property name="label" translatable="yes">Set PIN on device</property>
<signal name="clicked" handler="handle_start_setting_new_pin" swapped="true"/>
</object>
</child>
</object>
</child>
</object>
</property>
</object>
Expand Down
Loading
Loading