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