shared/models/
oauth_provider.rs

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}