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}