1use crate::{
2 models::{InsertQueryBuilder, UpdateQueryBuilder},
3 prelude::*,
4};
5use garde::Validate;
6use rand::distr::SampleString;
7use serde::{Deserialize, Serialize};
8use sqlx::{Row, postgres::PgRow};
9use std::{
10 collections::BTreeMap,
11 sync::{Arc, LazyLock},
12};
13use utoipa::ToSchema;
14
15#[derive(Serialize, Deserialize, Clone)]
16pub struct OAuthProvider {
17 pub uuid: uuid::Uuid,
18
19 pub name: compact_str::CompactString,
20 pub description: Option<compact_str::CompactString>,
21
22 pub client_id: compact_str::CompactString,
23 pub client_secret: Vec<u8>,
24 pub auth_url: String,
25 pub token_url: String,
26 pub info_url: String,
27 pub scopes: Vec<compact_str::CompactString>,
28
29 pub identifier_path: String,
30 pub email_path: Option<String>,
31 pub username_path: Option<String>,
32 pub name_first_path: Option<String>,
33 pub name_last_path: Option<String>,
34
35 pub enabled: bool,
36 pub login_only: bool,
37 pub link_viewable: bool,
38 pub user_manageable: bool,
39 pub basic_auth: bool,
40
41 pub created: chrono::NaiveDateTime,
42}
43
44impl BaseModel for OAuthProvider {
45 const NAME: &'static str = "oauth_provider";
46
47 #[inline]
48 fn columns(prefix: Option<&str>) -> BTreeMap<&'static str, compact_str::CompactString> {
49 let prefix = prefix.unwrap_or_default();
50
51 BTreeMap::from([
52 (
53 "oauth_providers.uuid",
54 compact_str::format_compact!("{prefix}uuid"),
55 ),
56 (
57 "oauth_providers.name",
58 compact_str::format_compact!("{prefix}name"),
59 ),
60 (
61 "oauth_providers.description",
62 compact_str::format_compact!("{prefix}description"),
63 ),
64 (
65 "oauth_providers.client_id",
66 compact_str::format_compact!("{prefix}client_id"),
67 ),
68 (
69 "oauth_providers.client_secret",
70 compact_str::format_compact!("{prefix}client_secret"),
71 ),
72 (
73 "oauth_providers.auth_url",
74 compact_str::format_compact!("{prefix}auth_url"),
75 ),
76 (
77 "oauth_providers.token_url",
78 compact_str::format_compact!("{prefix}token_url"),
79 ),
80 (
81 "oauth_providers.info_url",
82 compact_str::format_compact!("{prefix}info_url"),
83 ),
84 (
85 "oauth_providers.scopes",
86 compact_str::format_compact!("{prefix}scopes"),
87 ),
88 (
89 "oauth_providers.identifier_path",
90 compact_str::format_compact!("{prefix}identifier_path"),
91 ),
92 (
93 "oauth_providers.email_path",
94 compact_str::format_compact!("{prefix}email_path"),
95 ),
96 (
97 "oauth_providers.username_path",
98 compact_str::format_compact!("{prefix}username_path"),
99 ),
100 (
101 "oauth_providers.name_first_path",
102 compact_str::format_compact!("{prefix}name_first_path"),
103 ),
104 (
105 "oauth_providers.name_last_path",
106 compact_str::format_compact!("{prefix}name_last_path"),
107 ),
108 (
109 "oauth_providers.enabled",
110 compact_str::format_compact!("{prefix}enabled"),
111 ),
112 (
113 "oauth_providers.login_only",
114 compact_str::format_compact!("{prefix}login_only"),
115 ),
116 (
117 "oauth_providers.link_viewable",
118 compact_str::format_compact!("{prefix}link_viewable"),
119 ),
120 (
121 "oauth_providers.user_manageable",
122 compact_str::format_compact!("{prefix}user_manageable"),
123 ),
124 (
125 "oauth_providers.basic_auth",
126 compact_str::format_compact!("{prefix}basic_auth"),
127 ),
128 (
129 "oauth_providers.created",
130 compact_str::format_compact!("{prefix}created"),
131 ),
132 ])
133 }
134
135 #[inline]
136 fn map(prefix: Option<&str>, row: &PgRow) -> Result<Self, crate::database::DatabaseError> {
137 let prefix = prefix.unwrap_or_default();
138
139 Ok(Self {
140 uuid: row.try_get(compact_str::format_compact!("{prefix}uuid").as_str())?,
141 name: row.try_get(compact_str::format_compact!("{prefix}name").as_str())?,
142 description: row
143 .try_get(compact_str::format_compact!("{prefix}description").as_str())?,
144 client_id: row.try_get(compact_str::format_compact!("{prefix}client_id").as_str())?,
145 client_secret: row
146 .try_get(compact_str::format_compact!("{prefix}client_secret").as_str())?,
147 auth_url: row.try_get(compact_str::format_compact!("{prefix}auth_url").as_str())?,
148 token_url: row.try_get(compact_str::format_compact!("{prefix}token_url").as_str())?,
149 info_url: row.try_get(compact_str::format_compact!("{prefix}info_url").as_str())?,
150 scopes: row.try_get(compact_str::format_compact!("{prefix}scopes").as_str())?,
151 identifier_path: row
152 .try_get(compact_str::format_compact!("{prefix}identifier_path").as_str())?,
153 email_path: row.try_get(compact_str::format_compact!("{prefix}email_path").as_str())?,
154 username_path: row
155 .try_get(compact_str::format_compact!("{prefix}username_path").as_str())?,
156 name_first_path: row
157 .try_get(compact_str::format_compact!("{prefix}name_first_path").as_str())?,
158 name_last_path: row
159 .try_get(compact_str::format_compact!("{prefix}name_last_path").as_str())?,
160 enabled: row.try_get(compact_str::format_compact!("{prefix}enabled").as_str())?,
161 login_only: row.try_get(compact_str::format_compact!("{prefix}login_only").as_str())?,
162 link_viewable: row
163 .try_get(compact_str::format_compact!("{prefix}link_viewable").as_str())?,
164 user_manageable: row
165 .try_get(compact_str::format_compact!("{prefix}user_manageable").as_str())?,
166 basic_auth: row.try_get(compact_str::format_compact!("{prefix}basic_auth").as_str())?,
167 created: row.try_get(compact_str::format_compact!("{prefix}created").as_str())?,
168 })
169 }
170}
171
172impl OAuthProvider {
173 pub async fn all_with_pagination(
174 database: &crate::database::Database,
175 page: i64,
176 per_page: i64,
177 search: Option<&str>,
178 ) -> Result<super::Pagination<Self>, crate::database::DatabaseError> {
179 let offset = (page - 1) * per_page;
180
181 let rows = sqlx::query(&format!(
182 r#"
183 SELECT {}, COUNT(*) OVER() AS total_count
184 FROM oauth_providers
185 WHERE ($1 IS NULL OR oauth_providers.name ILIKE '%' || $1 || '%')
186 ORDER BY oauth_providers.created
187 LIMIT $2 OFFSET $3
188 "#,
189 Self::columns_sql(None)
190 ))
191 .bind(search)
192 .bind(per_page)
193 .bind(offset)
194 .fetch_all(database.read())
195 .await?;
196
197 Ok(super::Pagination {
198 total: rows
199 .first()
200 .map_or(Ok(0), |row| row.try_get("total_count"))?,
201 per_page,
202 page,
203 data: rows
204 .into_iter()
205 .map(|row| Self::map(None, &row))
206 .try_collect_vec()?,
207 })
208 }
209
210 pub async fn all_by_usable(
211 database: &crate::database::Database,
212 ) -> Result<Vec<Self>, crate::database::DatabaseError> {
213 let rows = sqlx::query(&format!(
214 r#"
215 SELECT {}
216 FROM oauth_providers
217 WHERE oauth_providers.enabled = true
218 ORDER BY oauth_providers.created
219 "#,
220 Self::columns_sql(None)
221 ))
222 .fetch_all(database.read())
223 .await?;
224
225 rows.into_iter()
226 .map(|row| Self::map(None, &row))
227 .try_collect_vec()
228 }
229
230 pub fn extract_identifier(&self, value: &serde_json::Value) -> Result<String, anyhow::Error> {
231 Ok(
232 match serde_json_path::JsonPath::parse(&self.identifier_path)?
233 .query(value)
234 .first()
235 .ok_or_else(|| {
236 crate::response::DisplayError::new(format!(
237 "unable to extract identifier from {:?}",
238 value
239 ))
240 })? {
241 serde_json::Value::String(string) => string.clone(),
242 val => val.to_string(),
243 },
244 )
245 }
246
247 pub fn extract_email(&self, value: &serde_json::Value) -> Result<String, anyhow::Error> {
248 Ok(
249 match serde_json_path::JsonPath::parse(match &self.email_path {
250 Some(path) => path,
251 None => {
252 return Ok(format!(
253 "{}@oauth.c7s.rs",
254 rand::distr::Alphanumeric.sample_string(&mut rand::rng(), 10)
255 ));
256 }
257 })?
258 .query(value)
259 .first()
260 .ok_or_else(|| {
261 crate::response::DisplayError::new(format!(
262 "unable to extract email from {:?}",
263 value
264 ))
265 })? {
266 serde_json::Value::String(string) => string.clone(),
267 val => val.to_string(),
268 },
269 )
270 }
271
272 pub fn extract_username(&self, value: &serde_json::Value) -> Result<String, anyhow::Error> {
273 Ok(
274 match serde_json_path::JsonPath::parse(match &self.username_path {
275 Some(path) => path,
276 None => return Ok(rand::distr::Alphanumeric.sample_string(&mut rand::rng(), 10)),
277 })?
278 .query(value)
279 .first()
280 .ok_or_else(|| {
281 crate::response::DisplayError::new(format!(
282 "unable to extract username from {:?}",
283 value
284 ))
285 })? {
286 serde_json::Value::String(string) => string.clone(),
287 val => val.to_string(),
288 },
289 )
290 }
291
292 pub fn extract_name_first(&self, value: &serde_json::Value) -> Result<String, anyhow::Error> {
293 Ok(
294 match serde_json_path::JsonPath::parse(match &self.name_first_path {
295 Some(path) => path,
296 None => return Ok("First".to_string()),
297 })?
298 .query(value)
299 .first()
300 .ok_or_else(|| {
301 crate::response::DisplayError::new(format!(
302 "unable to extract first name from {:?}",
303 value
304 ))
305 })? {
306 serde_json::Value::String(string) => string.clone(),
307 val => val.to_string(),
308 },
309 )
310 }
311
312 pub fn extract_name_last(&self, value: &serde_json::Value) -> Result<String, anyhow::Error> {
313 Ok(
314 match serde_json_path::JsonPath::parse(match &self.name_last_path {
315 Some(path) => path,
316 None => return Ok("Last".to_string()),
317 })?
318 .query(value)
319 .first()
320 .ok_or_else(|| {
321 crate::response::DisplayError::new(format!(
322 "unable to extract last name from {:?}",
323 value
324 ))
325 })? {
326 serde_json::Value::String(string) => string.clone(),
327 val => val.to_string(),
328 },
329 )
330 }
331
332 #[inline]
333 pub async fn into_admin_api_object(
334 self,
335 database: &crate::database::Database,
336 ) -> Result<AdminApiOAuthProvider, anyhow::Error> {
337 Ok(AdminApiOAuthProvider {
338 uuid: self.uuid,
339 name: self.name,
340 description: self.description,
341 client_id: self.client_id,
342 client_secret: database.decrypt(self.client_secret).await?,
343 auth_url: self.auth_url,
344 token_url: self.token_url,
345 info_url: self.info_url,
346 scopes: self.scopes,
347 identifier_path: self.identifier_path,
348 email_path: self.email_path,
349 username_path: self.username_path,
350 name_first_path: self.name_first_path,
351 name_last_path: self.name_last_path,
352 enabled: self.enabled,
353 login_only: self.login_only,
354 link_viewable: self.link_viewable,
355 user_manageable: self.user_manageable,
356 basic_auth: self.basic_auth,
357 created: self.created.and_utc(),
358 })
359 }
360
361 #[inline]
362 pub fn into_api_object(self) -> ApiOAuthProvider {
363 ApiOAuthProvider {
364 uuid: self.uuid,
365 name: self.name,
366 link_viewable: self.link_viewable,
367 user_manageable: self.user_manageable,
368 }
369 }
370}
371
372#[async_trait::async_trait]
373impl ByUuid for OAuthProvider {
374 async fn by_uuid(
375 database: &crate::database::Database,
376 uuid: uuid::Uuid,
377 ) -> Result<Self, crate::database::DatabaseError> {
378 let row = sqlx::query(&format!(
379 r#"
380 SELECT {}
381 FROM oauth_providers
382 WHERE oauth_providers.uuid = $1
383 "#,
384 Self::columns_sql(None)
385 ))
386 .bind(uuid)
387 .fetch_one(database.read())
388 .await?;
389
390 Self::map(None, &row)
391 }
392}
393
394#[derive(ToSchema, Deserialize, Validate)]
395pub struct CreateOAuthProviderOptions {
396 #[garde(length(chars, min = 3, max = 255))]
397 #[schema(min_length = 3, max_length = 255)]
398 pub name: compact_str::CompactString,
399 #[garde(length(chars, min = 1, max = 1024))]
400 #[schema(min_length = 1, max_length = 1024)]
401 pub description: Option<compact_str::CompactString>,
402 #[garde(skip)]
403 pub enabled: bool,
404 #[garde(skip)]
405 pub login_only: bool,
406 #[garde(skip)]
407 pub link_viewable: bool,
408 #[garde(skip)]
409 pub user_manageable: bool,
410 #[garde(skip)]
411 pub basic_auth: bool,
412
413 #[garde(length(chars, min = 3, max = 255))]
414 #[schema(min_length = 3, max_length = 255)]
415 pub client_id: compact_str::CompactString,
416 #[garde(length(chars, min = 3, max = 255))]
417 #[schema(min_length = 3, max_length = 255)]
418 pub client_secret: compact_str::CompactString,
419
420 #[garde(length(chars, min = 3, max = 255))]
421 #[schema(min_length = 3, max_length = 255)]
422 pub auth_url: String,
423 #[garde(length(chars, min = 3, max = 255))]
424 #[schema(min_length = 3, max_length = 255)]
425 pub token_url: String,
426 #[garde(length(chars, min = 3, max = 255))]
427 #[schema(min_length = 3, max_length = 255)]
428 pub info_url: String,
429 #[garde(length(max = 255))]
430 #[schema(max_length = 255)]
431 pub scopes: Vec<compact_str::CompactString>,
432
433 #[garde(length(chars, min = 3, max = 255))]
434 #[schema(min_length = 3, max_length = 255)]
435 pub identifier_path: String,
436 #[garde(length(chars, min = 1, max = 255))]
437 #[schema(min_length = 1, max_length = 255)]
438 pub email_path: Option<String>,
439 #[garde(length(chars, min = 1, max = 255))]
440 #[schema(min_length = 1, max_length = 255)]
441 pub username_path: Option<String>,
442 #[garde(length(chars, min = 1, max = 255))]
443 #[schema(min_length = 1, max_length = 255)]
444 pub name_first_path: Option<String>,
445 #[garde(length(chars, min = 1, max = 255))]
446 #[schema(min_length = 1, max_length = 255)]
447 pub name_last_path: Option<String>,
448}
449
450#[async_trait::async_trait]
451impl CreatableModel for OAuthProvider {
452 type CreateOptions<'a> = CreateOAuthProviderOptions;
453 type CreateResult = Self;
454
455 fn get_create_handlers() -> &'static LazyLock<CreateListenerList<Self>> {
456 static CREATE_LISTENERS: LazyLock<CreateListenerList<OAuthProvider>> =
457 LazyLock::new(|| Arc::new(ModelHandlerList::default()));
458
459 &CREATE_LISTENERS
460 }
461
462 async fn create(
463 state: &crate::State,
464 mut options: Self::CreateOptions<'_>,
465 ) -> Result<Self, crate::database::DatabaseError> {
466 options.validate()?;
467
468 let mut transaction = state.database.write().begin().await?;
469
470 let mut query_builder = InsertQueryBuilder::new("oauth_providers");
471
472 Self::run_create_handlers(&mut options, &mut query_builder, state, &mut transaction)
473 .await?;
474
475 let encrypted_client_secret = state
476 .database
477 .encrypt(options.client_secret.to_string())
478 .await
479 .map_err(|err| sqlx::Error::Encode(err.into()))?;
480
481 query_builder
482 .set("name", &options.name)
483 .set("description", &options.description)
484 .set("client_id", &options.client_id)
485 .set("client_secret", encrypted_client_secret)
486 .set("auth_url", &options.auth_url)
487 .set("token_url", &options.token_url)
488 .set("info_url", &options.info_url)
489 .set("scopes", &options.scopes)
490 .set("identifier_path", &options.identifier_path)
491 .set("email_path", &options.email_path)
492 .set("username_path", &options.username_path)
493 .set("name_first_path", &options.name_first_path)
494 .set("name_last_path", &options.name_last_path)
495 .set("enabled", options.enabled)
496 .set("login_only", options.login_only)
497 .set("link_viewable", options.link_viewable)
498 .set("user_manageable", options.user_manageable)
499 .set("basic_auth", options.basic_auth);
500
501 let row = query_builder
502 .returning(&Self::columns_sql(None))
503 .fetch_one(&mut *transaction)
504 .await?;
505 let oauth_provider = Self::map(None, &row)?;
506
507 transaction.commit().await?;
508
509 Ok(oauth_provider)
510 }
511}
512
513#[derive(ToSchema, Serialize, Deserialize, Validate, Clone, Default)]
514pub struct UpdateOAuthProviderOptions {
515 #[garde(length(chars, min = 3, max = 255))]
516 #[schema(min_length = 3, max_length = 255)]
517 pub name: Option<compact_str::CompactString>,
518 #[garde(length(chars, min = 1, max = 1024))]
519 #[schema(min_length = 1, max_length = 1024)]
520 #[serde(
521 default,
522 skip_serializing_if = "Option::is_none",
523 with = "::serde_with::rust::double_option"
524 )]
525 pub description: Option<Option<compact_str::CompactString>>,
526 #[garde(skip)]
527 pub enabled: Option<bool>,
528 #[garde(skip)]
529 pub login_only: Option<bool>,
530 #[garde(skip)]
531 pub link_viewable: Option<bool>,
532 #[garde(skip)]
533 pub user_manageable: Option<bool>,
534 #[garde(skip)]
535 pub basic_auth: Option<bool>,
536
537 #[garde(length(chars, min = 3, max = 255))]
538 #[schema(min_length = 3, max_length = 255)]
539 pub client_id: Option<compact_str::CompactString>,
540 #[garde(length(chars, min = 3, max = 255))]
541 #[schema(min_length = 3, max_length = 255)]
542 pub client_secret: Option<compact_str::CompactString>,
543
544 #[garde(length(chars, min = 3, max = 255))]
545 #[schema(min_length = 3, max_length = 255)]
546 pub auth_url: Option<String>,
547 #[garde(length(chars, min = 3, max = 255))]
548 #[schema(min_length = 3, max_length = 255)]
549 pub token_url: Option<String>,
550 #[garde(length(chars, min = 3, max = 255))]
551 #[schema(min_length = 3, max_length = 255)]
552 pub info_url: Option<String>,
553 #[garde(length(max = 255))]
554 #[schema(max_length = 255)]
555 pub scopes: Option<Vec<compact_str::CompactString>>,
556
557 #[garde(length(chars, min = 3, max = 255))]
558 #[schema(min_length = 3, max_length = 255)]
559 pub identifier_path: Option<String>,
560 #[garde(length(chars, min = 1, max = 255))]
561 #[schema(min_length = 1, max_length = 255)]
562 #[serde(
563 default,
564 skip_serializing_if = "Option::is_none",
565 with = "::serde_with::rust::double_option"
566 )]
567 pub email_path: Option<Option<String>>,
568 #[garde(length(chars, min = 1, max = 255))]
569 #[schema(min_length = 1, max_length = 255)]
570 #[serde(
571 default,
572 skip_serializing_if = "Option::is_none",
573 with = "::serde_with::rust::double_option"
574 )]
575 pub username_path: Option<Option<String>>,
576 #[garde(length(chars, min = 1, max = 255))]
577 #[schema(min_length = 1, max_length = 255)]
578 #[serde(
579 default,
580 skip_serializing_if = "Option::is_none",
581 with = "::serde_with::rust::double_option"
582 )]
583 pub name_first_path: Option<Option<String>>,
584 #[garde(length(chars, min = 1, max = 255))]
585 #[schema(min_length = 1, max_length = 255)]
586 #[serde(
587 default,
588 skip_serializing_if = "Option::is_none",
589 with = "::serde_with::rust::double_option"
590 )]
591 pub name_last_path: Option<Option<String>>,
592}
593
594#[async_trait::async_trait]
595impl UpdatableModel for OAuthProvider {
596 type UpdateOptions = UpdateOAuthProviderOptions;
597
598 fn get_update_handlers() -> &'static LazyLock<UpdateListenerList<Self>> {
599 static UPDATE_LISTENERS: LazyLock<UpdateListenerList<OAuthProvider>> =
600 LazyLock::new(|| Arc::new(ModelHandlerList::default()));
601
602 &UPDATE_LISTENERS
603 }
604
605 async fn update(
606 &mut self,
607 state: &crate::State,
608 mut options: Self::UpdateOptions,
609 ) -> Result<(), crate::database::DatabaseError> {
610 options.validate()?;
611
612 let mut transaction = state.database.write().begin().await?;
613
614 let mut query_builder = UpdateQueryBuilder::new("oauth_providers");
615
616 Self::run_update_handlers(
617 self,
618 &mut options,
619 &mut query_builder,
620 state,
621 &mut transaction,
622 )
623 .await?;
624
625 let encrypted_client_secret = if let Some(ref client_secret) = options.client_secret {
626 Some(
627 state
628 .database
629 .encrypt(client_secret.to_string())
630 .await
631 .map_err(|err| sqlx::Error::Encode(err.into()))?,
632 )
633 } else {
634 None
635 };
636
637 query_builder
638 .set("name", options.name.as_ref())
639 .set(
640 "description",
641 options.description.as_ref().map(|d| d.as_ref()),
642 )
643 .set("client_id", options.client_id.as_ref())
644 .set("client_secret", encrypted_client_secret)
645 .set("auth_url", options.auth_url.as_ref())
646 .set("token_url", options.token_url.as_ref())
647 .set("info_url", options.info_url.as_ref())
648 .set("scopes", options.scopes.as_ref())
649 .set("identifier_path", options.identifier_path.as_ref())
650 .set(
651 "email_path",
652 options.email_path.as_ref().map(|e| e.as_ref()),
653 )
654 .set(
655 "username_path",
656 options.username_path.as_ref().map(|u| u.as_ref()),
657 )
658 .set(
659 "name_first_path",
660 options.name_first_path.as_ref().map(|n| n.as_ref()),
661 )
662 .set(
663 "name_last_path",
664 options.name_last_path.as_ref().map(|n| n.as_ref()),
665 )
666 .set("enabled", options.enabled)
667 .set("login_only", options.login_only)
668 .set("link_viewable", options.link_viewable)
669 .set("user_manageable", options.user_manageable)
670 .set("basic_auth", options.basic_auth)
671 .where_eq("uuid", self.uuid);
672
673 query_builder.execute(&mut *transaction).await?;
674
675 if let Some(name) = options.name {
676 self.name = name;
677 }
678 if let Some(description) = options.description {
679 self.description = description;
680 }
681 if let Some(enabled) = options.enabled {
682 self.enabled = enabled;
683 }
684 if let Some(login_only) = options.login_only {
685 self.login_only = login_only;
686 }
687 if let Some(link_viewable) = options.link_viewable {
688 self.link_viewable = link_viewable;
689 }
690 if let Some(user_manageable) = options.user_manageable {
691 self.user_manageable = user_manageable;
692 }
693 if let Some(basic_auth) = options.basic_auth {
694 self.basic_auth = basic_auth;
695 }
696 if let Some(client_id) = options.client_id {
697 self.client_id = client_id;
698 }
699 if let Some(client_secret) = options.client_secret {
700 self.client_secret = state
701 .database
702 .encrypt(client_secret)
703 .await
704 .map_err(|err| sqlx::Error::Encode(err.into()))?;
705 }
706 if let Some(auth_url) = options.auth_url {
707 self.auth_url = auth_url;
708 }
709 if let Some(token_url) = options.token_url {
710 self.token_url = token_url;
711 }
712 if let Some(info_url) = options.info_url {
713 self.info_url = info_url;
714 }
715 if let Some(scopes) = options.scopes {
716 self.scopes = scopes;
717 }
718 if let Some(identifier_path) = options.identifier_path {
719 self.identifier_path = identifier_path;
720 }
721 if let Some(email_path) = options.email_path {
722 self.email_path = email_path;
723 }
724 if let Some(username_path) = options.username_path {
725 self.username_path = username_path;
726 }
727 if let Some(name_first_path) = options.name_first_path {
728 self.name_first_path = name_first_path;
729 }
730 if let Some(name_last_path) = options.name_last_path {
731 self.name_last_path = name_last_path;
732 }
733
734 transaction.commit().await?;
735
736 Ok(())
737 }
738}
739
740#[async_trait::async_trait]
741impl DeletableModel for OAuthProvider {
742 type DeleteOptions = ();
743
744 fn get_delete_handlers() -> &'static LazyLock<DeleteListenerList<Self>> {
745 static DELETE_LISTENERS: LazyLock<DeleteListenerList<OAuthProvider>> =
746 LazyLock::new(|| Arc::new(ModelHandlerList::default()));
747
748 &DELETE_LISTENERS
749 }
750
751 async fn delete(
752 &self,
753 state: &crate::State,
754 options: Self::DeleteOptions,
755 ) -> Result<(), anyhow::Error> {
756 let mut transaction = state.database.write().begin().await?;
757
758 self.run_delete_handlers(&options, state, &mut transaction)
759 .await?;
760
761 sqlx::query(
762 r#"
763 DELETE FROM oauth_providers
764 WHERE oauth_providers.uuid = $1
765 "#,
766 )
767 .bind(self.uuid)
768 .execute(&mut *transaction)
769 .await?;
770
771 transaction.commit().await?;
772
773 Ok(())
774 }
775}
776
777#[derive(ToSchema, Serialize)]
778#[schema(title = "AdminOAuthProvider")]
779pub struct AdminApiOAuthProvider {
780 pub uuid: uuid::Uuid,
781
782 pub name: compact_str::CompactString,
783 pub description: Option<compact_str::CompactString>,
784
785 pub client_id: compact_str::CompactString,
786 pub client_secret: compact_str::CompactString,
787 pub auth_url: String,
788 pub token_url: String,
789 pub info_url: String,
790 pub scopes: Vec<compact_str::CompactString>,
791
792 pub identifier_path: String,
793 pub email_path: Option<String>,
794 pub username_path: Option<String>,
795 pub name_first_path: Option<String>,
796 pub name_last_path: Option<String>,
797
798 pub enabled: bool,
799 pub login_only: bool,
800 pub link_viewable: bool,
801 pub user_manageable: bool,
802 pub basic_auth: bool,
803
804 pub created: chrono::DateTime<chrono::Utc>,
805}
806
807#[derive(ToSchema, Serialize)]
808#[schema(title = "OAuthProvider")]
809pub struct ApiOAuthProvider {
810 pub uuid: uuid::Uuid,
811
812 pub name: compact_str::CompactString,
813
814 pub link_viewable: bool,
815 pub user_manageable: bool,
816}