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