1
//! POD types for the biometric-authentication surface
2
//! (SUPER_PLAN_2 §1 feature 4 + research/02).
3
//!
4
//! Defined here in `azul-core` so the request config and result types
5
//! can cross the FFI without `azul-layout` having to be a dependency.
6
//! The stateful side (latest result, sync availability, async result
7
//! channel) lives in `azul_layout::managers::biometric::BiometricManager`
8
//! and re-exports these types for the existing import paths.
9
//!
10
//! Unlike geolocation (a continuous probe-driven subscription), biometric
11
//! auth is **request-driven**: a callback asks `App::request_biometric_auth`
12
//! with a [`BiometricPrompt`]; the OS draws its own modal; the platform
13
//! backend parks the [`BiometricResult`] in the manager's async channel
14
//! when the user responds.
15

            
16
use azul_css::AzString;
17

            
18
/// What biometric hardware the device can authenticate with right now.
19
///
20
/// This is the *sync availability probe* (iOS `LAContext.biometryType` /
21
/// `canEvaluatePolicy`; Android `BiometricManager.canAuthenticate`), not
22
/// the outcome of an auth attempt — that is [`BiometricResult`].
23
/// `NotAvailable` covers "no sensor", "not enrolled", and "disabled by
24
/// policy" alike; callers that need to distinguish those use the richer
25
/// per-attempt [`BiometricResult`] variants.
26
#[repr(C)]
27
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
28
pub enum BiometricKind {
29
    /// No usable biometric sensor (absent, unenrolled, or disabled).
30
    NotAvailable,
31
    /// Fingerprint reader (Touch ID, Android fingerprint, Windows Hello
32
    /// fingerprint).
33
    Fingerprint,
34
    /// Face recognition (Face ID, Android face unlock, Windows Hello face).
35
    Face,
36
    /// Iris scanner (Samsung legacy, some Android OEMs).
37
    Iris,
38
}
39

            
40
impl Default for BiometricKind {
41
    fn default() -> Self {
42
        BiometricKind::NotAvailable
43
    }
44
}
45

            
46
impl BiometricKind {
47
    /// `true` for any real sensor — i.e. anything except `NotAvailable`.
48
    /// Lets the demo gate decide whether to even offer a biometric unlock.
49
2
    pub fn is_available(&self) -> bool {
50
2
        !matches!(self, BiometricKind::NotAvailable)
51
2
    }
52
}
53

            
54
/// The outcome of one `request_biometric_auth` attempt, delivered to the
55
/// caller's completion callback once the OS prompt resolves.
56
///
57
/// Maps onto every platform's result enum: iOS `LAError`, Android
58
/// `BiometricPrompt.AuthenticationCallback`, Windows
59
/// `UserConsentVerificationResult`, Linux polkit / PAM (research/02 §6).
60
#[repr(C)]
61
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
62
pub enum BiometricResult {
63
    /// The user matched their face / finger / iris. Unlock granted.
64
    Authenticated,
65
    /// The user presented a biometric but it did not match (wrong
66
    /// finger / face). Distinct from `Cancelled` — the prompt is still
67
    /// up or retries were exhausted without a deliberate cancel.
68
    Failed,
69
    /// The user dismissed the prompt (tapped Cancel / pressed back).
70
    Cancelled,
71
    /// Biometrics failed but the user authenticated via the OS passcode
72
    /// / PIN / device-credential fallback. Still a successful unlock —
73
    /// only delivered when [`BiometricPrompt::allow_device_credential`]
74
    /// was set.
75
    FellBackToPasscode,
76
    /// No usable biometric is enrolled / available on this device, so
77
    /// the prompt could not be shown (Linux degraded path, or hardware
78
    /// absent). Pairs with [`BiometricKind::NotAvailable`].
79
    Unavailable,
80
    /// A platform error occurred (sensor busy, lockout, key invalidated,
81
    /// or an unmapped native error code).
82
    Error,
83
}
84

            
85
impl BiometricResult {
86
    /// `true` when the user successfully unlocked — either by biometric
87
    /// match (`Authenticated`) or by the OS passcode fallback
88
    /// (`FellBackToPasscode`). The vault gate keys off this.
89
9
    pub fn is_success(&self) -> bool {
90
5
        matches!(
91
9
            self,
92
            BiometricResult::Authenticated | BiometricResult::FellBackToPasscode
93
        )
94
9
    }
95
}
96

            
97
// FFI Option wrapper. `CallbackInfo::get_biometric_result() ->
98
// Option<BiometricResult>` returns `None` until the first request
99
// completes; this is the no-codegen prerequisite for that accessor
100
// (mirrors `OptionLocationFix`). The `availability` accessor returns a
101
// bare `BiometricKind` (NotAvailable encodes "none"), so no Option there.
102
impl_option!(
103
    BiometricResult,
104
    OptionBiometricResult,
105
    [Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash]
106
);
107

            
108
/// Configuration for one biometric-auth request — what the OS prompt
109
/// shows and which fallbacks are allowed. Passed to
110
/// `App::request_biometric_auth`.
111
///
112
/// Strings are plain [`AzString`]; an empty string means "use the
113
/// platform default label" (so callers only override what they care
114
/// about). This keeps the public surface engine-agnostic and codegen
115
/// stays a single struct with no nested `Option<String>` wrappers.
116
#[repr(C)]
117
#[derive(Debug, Clone, PartialEq, Eq)]
118
pub struct BiometricPrompt {
119
    /// Reason shown in the OS prompt — required on iOS
120
    /// (`localizedReason`; the `NSFaceIDUsageDescription` plist key is
121
    /// declared separately), shown as the Android subtitle and the
122
    /// Windows / Linux message line. Empty is accepted but discouraged.
123
    pub reason: AzString,
124
    /// Label for the cancel / negative button (Android requires one;
125
    /// iOS `localizedCancelTitle`). Empty → platform default ("Cancel").
126
    pub cancel_label: AzString,
127
    /// Allow the OS passcode / PIN / device-credential fallback when
128
    /// biometrics fail or aren't enrolled. When the user takes that
129
    /// path the result is [`BiometricResult::FellBackToPasscode`].
130
    /// `false` = biometric-only (iOS `…WithBiometrics`, Android
131
    /// `BIOMETRIC_STRONG` without `DEVICE_CREDENTIAL`).
132
    pub allow_device_credential: bool,
133
}
134

            
135
impl Default for BiometricPrompt {
136
168
    fn default() -> Self {
137
168
        Self {
138
168
            reason: AzString::from_const_str(""),
139
168
            cancel_label: AzString::from_const_str(""),
140
168
            allow_device_credential: false,
141
168
        }
142
168
    }
143
}
144

            
145
impl BiometricPrompt {
146
    /// Convenience constructor: a biometric-only prompt showing `reason`,
147
    /// with the platform-default cancel label and no passcode fallback.
148
126
    pub fn new(reason: AzString) -> Self {
149
126
        Self {
150
126
            reason,
151
126
            ..Self::default()
152
126
        }
153
126
    }
154
}