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}