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
998 changes: 642 additions & 356 deletions Cargo.lock

Large diffs are not rendered by default.

7 changes: 4 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,10 @@ objc2-ui-kit = { version = "0.3.1", default-features = false, feat


## Linux-specific dependencies used in multiple crates in the workspace.
polkit = "=0.17.0"
gio = "=0.17.0"

# polkit = "=0.17.0"
# gio = "=0.17.0"
zbus = "5.12.0"
zbus_polkit = "5.0.0"

## Windows-specific dependencies used in multiple crates in the workspace.
windows-core = { version = "0.56.0", default-features = false }
Expand Down
7 changes: 5 additions & 2 deletions crates/authentication/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ edition.workspace = true
authors = [
"Kevin Boos <[email protected]>",
"Klim Tsoutsman <[email protected]>",
"Tyrese Luo <[email protected]>",
"Project Robius Maintainers",
]
description = "Rust abstractions for multi-platform native authentication: biometrics, fingerprint, password, screen lock, TouchID, FaceID, Windows Hello, etc."
Expand Down Expand Up @@ -50,8 +51,10 @@ objc2-foundation = { workspace = true, features = ["NSError", "NSString"] }
# optional = true

[target.'cfg(target_os = "linux")'.dependencies]
polkit.workspace = true
gio.workspace = true
# polkit.workspace = true
# gio.workspace = true
zbus.workspace = true
zbus_polkit = { workspace = true }

[target.'cfg(target_os = "windows")'.dependencies]
retry.workspace = true
Expand Down
67 changes: 67 additions & 0 deletions crates/authentication/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,80 @@ To use this crate on Android, you must add the following to your app's `AndroidM
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
```

## Usage on Linux

On Linux, `robius-authentication` uses **polkit** to request authorization via the
desktop environment's native authentication prompt (GNOME/KDE/etc).

> [!IMPORTANT]
> **Ensure a polkit agent is running**
>
> The prompt is displayed by a polkit authentication agent (GNOME/KDE usually start one automatically).
> If no agent is running (headless/SSH), no prompt will appear and auth will fail.

### Add a polkit action policy file


#### Development & debugging

During development and debugging, you can simplify the process using the following steps:

1. Place the policy file within the project, for example in resources/com.yourapp.policy.

2. Install it once on your development machine using sudo:

```bash
sudo install -Dm644 resources/com.yourapp.policy \
/usr/share/polkit-1/actions/com.yourapp.policy
```

3. Verify functionality with pkaction:

```bash
pkaction --action-id com.yourapp.authenticate --verbose
```

Log back into your desktop session (or restart polkitd), then run your example.


Plicy file example:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<policyconfig>
<action id="com.yourapp.authenticate">
<description>Authenticate to use YourApp</description>
<message>Authentication is required</message>
<defaults>
<allow_active>auth_admin_keep</allow_active>
</defaults>
</action>
</policyconfig>
````
####

> [!TIP]
> Automatic addition during runtime is not recommended and should generally be avoided.
The reason is not technical feasibility, but rather security/distribution policy restrictions:
>
> polkit actions (.policy files) are system-level security policies. By design, they must be written by package managers or administrators during installation. Ordinary applications should not be allowed to modify system security configurations during runtime.
>
> Writing to `/usr/share/...` during runtime requires root privileges; allowing an app to elevate privileges to modify policy files is flagged as a security red flag by many distributions.
>
> The only recommended “automatic method” is installation-time automation.
This means packaging the `.policy` file alongside your deb/rpm/aur/flatpak package during installation.

## Example

```rust
use robius_authentication::{
AndroidText, BiometricStrength, Context, Policy, PolicyBuilder, Text, WindowsText,
};

// Linux ignores policy options like biometrics/password (kept for parity).
let policy: Policy = PolicyBuilder::new()
// The action ID must match your `.policy`.
.action_id("com.yourapp.authenticate") // optional if using default
.biometrics(Some(BiometricStrength::Strong))
.password(true)
.companion(true)
Expand All @@ -61,6 +127,7 @@ let callback = |auth_result| {
}
};


Context::new(())
.authenticate(text, &policy, callback)
.expect("Authentication failed");
Expand Down
60 changes: 46 additions & 14 deletions crates/authentication/examples/simple_authentication.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ use robius_authentication::{
AndroidText, BiometricStrength, Context, Policy, PolicyBuilder, Text, WindowsText,
};

/// If you're targeting Linux, make sure to set an appropriate action ID
/// in your policy. This is required for Polkit authentication.
/// See more README.md (How to use in Linux).
const POLICY: Policy = PolicyBuilder::new()
.action_id("com.yourapp.authenticate")
.biometrics(Some(BiometricStrength::Strong))
.password(true)
.companion(true)
Expand All @@ -22,19 +26,47 @@ const TEXT: Text = Text {
fn main() {
let context = Context::new(());

let res = context.authenticate(
TEXT,
&POLICY,
|result| match result {
Ok(_) => println!("Authentication successful"),
Err(e) => println!("Authentication failed: {:?}", e),
},
);

// Note: if `res` is `Ok`, the authentication did not necessarily succeed.
// The callback will be called with the result of the authentication.
// If `res` is `Err`, it indicates an error in the authentication policy or context setup.
if let Err(e) = res {
eprintln!("Authentication failed: {:?}", e);
#[cfg(target_os = "linux")]
{
use std::sync::mpsc;
use std::time::Duration;

let (tx, rx) = mpsc::channel();

let res = context.authenticate(TEXT, &POLICY, move |result| {
let _ = tx.send(result);
});

if let Err(e) = res {
eprintln!("Authentication request failed early: {:?}", e);
return;
}

match rx.recv_timeout(Duration::from_secs(120)) {
Ok(Ok(_)) => println!("Authentication successful"),
Ok(Err(e)) => println!("Authentication failed: {:?}", e),
Err(_) => println!("Authentication timed out / callback not fired"),
}

return;
}

#[cfg(not(target_os = "linux"))]
{
let res = context.authenticate(
TEXT,
&POLICY,
|result| match result {
Ok(_) => println!("Authentication successful"),
Err(e) => println!("Authentication failed: {:?}", e),
},
);

// Note: if `res` is `Ok`, the authentication did not necessarily succeed.
// The callback will be called with the result of the authentication.
// If `res` is `Err`, it indicates an error in the authentication policy or context setup.
if let Err(e) = res {
eprintln!("Authentication failed: {:?}", e);
}
}
}
Binary file added crates/authentication/img/linux-screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion crates/authentication/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
pub type Result<T> = std::result::Result<T, Error>;

/// An error produced during authentication.
#[derive(Debug)]
#[derive(Debug, Clone)]
pub enum Error {
// TODO: Reexport jni::errors::Error
// TODO: Remove target cfg
Expand Down
10 changes: 10 additions & 0 deletions crates/authentication/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,16 @@ impl PolicyBuilder {
}
}

#[inline]
#[must_use]
#[cfg(target_os = "linux")]
pub const fn action_id(self, id: &'static str) -> Self {
Self {
inner: self.inner.action_id(id),
}
}


/// Configures biometric authentication with the given strength.
///
/// The strength only has an effect on Android, see [`BiometricStrength`]
Expand Down
6 changes: 3 additions & 3 deletions crates/authentication/src/sys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ cfg_if::cfg_if! {
} else if #[cfg(target_vendor = "apple")] {
mod apple;
pub(crate) use apple::*;
// } else if #[cfg(target_os = "linux")] { // linux is currently unsupported
// mod linux;
// pub(crate) use linux::*;
} else if #[cfg(target_os = "linux")] {
mod linux;
pub(crate) use linux::*;
} else if #[cfg(target_os = "windows")] {
mod windows;
pub(crate) use windows::*;
Expand Down
84 changes: 0 additions & 84 deletions crates/authentication/src/sys/linux.rs

This file was deleted.

Loading