gatekeeper_members/
lib.rs

1pub use gatekeeper_core::RealmType;
2use gatekeeper_core::{GatekeeperReader, NfcTag, Realm, UndifferentiatedTag};
3use reqwest::header::AUTHORIZATION;
4use reqwest::StatusCode;
5use serde_json::Value;
6use std::env;
7use std::result::Result;
8use std::thread;
9use std::time::Duration;
10
11pub struct GateKeeperMemberListener<'a> {
12    nfc_device: GatekeeperReader<'a>,
13    http: reqwest::blocking::Client,
14
15    // Passed to GK-MQTT to resolve users
16    server_token: String,
17    // HTTP Endpoint
18    endpoint: String,
19
20    // Safeguard against double-scans
21    just_scanned: bool,
22
23    // Route URL
24    route: &'static str,
25}
26
27pub enum FetchError {
28    NotFound,
29    ParseError,
30    NetworkError,
31    Unknown,
32}
33
34trait RealmTypeExt {
35    fn get_route(&self) -> &'static str;
36    fn env_name(&self) -> &'static str;
37    fn get_auth_key(&self) -> Vec<u8>;
38    fn get_read_key(&self) -> Vec<u8>;
39    fn get_desfire_signing_public_key(&self) -> Vec<u8>;
40    fn get_mobile_decryption_private_key(&self) -> Vec<u8>;
41    fn get_mobile_signing_private_key(&self) -> Vec<u8>;
42}
43
44impl RealmTypeExt for RealmType {
45    fn get_route(&self) -> &'static str {
46        match self {
47            Self::Door => "doors",
48            Self::Drink => "drink",
49            Self::MemberProjects => "projects",
50        }
51    }
52    fn env_name(&self) -> &'static str {
53        match self {
54            Self::Door => "DOORS",
55            Self::Drink => "DRINK",
56            Self::MemberProjects => "MEMBER_PROJECTS",
57        }
58    }
59    fn get_auth_key(&self) -> Vec<u8> {
60        env::var(format!("GK_REALM_{}_AUTH_KEY", self.env_name()))
61            .unwrap()
62            .into_bytes()
63    }
64    fn get_read_key(&self) -> Vec<u8> {
65        env::var(format!("GK_REALM_{}_READ_KEY", self.env_name()))
66            .unwrap()
67            .into_bytes()
68    }
69    fn get_desfire_signing_public_key(&self) -> Vec<u8> {
70        env::var(format!("GK_REALM_{}_PUBLIC_KEY", self.env_name()))
71            .unwrap()
72            .into_bytes()
73    }
74    fn get_mobile_decryption_private_key(&self) -> Vec<u8> {
75        env::var(format!(
76            "GK_REALM_{}_MOBILE_CRYPT_PRIVATE_KEY",
77            self.env_name()
78        ))
79        .unwrap()
80        .into_bytes()
81    }
82    fn get_mobile_signing_private_key(&self) -> Vec<u8> {
83        env::var(format!("GK_REALM_{}_MOBILE_PRIVATE_KEY", self.env_name()))
84            .unwrap()
85            .into_bytes()
86    }
87}
88
89impl<'a> GateKeeperMemberListener<'a> {
90    pub fn new(conn_str: String, realm_type: RealmType) -> Option<Self> {
91        let realm = Realm::new(
92            realm_type,
93            realm_type.get_auth_key(),
94            realm_type.get_read_key(),
95            &realm_type.get_desfire_signing_public_key(),
96            &realm_type.get_mobile_decryption_private_key(),
97            &realm_type.get_mobile_signing_private_key(),
98            None,
99        );
100
101        Some(GateKeeperMemberListener {
102            nfc_device: GatekeeperReader::new(conn_str, realm)?,
103            http: reqwest::blocking::Client::new(),
104
105            server_token: env::var("GK_SERVER_TOKEN").unwrap(),
106            just_scanned: false,
107            endpoint: env::var("GK_HTTP_ENDPOINT")
108                .unwrap_or_else(|_| "http://localhost:3000".to_string()),
109            route: realm_type.get_route(),
110        })
111    }
112
113    pub fn poll_for_tag(&mut self) -> Option<UndifferentiatedTag> {
114        let nearby_tags = self.nfc_device.get_nearby_tags();
115        if nearby_tags.is_empty() {
116            self.just_scanned = false;
117        }
118        if self.just_scanned {
119            thread::sleep(Duration::from_millis(250));
120            return None;
121        }
122        self.just_scanned = !nearby_tags.is_empty();
123        nearby_tags.into_iter().next()
124    }
125
126    pub fn poll_for_user(&mut self) -> Option<String> {
127        self.poll_for_tag().and_then(|tag| tag.authenticate().ok())
128    }
129
130    pub fn wait_for_user(&mut self) -> Option<String> {
131        loop {
132            if let Some(association) = self.poll_for_user() {
133                return Some(association);
134            }
135        }
136    }
137
138    pub fn fetch_user(&self, key: String) -> Result<Value, FetchError> {
139        match self
140            .http
141            .get(format!(
142                "{}/{}/by-key/{}",
143                self.endpoint.clone(),
144                self.route,
145                &key
146            ))
147            .header(AUTHORIZATION, self.server_token.clone())
148            .send()
149        {
150            Ok(res) => match res.status() {
151                StatusCode::OK => {
152                    if let Ok(text) = res.text() {
153                        if let Ok(value) = serde_json::from_str(&text) {
154                            Ok(value)
155                        } else {
156                            Err(FetchError::ParseError)
157                        }
158                    } else {
159                        Err(FetchError::ParseError)
160                    }
161                }
162                StatusCode::NOT_FOUND => Err(FetchError::NotFound),
163                _ => Err(FetchError::Unknown),
164            },
165            Err(err) => {
166                println!("Error fetching data for key: {:?}", err);
167                Err(FetchError::NetworkError)
168            }
169        }
170    }
171}