Skip to main content

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 login_bypass_two_factor: bool,
38    pub link_viewable: bool,
39    pub user_manageable: bool,
40    pub basic_auth: bool,
41
42    pub created: chrono::NaiveDateTime,
43
44    extension_data: super::ModelExtensionData,
45}
46
47impl BaseModel for OAuthProvider {
48    const NAME: &'static str = "oauth_provider";
49
50    fn get_extension_list() -> &'static super::ModelExtensionList {
51        static EXTENSIONS: LazyLock<super::ModelExtensionList> =
52            LazyLock::new(|| parking_lot::RwLock::new(Vec::new()));
53
54        &EXTENSIONS
55    }
56
57    fn get_extension_data(&self) -> &super::ModelExtensionData {
58        &self.extension_data
59    }
60
61    #[inline]
62    fn base_columns(prefix: Option<&str>) -> BTreeMap<&'static str, compact_str::CompactString> {
63        let prefix = prefix.unwrap_or_default();
64
65        BTreeMap::from([
66            (
67                "oauth_providers.uuid",
68                compact_str::format_compact!("{prefix}uuid"),
69            ),
70            (
71                "oauth_providers.name",
72                compact_str::format_compact!("{prefix}name"),
73            ),
74            (
75                "oauth_providers.description",
76                compact_str::format_compact!("{prefix}description"),
77            ),
78            (
79                "oauth_providers.client_id",
80                compact_str::format_compact!("{prefix}client_id"),
81            ),
82            (
83                "oauth_providers.client_secret",
84                compact_str::format_compact!("{prefix}client_secret"),
85            ),
86            (
87                "oauth_providers.auth_url",
88                compact_str::format_compact!("{prefix}auth_url"),
89            ),
90            (
91                "oauth_providers.token_url",
92                compact_str::format_compact!("{prefix}token_url"),
93            ),
94            (
95                "oauth_providers.info_url",
96                compact_str::format_compact!("{prefix}info_url"),
97            ),
98            (
99                "oauth_providers.scopes",
100                compact_str::format_compact!("{prefix}scopes"),
101            ),
102            (
103                "oauth_providers.identifier_path",
104                compact_str::format_compact!("{prefix}identifier_path"),
105            ),
106            (
107                "oauth_providers.email_path",
108                compact_str::format_compact!("{prefix}email_path"),
109            ),
110            (
111                "oauth_providers.username_path",
112                compact_str::format_compact!("{prefix}username_path"),
113            ),
114            (
115                "oauth_providers.name_first_path",
116                compact_str::format_compact!("{prefix}name_first_path"),
117            ),
118            (
119                "oauth_providers.name_last_path",
120                compact_str::format_compact!("{prefix}name_last_path"),
121            ),
122            (
123                "oauth_providers.enabled",
124                compact_str::format_compact!("{prefix}enabled"),
125            ),
126            (
127                "oauth_providers.login_only",
128                compact_str::format_compact!("{prefix}login_only"),
129            ),
130            (
131                "oauth_providers.login_bypass_two_factor",
132                compact_str::format_compact!("{prefix}login_bypass_two_factor"),
133            ),
134            (
135                "oauth_providers.link_viewable",
136                compact_str::format_compact!("{prefix}link_viewable"),
137            ),
138            (
139                "oauth_providers.user_manageable",
140                compact_str::format_compact!("{prefix}user_manageable"),
141            ),
142            (
143                "oauth_providers.basic_auth",
144                compact_str::format_compact!("{prefix}basic_auth"),
145            ),
146            (
147                "oauth_providers.created",
148                compact_str::format_compact!("{prefix}created"),
149            ),
150        ])
151    }
152
153    #[inline]
154    fn map(prefix: Option<&str>, row: &PgRow) -> Result<Self, crate::database::DatabaseError> {
155        let prefix = prefix.unwrap_or_default();
156
157        Ok(Self {
158            uuid: row.try_get(compact_str::format_compact!("{prefix}uuid").as_str())?,
159            name: row.try_get(compact_str::format_compact!("{prefix}name").as_str())?,
160            description: row
161                .try_get(compact_str::format_compact!("{prefix}description").as_str())?,
162            client_id: row.try_get(compact_str::format_compact!("{prefix}client_id").as_str())?,
163            client_secret: row
164                .try_get(compact_str::format_compact!("{prefix}client_secret").as_str())?,
165            auth_url: row.try_get(compact_str::format_compact!("{prefix}auth_url").as_str())?,
166            token_url: row.try_get(compact_str::format_compact!("{prefix}token_url").as_str())?,
167            info_url: row.try_get(compact_str::format_compact!("{prefix}info_url").as_str())?,
168            scopes: row.try_get(compact_str::format_compact!("{prefix}scopes").as_str())?,
169            identifier_path: row
170                .try_get(compact_str::format_compact!("{prefix}identifier_path").as_str())?,
171            email_path: row.try_get(compact_str::format_compact!("{prefix}email_path").as_str())?,
172            username_path: row
173                .try_get(compact_str::format_compact!("{prefix}username_path").as_str())?,
174            name_first_path: row
175                .try_get(compact_str::format_compact!("{prefix}name_first_path").as_str())?,
176            name_last_path: row
177                .try_get(compact_str::format_compact!("{prefix}name_last_path").as_str())?,
178            enabled: row.try_get(compact_str::format_compact!("{prefix}enabled").as_str())?,
179            login_only: row.try_get(compact_str::format_compact!("{prefix}login_only").as_str())?,
180            login_bypass_two_factor: row.try_get(
181                compact_str::format_compact!("{prefix}login_bypass_two_factor").as_str(),
182            )?,
183            link_viewable: row
184                .try_get(compact_str::format_compact!("{prefix}link_viewable").as_str())?,
185            user_manageable: row
186                .try_get(compact_str::format_compact!("{prefix}user_manageable").as_str())?,
187            basic_auth: row.try_get(compact_str::format_compact!("{prefix}basic_auth").as_str())?,
188            created: row.try_get(compact_str::format_compact!("{prefix}created").as_str())?,
189            extension_data: Self::map_extensions(prefix, row)?,
190        })
191    }
192}
193
194impl OAuthProvider {
195    pub async fn all_with_pagination(
196        database: &crate::database::Database,
197        page: i64,
198        per_page: i64,
199        search: Option<&str>,
200    ) -> Result<super::Pagination<Self>, crate::database::DatabaseError> {
201        let offset = (page - 1) * per_page;
202
203        let rows = sqlx::query(sqlx::AssertSqlSafe(format!(
204            r#"
205            SELECT {}, COUNT(*) OVER() AS total_count
206            FROM oauth_providers
207            WHERE ($1 IS NULL OR oauth_providers.name ILIKE '%' || $1 || '%')
208            ORDER BY oauth_providers.created
209            LIMIT $2 OFFSET $3
210            "#,
211            Self::columns_sql(None)
212        )))
213        .bind(search)
214        .bind(per_page)
215        .bind(offset)
216        .fetch_all(database.read())
217        .await?;
218
219        Ok(super::Pagination {
220            total: rows
221                .first()
222                .map_or(Ok(0), |row| row.try_get("total_count"))?,
223            per_page,
224            page,
225            data: rows
226                .into_iter()
227                .map(|row| Self::map(None, &row))
228                .try_collect_vec()?,
229        })
230    }
231
232    pub async fn all_by_usable(
233        database: &crate::database::Database,
234    ) -> Result<Vec<Self>, crate::database::DatabaseError> {
235        let rows = sqlx::query(sqlx::AssertSqlSafe(format!(
236            r#"
237            SELECT {}
238            FROM oauth_providers
239            WHERE oauth_providers.enabled = true
240            ORDER BY oauth_providers.created
241            "#,
242            Self::columns_sql(None)
243        )))
244        .fetch_all(database.read())
245        .await?;
246
247        rows.into_iter()
248            .map(|row| Self::map(None, &row))
249            .try_collect_vec()
250    }
251
252    pub fn extract_identifier(&self, value: &serde_json::Value) -> Result<String, anyhow::Error> {
253        Ok(
254            match serde_json_path::JsonPath::parse(&self.identifier_path)?
255                .query(value)
256                .first()
257                .ok_or_else(|| {
258                    crate::response::DisplayError::new(format!(
259                        "unable to extract identifier from {:?}",
260                        value
261                    ))
262                })? {
263                serde_json::Value::String(string) => {
264                    crate::utils::truncate_up_to(string.clone(), 255)
265                }
266                val => crate::utils::truncate_up_to(val.to_string(), 255),
267            },
268        )
269    }
270
271    pub fn extract_email(&self, value: &serde_json::Value) -> Result<String, anyhow::Error> {
272        Ok(
273            match serde_json_path::JsonPath::parse(match &self.email_path {
274                Some(path) => path,
275                None => {
276                    return Ok(format!(
277                        "{}@oauth.c7s.rs",
278                        rand::distr::Alphanumeric.sample_string(&mut rand::rng(), 10)
279                    ));
280                }
281            })?
282            .query(value)
283            .first()
284            .ok_or_else(|| {
285                crate::response::DisplayError::new(format!(
286                    "unable to extract email from {:?}",
287                    value
288                ))
289            })? {
290                serde_json::Value::String(string) => {
291                    crate::utils::truncate_up_to(string.clone(), 255)
292                }
293                val => crate::utils::truncate_up_to(val.to_string(), 255),
294            },
295        )
296    }
297
298    pub fn extract_username(&self, value: &serde_json::Value) -> Result<String, anyhow::Error> {
299        Ok(
300            match serde_json_path::JsonPath::parse(match &self.username_path {
301                Some(path) => path,
302                None => return Ok(rand::distr::Alphanumeric.sample_string(&mut rand::rng(), 10)),
303            })?
304            .query(value)
305            .first()
306            .ok_or_else(|| {
307                crate::response::DisplayError::new(format!(
308                    "unable to extract username from {:?}",
309                    value
310                ))
311            })? {
312                serde_json::Value::String(string) => {
313                    crate::utils::truncate_up_to(string.clone(), 15)
314                }
315                val => crate::utils::truncate_up_to(val.to_string(), 15),
316            },
317        )
318    }
319
320    fn extract_optional_name(
321        path: Option<&String>,
322        value: &serde_json::Value,
323    ) -> Result<Option<String>, anyhow::Error> {
324        let path = match path {
325            Some(path) => serde_json_path::JsonPath::parse(path)?,
326            None => return Ok(None),
327        };
328
329        Ok(match path.query(value).first() {
330            None | Some(serde_json::Value::Null) => None,
331            Some(serde_json::Value::String(string)) => {
332                if string.is_empty() {
333                    None
334                } else {
335                    Some(crate::utils::truncate_up_to(string.clone(), 255))
336                }
337            }
338            Some(val) => Some(crate::utils::truncate_up_to(val.to_string(), 255)),
339        })
340    }
341
342    pub fn extract_name_first(
343        &self,
344        value: &serde_json::Value,
345    ) -> Result<Option<String>, anyhow::Error> {
346        Self::extract_optional_name(self.name_first_path.as_ref(), value)
347    }
348
349    pub fn extract_name_last(
350        &self,
351        value: &serde_json::Value,
352    ) -> Result<Option<String>, anyhow::Error> {
353        Self::extract_optional_name(self.name_last_path.as_ref(), value)
354    }
355}
356
357#[async_trait::async_trait]
358impl IntoAdminApiObject for OAuthProvider {
359    type AdminApiObject = AdminApiOAuthProvider;
360    type ExtraArgs<'a> = ();
361
362    async fn into_admin_api_object<'a>(
363        self,
364        state: &crate::State,
365        _args: Self::ExtraArgs<'a>,
366    ) -> Result<Self::AdminApiObject, crate::database::DatabaseError> {
367        let api_object = AdminApiOAuthProvider::init_hooks(&self, state).await?;
368
369        let api_object = finish_extendible!(
370            AdminApiOAuthProvider {
371                uuid: self.uuid,
372                name: self.name,
373                description: self.description,
374                client_id: self.client_id,
375                client_secret: state.database.decrypt(self.client_secret).await?,
376                auth_url: self.auth_url,
377                token_url: self.token_url,
378                info_url: self.info_url,
379                scopes: self.scopes,
380                identifier_path: self.identifier_path,
381                email_path: self.email_path,
382                username_path: self.username_path,
383                name_first_path: self.name_first_path,
384                name_last_path: self.name_last_path,
385                enabled: self.enabled,
386                login_only: self.login_only,
387                login_bypass_two_factor: self.login_bypass_two_factor,
388                link_viewable: self.link_viewable,
389                user_manageable: self.user_manageable,
390                basic_auth: self.basic_auth,
391                created: self.created.and_utc(),
392            },
393            api_object,
394            state
395        )?;
396
397        Ok(api_object)
398    }
399}
400
401#[async_trait::async_trait]
402impl IntoApiObject for OAuthProvider {
403    type ApiObject = ApiOAuthProvider;
404    type ExtraArgs<'a> = ();
405
406    async fn into_api_object<'a>(
407        self,
408        state: &crate::State,
409        _args: Self::ExtraArgs<'a>,
410    ) -> Result<Self::ApiObject, crate::database::DatabaseError> {
411        let api_object = ApiOAuthProvider::init_hooks(&self, state).await?;
412
413        let api_object = finish_extendible!(
414            ApiOAuthProvider {
415                uuid: self.uuid,
416                name: self.name,
417                link_viewable: self.link_viewable,
418                user_manageable: self.user_manageable,
419            },
420            api_object,
421            state
422        )?;
423
424        Ok(api_object)
425    }
426}
427
428#[async_trait::async_trait]
429impl ByUuid for OAuthProvider {
430    async fn by_uuid(
431        database: &crate::database::Database,
432        uuid: uuid::Uuid,
433    ) -> Result<Self, crate::database::DatabaseError> {
434        let row = sqlx::query(sqlx::AssertSqlSafe(format!(
435            r#"
436            SELECT {}
437            FROM oauth_providers
438            WHERE oauth_providers.uuid = $1
439            "#,
440            Self::columns_sql(None)
441        )))
442        .bind(uuid)
443        .fetch_one(database.read())
444        .await?;
445
446        Self::map(None, &row)
447    }
448
449    async fn by_uuid_with_transaction(
450        transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
451        uuid: uuid::Uuid,
452    ) -> Result<Self, crate::database::DatabaseError> {
453        let row = sqlx::query(sqlx::AssertSqlSafe(format!(
454            r#"
455            SELECT {}
456            FROM oauth_providers
457            WHERE oauth_providers.uuid = $1
458            "#,
459            Self::columns_sql(None)
460        )))
461        .bind(uuid)
462        .fetch_one(&mut **transaction)
463        .await?;
464
465        Self::map(None, &row)
466    }
467}
468
469#[derive(ToSchema, Deserialize, Validate)]
470pub struct CreateOAuthProviderOptions {
471    #[garde(length(chars, min = 1, max = 255))]
472    #[schema(min_length = 1, max_length = 255)]
473    pub name: compact_str::CompactString,
474    #[garde(length(chars, min = 1, max = 1024))]
475    #[schema(min_length = 1, max_length = 1024)]
476    pub description: Option<compact_str::CompactString>,
477    #[garde(skip)]
478    pub enabled: bool,
479    #[garde(skip)]
480    pub login_only: bool,
481    #[garde(skip)]
482    pub login_bypass_two_factor: bool,
483    #[garde(skip)]
484    pub link_viewable: bool,
485    #[garde(skip)]
486    pub user_manageable: bool,
487    #[garde(skip)]
488    pub basic_auth: bool,
489
490    #[garde(length(chars, min = 3, max = 255))]
491    #[schema(min_length = 3, max_length = 255)]
492    pub client_id: compact_str::CompactString,
493    #[garde(length(chars, min = 3, max = 255))]
494    #[schema(min_length = 3, max_length = 255)]
495    pub client_secret: compact_str::CompactString,
496
497    #[garde(length(chars, min = 3, max = 255))]
498    #[schema(min_length = 3, max_length = 255)]
499    pub auth_url: String,
500    #[garde(length(chars, min = 3, max = 255))]
501    #[schema(min_length = 3, max_length = 255)]
502    pub token_url: String,
503    #[garde(length(chars, min = 3, max = 255))]
504    #[schema(min_length = 3, max_length = 255)]
505    pub info_url: String,
506    #[garde(length(max = 255))]
507    #[schema(max_length = 255)]
508    pub scopes: Vec<compact_str::CompactString>,
509
510    #[garde(
511        length(chars, min = 3, max = 255),
512        custom(crate::utils::validate_json_path)
513    )]
514    #[schema(min_length = 3, max_length = 255)]
515    pub identifier_path: String,
516    #[garde(
517        length(chars, min = 1, max = 255),
518        inner(custom(crate::utils::validate_json_path))
519    )]
520    #[schema(min_length = 1, max_length = 255)]
521    pub email_path: Option<String>,
522    #[garde(
523        length(chars, min = 1, max = 255),
524        inner(custom(crate::utils::validate_json_path))
525    )]
526    #[schema(min_length = 1, max_length = 255)]
527    pub username_path: Option<String>,
528    #[garde(
529        length(chars, min = 1, max = 255),
530        inner(custom(crate::utils::validate_json_path))
531    )]
532    #[schema(min_length = 1, max_length = 255)]
533    pub name_first_path: Option<String>,
534    #[garde(
535        length(chars, min = 1, max = 255),
536        inner(custom(crate::utils::validate_json_path))
537    )]
538    #[schema(min_length = 1, max_length = 255)]
539    pub name_last_path: Option<String>,
540}
541
542#[async_trait::async_trait]
543impl CreatableModel for OAuthProvider {
544    type CreateOptions<'a> = CreateOAuthProviderOptions;
545    type CreateResult = Self;
546
547    fn get_create_handlers() -> &'static LazyLock<CreateListenerList<Self>> {
548        static CREATE_LISTENERS: LazyLock<CreateListenerList<OAuthProvider>> =
549            LazyLock::new(|| Arc::new(ModelHandlerList::default()));
550
551        &CREATE_LISTENERS
552    }
553
554    async fn create_with_transaction(
555        state: &crate::State,
556        mut options: Self::CreateOptions<'_>,
557        transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
558    ) -> Result<Self, crate::database::DatabaseError> {
559        options.validate()?;
560
561        let mut query_builder = InsertQueryBuilder::new("oauth_providers");
562
563        Self::run_create_handlers(&mut options, &mut query_builder, state, transaction).await?;
564
565        let encrypted_client_secret = state
566            .database
567            .encrypt(options.client_secret.to_string())
568            .await
569            .map_err(|err| sqlx::Error::Encode(err.into()))?;
570
571        query_builder
572            .set("name", &options.name)
573            .set("description", &options.description)
574            .set("client_id", &options.client_id)
575            .set("client_secret", encrypted_client_secret)
576            .set("auth_url", &options.auth_url)
577            .set("token_url", &options.token_url)
578            .set("info_url", &options.info_url)
579            .set("scopes", &options.scopes)
580            .set("identifier_path", &options.identifier_path)
581            .set("email_path", &options.email_path)
582            .set("username_path", &options.username_path)
583            .set("name_first_path", &options.name_first_path)
584            .set("name_last_path", &options.name_last_path)
585            .set("enabled", options.enabled)
586            .set("login_only", options.login_only)
587            .set("login_bypass_two_factor", options.login_bypass_two_factor)
588            .set("link_viewable", options.link_viewable)
589            .set("user_manageable", options.user_manageable)
590            .set("basic_auth", options.basic_auth);
591
592        let row = query_builder
593            .returning(&Self::columns_sql(None))
594            .fetch_one(&mut **transaction)
595            .await?;
596        let mut oauth_provider = Self::map(None, &row)?;
597
598        Self::run_after_create_handlers(&mut oauth_provider, &options, state, transaction).await?;
599
600        Ok(oauth_provider)
601    }
602}
603
604#[derive(ToSchema, Serialize, Deserialize, Validate, Clone, Default)]
605pub struct UpdateOAuthProviderOptions {
606    #[garde(length(chars, min = 1, max = 255))]
607    #[schema(min_length = 1, max_length = 255)]
608    pub name: Option<compact_str::CompactString>,
609    #[garde(length(chars, min = 1, max = 1024))]
610    #[schema(min_length = 1, max_length = 1024)]
611    #[serde(
612        default,
613        skip_serializing_if = "Option::is_none",
614        with = "::serde_with::rust::double_option"
615    )]
616    pub description: Option<Option<compact_str::CompactString>>,
617    #[garde(skip)]
618    pub enabled: Option<bool>,
619    #[garde(skip)]
620    pub login_only: Option<bool>,
621    #[garde(skip)]
622    pub login_bypass_two_factor: Option<bool>,
623    #[garde(skip)]
624    pub link_viewable: Option<bool>,
625    #[garde(skip)]
626    pub user_manageable: Option<bool>,
627    #[garde(skip)]
628    pub basic_auth: Option<bool>,
629
630    #[garde(length(chars, min = 3, max = 255))]
631    #[schema(min_length = 3, max_length = 255)]
632    pub client_id: Option<compact_str::CompactString>,
633    #[garde(length(chars, min = 3, max = 255))]
634    #[schema(min_length = 3, max_length = 255)]
635    pub client_secret: Option<compact_str::CompactString>,
636
637    #[garde(length(chars, min = 3, max = 255))]
638    #[schema(min_length = 3, max_length = 255)]
639    pub auth_url: Option<String>,
640    #[garde(length(chars, min = 3, max = 255))]
641    #[schema(min_length = 3, max_length = 255)]
642    pub token_url: Option<String>,
643    #[garde(length(chars, min = 3, max = 255))]
644    #[schema(min_length = 3, max_length = 255)]
645    pub info_url: Option<String>,
646    #[garde(length(max = 255))]
647    #[schema(max_length = 255)]
648    pub scopes: Option<Vec<compact_str::CompactString>>,
649
650    #[garde(
651        length(chars, min = 3, max = 255),
652        inner(custom(crate::utils::validate_json_path))
653    )]
654    #[schema(min_length = 3, max_length = 255)]
655    pub identifier_path: Option<String>,
656    #[garde(
657        length(chars, min = 1, max = 255),
658        inner(inner(custom(crate::utils::validate_json_path)))
659    )]
660    #[schema(min_length = 1, max_length = 255)]
661    #[serde(
662        default,
663        skip_serializing_if = "Option::is_none",
664        with = "::serde_with::rust::double_option"
665    )]
666    pub email_path: Option<Option<String>>,
667    #[garde(
668        length(chars, min = 1, max = 255),
669        inner(inner(custom(crate::utils::validate_json_path)))
670    )]
671    #[schema(min_length = 1, max_length = 255)]
672    #[serde(
673        default,
674        skip_serializing_if = "Option::is_none",
675        with = "::serde_with::rust::double_option"
676    )]
677    pub username_path: Option<Option<String>>,
678    #[garde(
679        length(chars, min = 1, max = 255),
680        inner(inner(custom(crate::utils::validate_json_path)))
681    )]
682    #[schema(min_length = 1, max_length = 255)]
683    #[serde(
684        default,
685        skip_serializing_if = "Option::is_none",
686        with = "::serde_with::rust::double_option"
687    )]
688    pub name_first_path: Option<Option<String>>,
689    #[garde(
690        length(chars, min = 1, max = 255),
691        inner(inner(custom(crate::utils::validate_json_path)))
692    )]
693    #[schema(min_length = 1, max_length = 255)]
694    #[serde(
695        default,
696        skip_serializing_if = "Option::is_none",
697        with = "::serde_with::rust::double_option"
698    )]
699    pub name_last_path: Option<Option<String>>,
700}
701
702#[async_trait::async_trait]
703impl UpdatableModel for OAuthProvider {
704    type UpdateOptions = UpdateOAuthProviderOptions;
705
706    fn get_update_handlers() -> &'static LazyLock<UpdateHandlerList<Self>> {
707        static UPDATE_LISTENERS: LazyLock<UpdateHandlerList<OAuthProvider>> =
708            LazyLock::new(|| Arc::new(ModelHandlerList::default()));
709
710        &UPDATE_LISTENERS
711    }
712
713    async fn update_with_transaction(
714        &mut self,
715        state: &crate::State,
716        mut options: Self::UpdateOptions,
717        transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
718    ) -> Result<(), crate::database::DatabaseError> {
719        options.validate()?;
720
721        let mut query_builder = UpdateQueryBuilder::new("oauth_providers");
722
723        self.run_update_handlers(&mut options, &mut query_builder, state, transaction)
724            .await?;
725
726        let encrypted_client_secret = if let Some(ref client_secret) = options.client_secret {
727            Some(
728                state
729                    .database
730                    .encrypt(client_secret.to_string())
731                    .await
732                    .map_err(|err| sqlx::Error::Encode(err.into()))?,
733            )
734        } else {
735            None
736        };
737
738        query_builder
739            .set("name", options.name.as_ref())
740            .set(
741                "description",
742                options.description.as_ref().map(|d| d.as_ref()),
743            )
744            .set("client_id", options.client_id.as_ref())
745            .set("client_secret", encrypted_client_secret)
746            .set("auth_url", options.auth_url.as_ref())
747            .set("token_url", options.token_url.as_ref())
748            .set("info_url", options.info_url.as_ref())
749            .set("scopes", options.scopes.as_ref())
750            .set("identifier_path", options.identifier_path.as_ref())
751            .set(
752                "email_path",
753                options.email_path.as_ref().map(|e| e.as_ref()),
754            )
755            .set(
756                "username_path",
757                options.username_path.as_ref().map(|u| u.as_ref()),
758            )
759            .set(
760                "name_first_path",
761                options.name_first_path.as_ref().map(|n| n.as_ref()),
762            )
763            .set(
764                "name_last_path",
765                options.name_last_path.as_ref().map(|n| n.as_ref()),
766            )
767            .set("enabled", options.enabled)
768            .set("login_only", options.login_only)
769            .set("login_bypass_two_factor", options.login_bypass_two_factor)
770            .set("link_viewable", options.link_viewable)
771            .set("user_manageable", options.user_manageable)
772            .set("basic_auth", options.basic_auth)
773            .where_eq("uuid", self.uuid);
774
775        query_builder.execute(&mut **transaction).await?;
776
777        if let Some(name) = options.name {
778            self.name = name;
779        }
780        if let Some(description) = options.description {
781            self.description = description;
782        }
783        if let Some(enabled) = options.enabled {
784            self.enabled = enabled;
785        }
786        if let Some(login_only) = options.login_only {
787            self.login_only = login_only;
788        }
789        if let Some(login_bypass_two_factor) = options.login_bypass_two_factor {
790            self.login_bypass_two_factor = login_bypass_two_factor;
791        }
792        if let Some(link_viewable) = options.link_viewable {
793            self.link_viewable = link_viewable;
794        }
795        if let Some(user_manageable) = options.user_manageable {
796            self.user_manageable = user_manageable;
797        }
798        if let Some(basic_auth) = options.basic_auth {
799            self.basic_auth = basic_auth;
800        }
801        if let Some(client_id) = options.client_id {
802            self.client_id = client_id;
803        }
804        if let Some(client_secret) = options.client_secret {
805            self.client_secret = state
806                .database
807                .encrypt(client_secret)
808                .await
809                .map_err(|err| sqlx::Error::Encode(err.into()))?;
810        }
811        if let Some(auth_url) = options.auth_url {
812            self.auth_url = auth_url;
813        }
814        if let Some(token_url) = options.token_url {
815            self.token_url = token_url;
816        }
817        if let Some(info_url) = options.info_url {
818            self.info_url = info_url;
819        }
820        if let Some(scopes) = options.scopes {
821            self.scopes = scopes;
822        }
823        if let Some(identifier_path) = options.identifier_path {
824            self.identifier_path = identifier_path;
825        }
826        if let Some(email_path) = options.email_path {
827            self.email_path = email_path;
828        }
829        if let Some(username_path) = options.username_path {
830            self.username_path = username_path;
831        }
832        if let Some(name_first_path) = options.name_first_path {
833            self.name_first_path = name_first_path;
834        }
835        if let Some(name_last_path) = options.name_last_path {
836            self.name_last_path = name_last_path;
837        }
838
839        self.run_after_update_handlers(state, transaction).await?;
840
841        Ok(())
842    }
843}
844
845#[async_trait::async_trait]
846impl DeletableModel for OAuthProvider {
847    type DeleteOptions = ();
848
849    fn get_delete_handlers() -> &'static LazyLock<DeleteHandlerList<Self>> {
850        static DELETE_LISTENERS: LazyLock<DeleteHandlerList<OAuthProvider>> =
851            LazyLock::new(|| Arc::new(ModelHandlerList::default()));
852
853        &DELETE_LISTENERS
854    }
855
856    async fn delete_with_transaction(
857        &self,
858        state: &crate::State,
859        options: Self::DeleteOptions,
860        transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
861    ) -> Result<(), anyhow::Error> {
862        self.run_delete_handlers(&options, state, transaction)
863            .await?;
864
865        sqlx::query(
866            r#"
867            DELETE FROM oauth_providers
868            WHERE oauth_providers.uuid = $1
869            "#,
870        )
871        .bind(self.uuid)
872        .execute(&mut **transaction)
873        .await?;
874
875        self.run_after_delete_handlers(&options, state, transaction)
876            .await?;
877
878        Ok(())
879    }
880}
881
882#[derive(Validate)]
883pub struct DuplicateOAuthProviderOptions {
884    #[garde(length(chars, min = 1, max = 255))]
885    pub name: compact_str::CompactString,
886}
887
888#[async_trait::async_trait]
889impl DuplicableModel for OAuthProvider {
890    type DuplicateOptions<'a> = DuplicateOAuthProviderOptions;
891
892    fn get_duplicate_handlers() -> &'static LazyLock<DuplicateHandlerList<Self>> {
893        static DUPLICATE_LISTENERS: LazyLock<DuplicateHandlerList<OAuthProvider>> =
894            LazyLock::new(|| Arc::new(ModelHandlerList::default()));
895
896        &DUPLICATE_LISTENERS
897    }
898
899    async fn duplicate_with_transaction(
900        &self,
901        state: &crate::State,
902        options: Self::DuplicateOptions<'_>,
903        transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
904    ) -> Result<Self, crate::database::DatabaseError> {
905        options.validate()?;
906
907        self.run_duplicate_handlers(&options, state, transaction)
908            .await?;
909
910        let mut query_builder = InsertQueryBuilder::new("oauth_providers");
911
912        query_builder
913            .set("name", &options.name)
914            .set("description", &self.description)
915            .set("client_id", &self.client_id)
916            .set("client_secret", self.client_secret.clone())
917            .set("auth_url", &self.auth_url)
918            .set("token_url", &self.token_url)
919            .set("info_url", &self.info_url)
920            .set("scopes", &self.scopes)
921            .set("identifier_path", &self.identifier_path)
922            .set("email_path", &self.email_path)
923            .set("username_path", &self.username_path)
924            .set("name_first_path", &self.name_first_path)
925            .set("name_last_path", &self.name_last_path)
926            .set("enabled", self.enabled)
927            .set("login_only", self.login_only)
928            .set("login_bypass_two_factor", self.login_bypass_two_factor)
929            .set("link_viewable", self.link_viewable)
930            .set("user_manageable", self.user_manageable)
931            .set("basic_auth", self.basic_auth);
932
933        let row = query_builder
934            .returning(&Self::columns_sql(None))
935            .fetch_one(&mut **transaction)
936            .await?;
937        let mut oauth_provider = Self::map(None, &row)?;
938
939        sqlx::query!(
940            "INSERT INTO oauth_provider_mappings (oauth_provider_uuid, matcher, mapping)
941            SELECT $1, oauth_provider_mappings.matcher, oauth_provider_mappings.mapping
942            FROM oauth_provider_mappings
943            WHERE oauth_provider_mappings.oauth_provider_uuid = $2",
944            oauth_provider.uuid,
945            self.uuid,
946        )
947        .execute(&mut **transaction)
948        .await?;
949
950        self.run_after_duplicate_handlers(&mut oauth_provider, &options, state, transaction)
951            .await?;
952
953        Ok(oauth_provider)
954    }
955}
956
957#[schema_extension_derive::extendible]
958#[init_args(OAuthProvider, crate::State)]
959#[hook_args(crate::State)]
960#[derive(ToSchema, Serialize)]
961#[schema(title = "AdminOAuthProvider")]
962pub struct AdminApiOAuthProvider {
963    pub uuid: uuid::Uuid,
964
965    pub name: compact_str::CompactString,
966    pub description: Option<compact_str::CompactString>,
967
968    pub client_id: compact_str::CompactString,
969    pub client_secret: compact_str::CompactString,
970    pub auth_url: String,
971    pub token_url: String,
972    pub info_url: String,
973    pub scopes: Vec<compact_str::CompactString>,
974
975    pub identifier_path: String,
976    pub email_path: Option<String>,
977    pub username_path: Option<String>,
978    pub name_first_path: Option<String>,
979    pub name_last_path: Option<String>,
980
981    pub enabled: bool,
982    pub login_only: bool,
983    pub login_bypass_two_factor: bool,
984    pub link_viewable: bool,
985    pub user_manageable: bool,
986    pub basic_auth: bool,
987
988    pub created: chrono::DateTime<chrono::Utc>,
989}
990
991#[schema_extension_derive::extendible]
992#[init_args(OAuthProvider, crate::State)]
993#[hook_args(crate::State)]
994#[derive(ToSchema, Serialize)]
995#[schema(title = "OAuthProvider")]
996pub struct ApiOAuthProvider {
997    pub uuid: uuid::Uuid,
998
999    pub name: compact_str::CompactString,
1000
1001    pub link_viewable: bool,
1002    pub user_manageable: bool,
1003}