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