Skip to main content

forge_server/surface/
biometric.rs

1//! `biometric_enrolments()`: the device enrolments the settings page lists
2//! and the unlock card reads.
3
4use forge_primitives::biometric::BiometricEnrolment;
5
6use super::ViewSurface;
7
8impl ViewSurface {
9    /// Every device's enrolment, with no wrapped blob in it: a view draws
10    /// which purposes a device enrolled and when each was last released, and
11    /// the bytes stay behind a release.
12    ///
13    /// A store that cannot be read is an error rather than an empty list, so
14    /// the page can tell "no device is enrolled" from "the list could not be
15    /// read".
16    pub fn biometric_enrolments(&self) -> Result<Vec<BiometricEnrolment>, String> {
17        self.workspace.biometric_enrolments()
18    }
19}
20
21#[cfg(test)]
22mod tests {
23    use std::sync::Arc;
24
25    use forge_primitives::biometric::BiometricBlob;
26    use forge_workspace::Command;
27
28    use super::*;
29
30    /// The two commands a client drives answer with what a view reads: an
31    /// enrol lands in the list, and a release hands back the same bytes.
32    #[test]
33    fn an_enrolment_is_what_the_view_reads_and_what_a_release_answers() {
34        let (workspace, _dir) = crate::surface::testing::workspace();
35        let id = uuid::Uuid::new_v4();
36        let blob = BiometricBlob::encode(b"wrapped-secret");
37
38        workspace
39            .dispatch(Command::BiometricEnrol {
40                enrolment: id,
41                device: "Ved's MacBook Pro".to_owned(),
42                purpose: "vault".to_owned(),
43                blob: blob.blob.clone(),
44                respond: None,
45            })
46            .expect("the enrol routes");
47
48        let view = ViewSurface::new(Arc::clone(&workspace)).biometric_enrolments().expect("read");
49        assert_eq!(view.len(), 1);
50        assert_eq!(view[0].id, id);
51        assert_eq!(view[0].device, "Ved's MacBook Pro");
52        assert_eq!(view[0].purposes[0].purpose, "vault");
53        assert!(
54            serde_json::to_value(&view[0]).expect("renders").get("blob").is_none(),
55            "the list a view reads carries no wrapped blob",
56        );
57
58        let (sender, mut receiver) = tokio::sync::oneshot::channel();
59        workspace
60            .dispatch(Command::BiometricRelease {
61                enrolment: id,
62                purpose: "vault".to_owned(),
63                respond: Some(sender),
64            })
65            .expect("the release routes");
66        let answer = receiver.try_recv().expect("answered").expect("the purpose is enrolled");
67        assert_eq!(answer, blob);
68    }
69
70    /// A release naming a purpose the device never enrolled answers `null`,
71    /// which is the state a client falls back to the password on.
72    #[test]
73    fn a_release_for_an_unenrolled_purpose_answers_nothing() {
74        let (workspace, _dir) = crate::surface::testing::workspace();
75        let (sender, mut receiver) = tokio::sync::oneshot::channel();
76
77        workspace
78            .dispatch(Command::BiometricRelease {
79                enrolment: uuid::Uuid::new_v4(),
80                purpose: "vault".to_owned(),
81                respond: Some(sender),
82            })
83            .expect("the release routes");
84
85        assert_eq!(receiver.try_recv().expect("answered"), None);
86    }
87}