forge_primitives/
biometric.rs1use std::time::SystemTime;
15
16use serde::{Deserialize, Serialize};
17use uuid::Uuid;
18
19#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
22pub struct BiometricPurpose {
23 pub purpose: String,
25 pub created_at: SystemTime,
27 #[serde(default, skip_serializing_if = "Option::is_none")]
29 pub last_used_at: Option<SystemTime>,
30}
31
32#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
35pub struct BiometricEnrolment {
36 pub id: Uuid,
39 pub device: String,
41 pub created_at: SystemTime,
43 pub purposes: Vec<BiometricPurpose>,
44}
45
46#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
49pub struct BiometricBlob {
50 pub blob: String,
52}
53
54impl BiometricBlob {
55 pub fn encode(blob: &[u8]) -> Self {
57 use base64::Engine as _;
58 Self { blob: base64::engine::general_purpose::STANDARD.encode(blob) }
59 }
60
61 pub fn decode(&self) -> Result<Vec<u8>, String> {
64 use base64::Engine as _;
65 base64::engine::general_purpose::STANDARD
66 .decode(self.blob.as_bytes())
67 .map_err(|error| format!("the wrapped blob is not base64: {error}"))
68 }
69}
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74 use std::time::Duration;
75
76 fn an_enrolment() -> BiometricEnrolment {
77 BiometricEnrolment {
78 id: Uuid::nil(),
79 device: "Ved's MacBook Pro".to_owned(),
80 created_at: SystemTime::UNIX_EPOCH + Duration::from_secs(1_790_000_000),
81 purposes: vec![BiometricPurpose {
82 purpose: "vault".to_owned(),
83 created_at: SystemTime::UNIX_EPOCH + Duration::from_secs(1_790_000_000),
84 last_used_at: Some(SystemTime::UNIX_EPOCH + Duration::from_secs(1_790_000_100)),
85 }],
86 }
87 }
88
89 #[test]
92 fn an_enrolment_round_trips() {
93 let enrolment = an_enrolment();
94 let json = serde_json::to_string(&enrolment).expect("an enrolment serialises");
95 let back: BiometricEnrolment = serde_json::from_str(&json).expect("an enrolment decodes");
96 assert_eq!(enrolment, back);
97 }
98
99 #[test]
102 fn the_view_carries_no_blob() {
103 let value = serde_json::to_value(an_enrolment()).expect("an enrolment serialises");
104 let keys = |value: &serde_json::Value| {
105 let mut keys: Vec<String> =
106 value.as_object().expect("an enrolment is an object").keys().cloned().collect();
107 keys.sort_unstable();
108 keys
109 };
110 assert_eq!(keys(&value), ["created_at", "device", "id", "purposes"]);
111 assert_eq!(keys(&value["purposes"][0]), ["created_at", "last_used_at", "purpose"]);
112 assert!(
113 !value.to_string().contains("blob"),
114 "the enrolment list names no blob at all: {value}",
115 );
116 }
117
118 #[test]
121 fn a_blob_encodes_and_decodes_to_the_same_bytes() {
122 let bytes = vec![0u8, 1, 2, 250, 255];
123 let wire = BiometricBlob::encode(&bytes);
124 assert_eq!(wire.decode().expect("a base64 blob decodes"), bytes);
125 }
126
127 #[test]
130 fn a_blob_that_is_not_base64_is_refused() {
131 let wire = BiometricBlob { blob: "not base64!".to_owned() };
132 assert!(wire.decode().is_err());
133 }
134}