Skip to main content

shared/models/
oauth_provider.rs

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