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(|| std::sync::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(&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(&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(&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(&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(length(chars, min = 3, max = 255))]
518    #[schema(min_length = 3, max_length = 255)]
519    pub identifier_path: String,
520    #[garde(length(chars, min = 1, max = 255))]
521    #[schema(min_length = 1, max_length = 255)]
522    pub email_path: Option<String>,
523    #[garde(length(chars, min = 1, max = 255))]
524    #[schema(min_length = 1, max_length = 255)]
525    pub username_path: Option<String>,
526    #[garde(length(chars, min = 1, max = 255))]
527    #[schema(min_length = 1, max_length = 255)]
528    pub name_first_path: Option<String>,
529    #[garde(length(chars, min = 1, max = 255))]
530    #[schema(min_length = 1, max_length = 255)]
531    pub name_last_path: Option<String>,
532}
533
534#[async_trait::async_trait]
535impl CreatableModel for OAuthProvider {
536    type CreateOptions<'a> = CreateOAuthProviderOptions;
537    type CreateResult = Self;
538
539    fn get_create_handlers() -> &'static LazyLock<CreateListenerList<Self>> {
540        static CREATE_LISTENERS: LazyLock<CreateListenerList<OAuthProvider>> =
541            LazyLock::new(|| Arc::new(ModelHandlerList::default()));
542
543        &CREATE_LISTENERS
544    }
545
546    async fn create_with_transaction(
547        state: &crate::State,
548        mut options: Self::CreateOptions<'_>,
549        transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
550    ) -> Result<Self, crate::database::DatabaseError> {
551        options.validate()?;
552
553        let mut query_builder = InsertQueryBuilder::new("oauth_providers");
554
555        Self::run_create_handlers(&mut options, &mut query_builder, state, transaction).await?;
556
557        let encrypted_client_secret = state
558            .database
559            .encrypt(options.client_secret.to_string())
560            .await
561            .map_err(|err| sqlx::Error::Encode(err.into()))?;
562
563        query_builder
564            .set("name", &options.name)
565            .set("description", &options.description)
566            .set("client_id", &options.client_id)
567            .set("client_secret", encrypted_client_secret)
568            .set("auth_url", &options.auth_url)
569            .set("token_url", &options.token_url)
570            .set("info_url", &options.info_url)
571            .set("scopes", &options.scopes)
572            .set("identifier_path", &options.identifier_path)
573            .set("email_path", &options.email_path)
574            .set("username_path", &options.username_path)
575            .set("name_first_path", &options.name_first_path)
576            .set("name_last_path", &options.name_last_path)
577            .set("enabled", options.enabled)
578            .set("login_only", options.login_only)
579            .set("login_bypass_2fa", options.login_bypass_2fa)
580            .set("link_viewable", options.link_viewable)
581            .set("user_manageable", options.user_manageable)
582            .set("basic_auth", options.basic_auth);
583
584        let row = query_builder
585            .returning(&Self::columns_sql(None))
586            .fetch_one(&mut **transaction)
587            .await?;
588        let mut oauth_provider = Self::map(None, &row)?;
589
590        Self::run_after_create_handlers(&mut oauth_provider, &options, state, transaction).await?;
591
592        Ok(oauth_provider)
593    }
594}
595
596#[derive(ToSchema, Serialize, Deserialize, Validate, Clone, Default)]
597pub struct UpdateOAuthProviderOptions {
598    #[garde(length(chars, min = 1, max = 255))]
599    #[schema(min_length = 1, max_length = 255)]
600    pub name: Option<compact_str::CompactString>,
601    #[garde(length(chars, min = 1, max = 1024))]
602    #[schema(min_length = 1, max_length = 1024)]
603    #[serde(
604        default,
605        skip_serializing_if = "Option::is_none",
606        with = "::serde_with::rust::double_option"
607    )]
608    pub description: Option<Option<compact_str::CompactString>>,
609    #[garde(skip)]
610    pub enabled: Option<bool>,
611    #[garde(skip)]
612    pub login_only: Option<bool>,
613    #[garde(skip)]
614    pub login_bypass_2fa: Option<bool>,
615    #[garde(skip)]
616    pub link_viewable: Option<bool>,
617    #[garde(skip)]
618    pub user_manageable: Option<bool>,
619    #[garde(skip)]
620    pub basic_auth: Option<bool>,
621
622    #[garde(length(chars, min = 3, max = 255))]
623    #[schema(min_length = 3, max_length = 255)]
624    pub client_id: Option<compact_str::CompactString>,
625    #[garde(length(chars, min = 3, max = 255))]
626    #[schema(min_length = 3, max_length = 255)]
627    pub client_secret: Option<compact_str::CompactString>,
628
629    #[garde(length(chars, min = 3, max = 255))]
630    #[schema(min_length = 3, max_length = 255)]
631    pub auth_url: Option<String>,
632    #[garde(length(chars, min = 3, max = 255))]
633    #[schema(min_length = 3, max_length = 255)]
634    pub token_url: Option<String>,
635    #[garde(length(chars, min = 3, max = 255))]
636    #[schema(min_length = 3, max_length = 255)]
637    pub info_url: Option<String>,
638    #[garde(length(max = 255))]
639    #[schema(max_length = 255)]
640    pub scopes: Option<Vec<compact_str::CompactString>>,
641
642    #[garde(length(chars, min = 3, max = 255))]
643    #[schema(min_length = 3, max_length = 255)]
644    pub identifier_path: Option<String>,
645    #[garde(length(chars, min = 1, max = 255))]
646    #[schema(min_length = 1, max_length = 255)]
647    #[serde(
648        default,
649        skip_serializing_if = "Option::is_none",
650        with = "::serde_with::rust::double_option"
651    )]
652    pub email_path: Option<Option<String>>,
653    #[garde(length(chars, min = 1, max = 255))]
654    #[schema(min_length = 1, max_length = 255)]
655    #[serde(
656        default,
657        skip_serializing_if = "Option::is_none",
658        with = "::serde_with::rust::double_option"
659    )]
660    pub username_path: Option<Option<String>>,
661    #[garde(length(chars, min = 1, max = 255))]
662    #[schema(min_length = 1, max_length = 255)]
663    #[serde(
664        default,
665        skip_serializing_if = "Option::is_none",
666        with = "::serde_with::rust::double_option"
667    )]
668    pub name_first_path: Option<Option<String>>,
669    #[garde(length(chars, min = 1, max = 255))]
670    #[schema(min_length = 1, max_length = 255)]
671    #[serde(
672        default,
673        skip_serializing_if = "Option::is_none",
674        with = "::serde_with::rust::double_option"
675    )]
676    pub name_last_path: Option<Option<String>>,
677}
678
679#[async_trait::async_trait]
680impl UpdatableModel for OAuthProvider {
681    type UpdateOptions = UpdateOAuthProviderOptions;
682
683    fn get_update_handlers() -> &'static LazyLock<UpdateHandlerList<Self>> {
684        static UPDATE_LISTENERS: LazyLock<UpdateHandlerList<OAuthProvider>> =
685            LazyLock::new(|| Arc::new(ModelHandlerList::default()));
686
687        &UPDATE_LISTENERS
688    }
689
690    async fn update_with_transaction(
691        &mut self,
692        state: &crate::State,
693        mut options: Self::UpdateOptions,
694        transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
695    ) -> Result<(), crate::database::DatabaseError> {
696        options.validate()?;
697
698        let mut query_builder = UpdateQueryBuilder::new("oauth_providers");
699
700        self.run_update_handlers(&mut options, &mut query_builder, state, transaction)
701            .await?;
702
703        let encrypted_client_secret = if let Some(ref client_secret) = options.client_secret {
704            Some(
705                state
706                    .database
707                    .encrypt(client_secret.to_string())
708                    .await
709                    .map_err(|err| sqlx::Error::Encode(err.into()))?,
710            )
711        } else {
712            None
713        };
714
715        query_builder
716            .set("name", options.name.as_ref())
717            .set(
718                "description",
719                options.description.as_ref().map(|d| d.as_ref()),
720            )
721            .set("client_id", options.client_id.as_ref())
722            .set("client_secret", encrypted_client_secret)
723            .set("auth_url", options.auth_url.as_ref())
724            .set("token_url", options.token_url.as_ref())
725            .set("info_url", options.info_url.as_ref())
726            .set("scopes", options.scopes.as_ref())
727            .set("identifier_path", options.identifier_path.as_ref())
728            .set(
729                "email_path",
730                options.email_path.as_ref().map(|e| e.as_ref()),
731            )
732            .set(
733                "username_path",
734                options.username_path.as_ref().map(|u| u.as_ref()),
735            )
736            .set(
737                "name_first_path",
738                options.name_first_path.as_ref().map(|n| n.as_ref()),
739            )
740            .set(
741                "name_last_path",
742                options.name_last_path.as_ref().map(|n| n.as_ref()),
743            )
744            .set("enabled", options.enabled)
745            .set("login_only", options.login_only)
746            .set("login_bypass_2fa", options.login_bypass_2fa)
747            .set("link_viewable", options.link_viewable)
748            .set("user_manageable", options.user_manageable)
749            .set("basic_auth", options.basic_auth)
750            .where_eq("uuid", self.uuid);
751
752        query_builder.execute(&mut **transaction).await?;
753
754        if let Some(name) = options.name {
755            self.name = name;
756        }
757        if let Some(description) = options.description {
758            self.description = description;
759        }
760        if let Some(enabled) = options.enabled {
761            self.enabled = enabled;
762        }
763        if let Some(login_only) = options.login_only {
764            self.login_only = login_only;
765        }
766        if let Some(login_bypass_2fa) = options.login_bypass_2fa {
767            self.login_bypass_2fa = login_bypass_2fa;
768        }
769        if let Some(link_viewable) = options.link_viewable {
770            self.link_viewable = link_viewable;
771        }
772        if let Some(user_manageable) = options.user_manageable {
773            self.user_manageable = user_manageable;
774        }
775        if let Some(basic_auth) = options.basic_auth {
776            self.basic_auth = basic_auth;
777        }
778        if let Some(client_id) = options.client_id {
779            self.client_id = client_id;
780        }
781        if let Some(client_secret) = options.client_secret {
782            self.client_secret = state
783                .database
784                .encrypt(client_secret)
785                .await
786                .map_err(|err| sqlx::Error::Encode(err.into()))?;
787        }
788        if let Some(auth_url) = options.auth_url {
789            self.auth_url = auth_url;
790        }
791        if let Some(token_url) = options.token_url {
792            self.token_url = token_url;
793        }
794        if let Some(info_url) = options.info_url {
795            self.info_url = info_url;
796        }
797        if let Some(scopes) = options.scopes {
798            self.scopes = scopes;
799        }
800        if let Some(identifier_path) = options.identifier_path {
801            self.identifier_path = identifier_path;
802        }
803        if let Some(email_path) = options.email_path {
804            self.email_path = email_path;
805        }
806        if let Some(username_path) = options.username_path {
807            self.username_path = username_path;
808        }
809        if let Some(name_first_path) = options.name_first_path {
810            self.name_first_path = name_first_path;
811        }
812        if let Some(name_last_path) = options.name_last_path {
813            self.name_last_path = name_last_path;
814        }
815
816        self.run_after_update_handlers(state, transaction).await?;
817
818        Ok(())
819    }
820}
821
822#[async_trait::async_trait]
823impl DeletableModel for OAuthProvider {
824    type DeleteOptions = ();
825
826    fn get_delete_handlers() -> &'static LazyLock<DeleteHandlerList<Self>> {
827        static DELETE_LISTENERS: LazyLock<DeleteHandlerList<OAuthProvider>> =
828            LazyLock::new(|| Arc::new(ModelHandlerList::default()));
829
830        &DELETE_LISTENERS
831    }
832
833    async fn delete_with_transaction(
834        &self,
835        state: &crate::State,
836        options: Self::DeleteOptions,
837        transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
838    ) -> Result<(), anyhow::Error> {
839        self.run_delete_handlers(&options, state, transaction)
840            .await?;
841
842        sqlx::query(
843            r#"
844            DELETE FROM oauth_providers
845            WHERE oauth_providers.uuid = $1
846            "#,
847        )
848        .bind(self.uuid)
849        .execute(&mut **transaction)
850        .await?;
851
852        self.run_after_delete_handlers(&options, state, transaction)
853            .await?;
854
855        Ok(())
856    }
857}
858
859#[schema_extension_derive::extendible]
860#[init_args(OAuthProvider, crate::State)]
861#[hook_args(crate::State)]
862#[derive(ToSchema, Serialize)]
863#[schema(title = "AdminOAuthProvider")]
864pub struct AdminApiOAuthProvider {
865    pub uuid: uuid::Uuid,
866
867    pub name: compact_str::CompactString,
868    pub description: Option<compact_str::CompactString>,
869
870    pub client_id: compact_str::CompactString,
871    pub client_secret: compact_str::CompactString,
872    pub auth_url: String,
873    pub token_url: String,
874    pub info_url: String,
875    pub scopes: Vec<compact_str::CompactString>,
876
877    pub identifier_path: String,
878    pub email_path: Option<String>,
879    pub username_path: Option<String>,
880    pub name_first_path: Option<String>,
881    pub name_last_path: Option<String>,
882
883    pub enabled: bool,
884    pub login_only: bool,
885    pub login_bypass_2fa: bool,
886    pub link_viewable: bool,
887    pub user_manageable: bool,
888    pub basic_auth: bool,
889
890    pub created: chrono::DateTime<chrono::Utc>,
891}
892
893#[schema_extension_derive::extendible]
894#[init_args(OAuthProvider, crate::State)]
895#[hook_args(crate::State)]
896#[derive(ToSchema, Serialize)]
897#[schema(title = "OAuthProvider")]
898pub struct ApiOAuthProvider {
899    pub uuid: uuid::Uuid,
900
901    pub name: compact_str::CompactString,
902
903    pub link_viewable: bool,
904    pub user_manageable: bool,
905}