1use super::SqlxStore;
2use super::entities::device_code::Model;
3use crate::error::record_not_updated;
4use crate::model::{self, ActiveRow, SqlxModel};
5use crate::pool::{Engine, Exec};
6use crate::schema::AuthSchema;
7use crate::sql::Sql;
8use crate::value::SqlValue;
9use alibi_core::error::{AuthError, AuthResult};
10use alibi_core::store::DeviceCodeStore;
11use alibi_core::types::{CreateDeviceCode, DeviceCode, UpdateDeviceCode};
12use async_trait::async_trait;
13use uuid::Uuid;
14
15impl<S: AuthSchema + Send + Sync> SqlxStore<S> {
16 async fn create_device_code_with_connection(
17 &self,
18 exec: Exec<'_>,
19 input: CreateDeviceCode,
20 ) -> AuthResult<DeviceCode> {
21 let mut active = ActiveRow::new();
22 active.set(
23 "id",
24 self.generated_id(self.exec(), "deviceCode", "device_code", "id")
25 .await?
26 .unwrap_or_else(|| Uuid::new_v4().to_string()),
27 );
28 active.set("device_code", input.device_code);
29 active.set("user_code", input.user_code);
30 active.set("user_id", input.user_id);
31 active.set("expires_at", input.expires_at);
32 active.set("status", input.status);
33 active.set("last_polled_at", input.last_polled_at);
34 active.set("polling_interval", input.polling_interval);
35 active.set("client_id", input.client_id);
36 active.set("scope", input.scope);
37 model::insert::<Model>(exec, &active)
38 .await
39 .map(|model| DeviceCode::from(&model))
40 }
41
42 async fn find_device_code(&self, column: &str, value: &str) -> AuthResult<Option<DeviceCode>> {
43 let mut sql = model::select_model::<Model>(self.exec());
44 sql.push(" WHERE ");
45 sql.compare(Model::TABLE, column, " = ", value);
46 model::limit_one(&mut sql);
47 Ok(self
48 .exec()
49 .fetch_optional::<Model>(sql)
50 .await?
51 .map(|model| DeviceCode::from(&model)))
52 }
53
54 async fn update_device_codes(
56 &self,
57 sets: Vec<(&'static str, SqlValue)>,
58 guards: Vec<(&'static str, Option<SqlValue>)>,
59 ) -> AuthResult<u64> {
60 let mut sql = Sql::with(self.exec().engine(), "UPDATE ");
61 sql.ident(Model::TABLE);
62 sql.push(" SET ");
63 for (index, (column, value)) in sets.into_iter().enumerate() {
64 if index > 0 {
65 sql.push(", ");
66 }
67 sql.assign(column, value);
68 }
69 for (index, (column, value)) in guards.into_iter().enumerate() {
70 sql.push(if index == 0 { " WHERE " } else { " AND " });
71 sql.column(Model::TABLE, column);
72 match value {
73 Some(value) => {
74 sql.push(" = ");
75 sql.bind(value);
76 }
77 None => {
78 sql.push(" IS NULL");
79 }
80 }
81 }
82 self.exec().execute(sql).await
83 }
84}
85
86#[async_trait]
87impl<S> DeviceCodeStore for SqlxStore<S>
88where
89 S: AuthSchema + Send + Sync,
90{
91 async fn create_device_code(&self, input: CreateDeviceCode) -> AuthResult<DeviceCode> {
92 self.create_device_code_with_connection(self.exec(), input)
93 .await
94 }
95 async fn create_device_code_with_fields(
96 &self,
97 input: CreateDeviceCode,
98 fields: serde_json::Map<String, serde_json::Value>,
99 ) -> AuthResult<DeviceCode> {
100 if fields.is_empty() {
101 return self.create_device_code(input).await;
102 }
103 self.in_transaction(true, async move |tx| {
104 let exec = crate::pool::Exec::tx(tx);
105 let create = Sql::with(exec.engine(), "CREATE TABLE IF NOT EXISTS device_code_fields (device_code_id TEXT PRIMARY KEY REFERENCES device_code(id) ON DELETE CASCADE, fields TEXT NOT NULL)");
106 _ = exec.execute(create).await?;
107 let row = self.create_device_code_with_connection(exec, input).await?;
108 let mut insert = Sql::with(exec.engine(), "INSERT INTO device_code_fields (device_code_id, fields) VALUES (");
109 insert.bind(row.id.clone());insert.push(", ");insert.bind(serde_json::to_string(&fields)?);insert.push(")");
110 _ = exec.execute(insert).await?;
111 Ok(row)
112 }).await
113 }
114 async fn device_code_fields(
115 &self,
116 id: &str,
117 ) -> AuthResult<serde_json::Map<String, serde_json::Value>> {
118 if !super::migrator::has_table(self.exec(), "device_code_fields").await? {
119 return Ok(serde_json::Map::new());
120 }
121 let mut sql = Sql::with(
122 self.exec().engine(),
123 "SELECT fields FROM device_code_fields WHERE device_code_id = ",
124 );
125 sql.bind(id);
126 self.exec()
127 .fetch_scalar::<String>(sql)
128 .await?
129 .map(|fields| serde_json::from_str(&fields).map_err(Into::into))
130 .transpose()
131 .map(Option::unwrap_or_default)
132 }
133 async fn consume_device_code(
134 &self,
135 id: &str,
136 status: &str,
137 ownership: &serde_json::Map<String, serde_json::Value>,
138 ) -> AuthResult<Option<DeviceCode>> {
139 let mut sql = Sql::with(self.exec().engine(), "DELETE FROM device_code WHERE id = ");
140 sql.bind(id);
141 sql.push(" AND status = ");
142 sql.bind(status);
143 for (field, value) in ownership {
144 sql.push(" AND EXISTS (SELECT 1 FROM device_code_fields WHERE device_code_id = device_code.id AND ");
145 if self.exec().engine() == Engine::Postgres {
146 sql.push("CAST(fields AS JSONB) -> ");
147 sql.bind(field);
148 sql.push(" = CAST(");
149 sql.bind(value.to_string());
150 sql.push(" AS JSONB)");
151 } else {
152 sql.push("json_extract(fields, ");
153 sql.bind(format!("$.{}", serde_json::to_string(field)?));
154 sql.push(") IS json_extract(");
155 sql.bind(value.to_string());
156 sql.push(", '$')");
157 }
158 sql.push(")");
159 }
160 sql.push(" RETURNING ");
161 for (index, column) in Model::COLUMN_NAMES.iter().enumerate() {
162 if index > 0 {
163 sql.push(", ");
164 }
165 sql.ident(column);
166 }
167 self.exec()
168 .fetch_optional::<Model>(sql)
169 .await
170 .map(|row| row.as_ref().map(DeviceCode::from))
171 }
172
173 async fn get_device_code_by_device_code(
174 &self,
175 device_code: &str,
176 ) -> AuthResult<Option<DeviceCode>> {
177 self.find_device_code("device_code", device_code).await
178 }
179
180 async fn get_device_code_by_user_code(
181 &self,
182 user_code: &str,
183 ) -> AuthResult<Option<DeviceCode>> {
184 self.find_device_code("user_code", user_code).await
185 }
186
187 async fn update_device_code(
188 &self,
189 id: &str,
190 update: UpdateDeviceCode,
191 ) -> AuthResult<DeviceCode> {
192 let mut sql = model::by_id::<Model>(self.exec(), id);
193 model::limit_one(&mut sql);
194 let Some(model) = self.exec().fetch_optional::<Model>(sql).await? else {
195 return Err(AuthError::not_found("Device code not found"));
196 };
197
198 let mut active = model.into_active();
199 if let Some(status) = update.status {
200 active.set("status", status);
201 }
202 if let Some(user_id) = update.user_id {
203 active.set("user_id", user_id);
204 }
205 if let Some(last_polled_at) = update.last_polled_at {
206 active.set("last_polled_at", last_polled_at);
207 }
208
209 model::update::<Model>(self.exec(), &active)
210 .await?
211 .map(|model| DeviceCode::from(&model))
212 .ok_or_else(record_not_updated)
213 }
214
215 async fn update_device_code_if_status(
216 &self,
217 id: &str,
218 current_status: &str,
219 update: UpdateDeviceCode,
220 ) -> AuthResult<bool> {
221 let mut sets = Vec::new();
222 if let Some(status) = update.status {
223 sets.push(("status", status.into()));
224 }
225 if let Some(user_id) = update.user_id {
226 sets.push(("user_id", user_id.into()));
227 }
228 if let Some(last_polled_at) = update.last_polled_at {
229 sets.push(("last_polled_at", last_polled_at.into()));
230 }
231 self.update_device_codes(
232 sets,
233 vec![
234 ("id", Some(id.into())),
235 ("status", Some(current_status.into())),
236 ],
237 )
238 .await
239 .map(|affected| affected == 1)
240 }
241
242 async fn claim_device_code(&self, id: &str, user_id: &str) -> AuthResult<bool> {
243 self.update_device_codes(
244 vec![("user_id", user_id.into())],
245 vec![
246 ("id", Some(id.into())),
247 ("status", Some("pending".into())),
248 ("user_id", None),
249 ],
250 )
251 .await
252 .map(|affected| affected == 1)
253 }
254
255 async fn delete_device_code(&self, id: &str) -> AuthResult<()> {
256 self.exec()
257 .execute(model::delete_by_id::<Model>(self.exec(), id))
258 .await
259 .map(|_| ())
260 }
261
262 async fn delete_device_code_if_status(&self, id: &str, status: &str) -> AuthResult<bool> {
263 let mut sql = model::delete_by_id::<Model>(self.exec(), id);
264 sql.push(" AND ");
265 sql.compare(Model::TABLE, "status", " = ", status);
266 self.exec().execute(sql).await.map(|affected| affected == 1)
267 }
268}