Skip to main content

alibi_core/store/stateless/
device_codes.rs

1//! Native device grants share the no-database identity lifetime.
2use crate::store::DeviceCodeStore;
3use crate::store::stateless::StatelessStore;
4use crate::{AuthError, AuthResult, CreateDeviceCode, DeviceCode, UpdateDeviceCode};
5use async_trait::async_trait;
6
7#[async_trait]
8impl DeviceCodeStore for StatelessStore {
9    async fn create_device_code(&self, input: CreateDeviceCode) -> AuthResult<DeviceCode> {
10        self.create_device_code_with_fields(input, serde_json::Map::new())
11            .await
12    }
13    async fn create_device_code_with_fields(
14        &self,
15        input: CreateDeviceCode,
16        fields: serde_json::Map<String, serde_json::Value>,
17    ) -> AuthResult<DeviceCode> {
18        let device_code = DeviceCode {
19            id: uuid::Uuid::new_v4().to_string(),
20            device_code: input.device_code,
21            user_code: input.user_code,
22            user_id: input.user_id,
23            expires_at: input.expires_at,
24            status: input.status,
25            last_polled_at: input.last_polled_at,
26            polling_interval: input.polling_interval,
27            client_id: input.client_id,
28            scope: input.scope,
29        };
30        let mut state = self.lock()?;
31        _ = state
32            .device_code_fields
33            .insert(device_code.id.clone(), fields);
34        _ = state
35            .device_codes
36            .insert(device_code.id.clone(), device_code.clone());
37        Ok(device_code)
38    }
39    async fn device_code_fields(
40        &self,
41        id: &str,
42    ) -> AuthResult<serde_json::Map<String, serde_json::Value>> {
43        Ok(self
44            .lock()?
45            .device_code_fields
46            .get(id)
47            .cloned()
48            .unwrap_or_default())
49    }
50    async fn consume_device_code(
51        &self,
52        id: &str,
53        status: &str,
54        ownership: &serde_json::Map<String, serde_json::Value>,
55    ) -> AuthResult<Option<DeviceCode>> {
56        let mut state = self.lock()?;
57        let matches = state
58            .device_codes
59            .get(id)
60            .is_some_and(|row| row.status == status)
61            && ownership.iter().all(|(key, value)| {
62                state
63                    .device_code_fields
64                    .get(id)
65                    .and_then(|fields| fields.get(key))
66                    == Some(value)
67            });
68        if !matches {
69            return Ok(None);
70        }
71        _ = state.device_code_fields.shift_remove(id);
72        Ok(state.device_codes.shift_remove(id))
73    }
74    async fn get_device_code_by_device_code(
75        &self,
76        device_code: &str,
77    ) -> AuthResult<Option<DeviceCode>> {
78        Ok(self
79            .lock()?
80            .device_codes
81            .values()
82            .find(|value| value.device_code == device_code)
83            .cloned())
84    }
85
86    async fn get_device_code_by_user_code(
87        &self,
88        user_code: &str,
89    ) -> AuthResult<Option<DeviceCode>> {
90        Ok(self
91            .lock()?
92            .device_codes
93            .values()
94            .find(|value| value.user_code == user_code)
95            .cloned())
96    }
97
98    async fn update_device_code(
99        &self,
100        id: &str,
101        update: UpdateDeviceCode,
102    ) -> AuthResult<DeviceCode> {
103        let mut state = self.lock()?;
104        let device_code = state
105            .device_codes
106            .get_mut(id)
107            .ok_or_else(|| AuthError::not_found("Device code not found"))?;
108
109        if let Some(status) = update.status {
110            device_code.status = status;
111        }
112        if let Some(user_id) = update.user_id {
113            device_code.user_id = user_id;
114        }
115        if let Some(last_polled_at) = update.last_polled_at {
116            device_code.last_polled_at = last_polled_at;
117        }
118
119        Ok(device_code.clone())
120    }
121
122    async fn update_device_code_if_status(
123        &self,
124        id: &str,
125        current_status: &str,
126        update: UpdateDeviceCode,
127    ) -> AuthResult<bool> {
128        let mut state = self.lock()?;
129        let Some(device_code) = state.device_codes.get_mut(id) else {
130            return Ok(false);
131        };
132
133        if device_code.status != current_status {
134            return Ok(false);
135        }
136
137        if let Some(status) = update.status {
138            device_code.status = status;
139        }
140        if let Some(user_id) = update.user_id {
141            device_code.user_id = user_id;
142        }
143        if let Some(last_polled_at) = update.last_polled_at {
144            device_code.last_polled_at = last_polled_at;
145        }
146        Ok(true)
147    }
148
149    async fn claim_device_code(&self, id: &str, user_id: &str) -> AuthResult<bool> {
150        let mut state = self.lock()?;
151        let Some(device_code) = state.device_codes.get_mut(id) else {
152            return Ok(false);
153        };
154
155        if device_code.status != "pending" || device_code.user_id.is_some() {
156            return Ok(false);
157        }
158
159        device_code.user_id = Some(user_id.to_owned());
160        Ok(true)
161    }
162
163    async fn delete_device_code(&self, id: &str) -> AuthResult<()> {
164        let mut state = self.lock()?;
165        _ = state.device_codes.shift_remove(id);
166        _ = state.device_code_fields.shift_remove(id);
167        Ok(())
168    }
169
170    async fn delete_device_code_if_status(&self, id: &str, status: &str) -> AuthResult<bool> {
171        let mut state = self.lock()?;
172        let should_delete = state
173            .device_codes
174            .get(id)
175            .is_some_and(|device_code| device_code.status == status);
176
177        if should_delete {
178            _ = state.device_codes.shift_remove(id);
179            _ = state.device_code_fields.shift_remove(id);
180        }
181
182        Ok(should_delete)
183    }
184}