1use crate::domain::authorization::AuthorizedResponse;
6use actix_web::{
7 HttpResponse, HttpResponseBuilder, error,
8 http::{StatusCode, header::ContentType},
9};
10use backtrace::Backtrace;
11use derive_more::Display;
12use dpop_verifier::error::DpopError;
13use headless_lms_chatbot::prelude::ChatbotError;
14use headless_lms_models::{ModelError, ModelErrorType};
15use headless_lms_utils::error::{
16 backend_error::BackendError, backtrace_formatter::format_backtrace, util_error::UtilError,
17};
18use serde::{Deserialize, Serialize};
19use tracing_error::SpanTrace;
20#[cfg(feature = "ts_rs")]
21use ts_rs::TS;
22use uuid::Uuid;
23
24pub type ControllerResult<T, E = ControllerError> = std::result::Result<AuthorizedResponse<T>, E>;
31
32#[derive(Debug, Display, Serialize, Deserialize)]
34pub enum ControllerErrorType {
35 #[display("Internal server error")]
37 InternalServerError,
38
39 #[display("Bad request")]
41 BadRequest,
42
43 #[display("Bad request")]
45 BadRequestWithData(ErrorData),
46
47 #[display("Not found")]
49 NotFound,
50
51 #[display("Unauthorized")]
53 Unauthorized,
54
55 #[display("Forbidden")]
57 Forbidden,
58
59 #[display("OAuthError")]
61 OAuthError(Box<OAuthErrorData>),
62}
63
64pub struct ControllerError {
128 error_type: <ControllerError as BackendError>::ErrorType,
129 message: String,
130 source: Option<anyhow::Error>,
132 span_trace: Box<SpanTrace>,
134 backtrace: Box<Backtrace>,
136}
137
138impl std::fmt::Debug for ControllerError {
140 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
141 f.debug_struct("ControllerError")
142 .field("error_type", &self.error_type)
143 .field("message", &self.message)
144 .field("source", &self.source)
145 .finish()?;
146
147 f.write_str("\n\nOperating system thread stack backtrace:\n")?;
148 format_backtrace(&self.backtrace, f)?;
149
150 f.write_str("\n\nTokio tracing span trace:\n")?;
151 f.write_fmt(format_args!("{}\n", &self.span_trace))?;
152
153 Ok(())
154 }
155}
156
157impl std::error::Error for ControllerError {
158 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
159 self.source.as_ref().and_then(|o| o.source())
160 }
161
162 fn cause(&self) -> Option<&dyn std::error::Error> {
163 self.source()
164 }
165}
166
167impl std::fmt::Display for ControllerError {
168 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
169 write!(
170 f,
171 "ControllerError {:?} {:?}",
172 self.error_type, self.message
173 )
174 }
175}
176
177impl BackendError for ControllerError {
178 type ErrorType = ControllerErrorType;
179
180 fn new<M: Into<String>, S: Into<Option<anyhow::Error>>>(
181 error_type: Self::ErrorType,
182 message: M,
183 source_error: S,
184 ) -> Self {
185 Self::new_with_traces(
186 error_type,
187 message,
188 source_error,
189 Backtrace::new(),
190 SpanTrace::capture(),
191 )
192 }
193
194 fn backtrace(&self) -> Option<&Backtrace> {
195 Some(&self.backtrace)
196 }
197
198 fn error_type(&self) -> &Self::ErrorType {
199 &self.error_type
200 }
201
202 fn message(&self) -> &str {
203 &self.message
204 }
205
206 fn span_trace(&self) -> &SpanTrace {
207 &self.span_trace
208 }
209
210 fn new_with_traces<M: Into<String>, S: Into<Option<anyhow::Error>>>(
211 error_type: Self::ErrorType,
212 message: M,
213 source_error: S,
214 backtrace: Backtrace,
215 span_trace: SpanTrace,
216 ) -> Self {
217 Self {
218 error_type,
219 message: message.into(),
220 source: source_error.into(),
221 span_trace: Box::new(span_trace),
222 backtrace: Box::new(backtrace),
223 }
224 }
225}
226
227#[derive(Debug, Serialize, Deserialize, Clone)]
228#[cfg_attr(feature = "ts_rs", derive(TS))]
229#[serde(rename_all = "snake_case")]
230pub enum ErrorData {
231 BlockId(Uuid),
232}
233
234#[derive(Debug, Serialize, Deserialize)]
236#[cfg_attr(feature = "ts_rs", derive(TS))]
237pub struct ErrorResponse {
238 pub title: String,
239 pub message: String,
240 pub source: Option<String>,
241 pub data: Option<ErrorData>,
242}
243
244impl error::ResponseError for ControllerError {
245 fn error_response(&self) -> HttpResponse {
246 if let ControllerErrorType::InternalServerError = &self.error_type {
247 use std::fmt::Write as _;
248 let mut err_string = String::new();
249 let mut source = Some(self as &dyn std::error::Error);
250 while let Some(err) = source {
251 let _ = write!(err_string, "{}\n ", err);
252 source = err.source();
253 }
254 error!("Internal server error: {}", err_string);
255 }
256 if let ControllerErrorType::OAuthError(data) = &self.error_type {
257 if let Some(uri) = &data.redirect_uri
258 && let Ok(mut url) = url::Url::parse(uri)
259 {
260 {
261 let mut qp = url.query_pairs_mut();
262 qp.append_pair("error", &data.error);
263 qp.append_pair("error_description", &data.error_description);
264 if let Some(state) = &data.state {
265 qp.append_pair("state", state);
266 }
267 }
268 let loc = url.to_string();
269 return HttpResponse::Found()
270 .append_header(("Location", loc))
271 .finish();
272 }
273
274 let status = match data.error.as_str() {
275 "invalid_client" => StatusCode::UNAUTHORIZED, "invalid_token" => StatusCode::UNAUTHORIZED, "invalid_dpop_proof" => StatusCode::UNAUTHORIZED, "use_dpop_nonce" => StatusCode::UNAUTHORIZED, "insufficient_scope" => StatusCode::FORBIDDEN, _ => StatusCode::BAD_REQUEST,
281 };
282
283 let mut res = HttpResponse::build(status);
284 fn escape_auth_param(s: &str) -> String {
286 s.replace('\\', "\\\\").replace('"', "\\\"")
287 }
288
289 match data.error.as_str() {
290 "invalid_client" | "invalid_token" | "insufficient_scope" | "invalid_request" => {
292 let err = escape_auth_param(&data.error);
293 let desc = escape_auth_param(&data.error_description);
294 let hdr = format!(r#"Bearer error="{}", error_description="{}""#, err, desc);
295 res.append_header(("WWW-Authenticate", hdr));
296 }
297
298 "invalid_dpop_proof" => {
300 let err = escape_auth_param(&data.error);
301 let desc = escape_auth_param(&data.error_description);
302 let hdr = format!(r#"DPoP error="{}", error_description="{}""#, err, desc);
303 res.append_header(("WWW-Authenticate", hdr));
304 }
305
306 "use_dpop_nonce" => {
307 let err = escape_auth_param(&data.error);
308 let desc = escape_auth_param(&data.error_description);
309 let hdr = format!(r#"DPoP error="{}", error_description="{}""#, err, desc);
310 res.append_header(("WWW-Authenticate", hdr));
311
312 if let Some(nonce) = &data.nonce {
314 res.append_header(("DPoP-Nonce", nonce.clone()));
315 }
316 }
317
318 _ => {}
319 }
320
321 res.append_header(("Cache-Control", "no-store"))
323 .append_header(("Pragma", "no-cache"));
324
325 return res.json(serde_json::json!({
326 "error": data.error,
327 "error_description": data.error_description
328 }));
329 }
330
331 let status = self.status_code();
332
333 let error_data = match &self.error_type {
334 ControllerErrorType::BadRequestWithData(data) => Some(data.clone()),
335 _ => None,
336 };
337
338 let source_message = self.source.as_ref().map(|anyhow_err| {
339 if let Some(controller_err) = anyhow_err.downcast_ref::<ControllerError>() {
340 controller_err.message.clone()
341 } else {
342 anyhow_err.to_string()
343 }
344 });
345
346 let error_response = ErrorResponse {
347 title: status
348 .canonical_reason()
349 .map(str::to_string)
350 .unwrap_or_else(|| status.to_string()),
351 message: self.message.clone(),
352 source: source_message,
353 data: error_data,
354 };
355
356 HttpResponseBuilder::new(status)
357 .append_header(ContentType::json())
358 .body(
359 serde_json::to_string(&error_response).unwrap_or_else(|_| {
360 r#"{"title":"Internal Server Error","message":"Error occurred while formatting error message."}"#.to_string()
361 }),
362 )
363 }
364
365 fn status_code(&self) -> StatusCode {
366 match self.error_type {
367 ControllerErrorType::InternalServerError => StatusCode::INTERNAL_SERVER_ERROR,
368 ControllerErrorType::BadRequest => StatusCode::BAD_REQUEST,
369 ControllerErrorType::BadRequestWithData(_) => StatusCode::BAD_REQUEST,
370 ControllerErrorType::NotFound => StatusCode::NOT_FOUND,
371 ControllerErrorType::Unauthorized => StatusCode::UNAUTHORIZED,
372 ControllerErrorType::Forbidden => StatusCode::FORBIDDEN,
373 ControllerErrorType::OAuthError(_) => StatusCode::OK,
374 }
375 }
376}
377
378#[derive(Debug, Serialize, Deserialize, Clone)]
379#[serde(rename_all = "snake_case")]
380pub struct OAuthErrorData {
381 pub error: String,
382 pub error_description: String,
383 pub redirect_uri: Option<String>,
384 pub state: Option<String>,
385 pub nonce: Option<String>,
386}
387
388pub enum OAuthErrorCode {
389 InvalidGrant,
390 InvalidRequest,
391 InvalidClient,
392 InvalidToken,
393 InsufficientScope,
394 UnsupportedGrantType,
395 UnsupportedResponseType,
396 ServerError,
397 InvalidDpopProof,
398 UseDpopNonce,
399}
400
401impl OAuthErrorCode {
402 pub fn as_str(&self) -> &'static str {
403 match self {
404 Self::InvalidGrant => "invalid_grant",
405 Self::InvalidRequest => "invalid_request",
406 Self::InvalidClient => "invalid_client",
407 Self::InvalidToken => "invalid_token",
408 Self::InsufficientScope => "insufficient_scope",
409 Self::UnsupportedGrantType => "unsupported_grant_type",
410 Self::UnsupportedResponseType => "unsupported_response_type",
411 Self::ServerError => "server_error",
412 Self::InvalidDpopProof => "invalid_dpop_proof",
413 Self::UseDpopNonce => "use_dpop_nonce",
414 }
415 }
416}
417
418impl From<anyhow::Error> for ControllerError {
419 fn from(err: anyhow::Error) -> ControllerError {
420 if let Some(sqlx::Error::RowNotFound) = err.downcast_ref::<sqlx::Error>() {
421 return Self::new(ControllerErrorType::NotFound, err.to_string(), Some(err));
422 }
423
424 Self::new(
425 ControllerErrorType::InternalServerError,
426 err.to_string(),
427 Some(err),
428 )
429 }
430}
431
432impl From<uuid::Error> for ControllerError {
433 fn from(err: uuid::Error) -> ControllerError {
434 Self::new(
435 ControllerErrorType::BadRequest,
436 err.to_string(),
437 Some(err.into()),
438 )
439 }
440}
441
442impl From<sqlx::Error> for ControllerError {
443 fn from(err: sqlx::Error) -> ControllerError {
444 Self::new(
445 ControllerErrorType::InternalServerError,
446 err.to_string(),
447 Some(err.into()),
448 )
449 }
450}
451
452impl From<git2::Error> for ControllerError {
453 fn from(err: git2::Error) -> ControllerError {
454 Self::new(
455 ControllerErrorType::InternalServerError,
456 err.to_string(),
457 Some(err.into()),
458 )
459 }
460}
461
462impl From<actix_web::Error> for ControllerError {
463 fn from(err: actix_web::Error) -> Self {
464 Self::new(
465 ControllerErrorType::InternalServerError,
466 err.to_string(),
467 None,
468 )
469 }
470}
471
472impl From<actix_multipart::MultipartError> for ControllerError {
473 fn from(err: actix_multipart::MultipartError) -> Self {
474 Self::new(
475 ControllerErrorType::InternalServerError,
476 err.to_string(),
477 None,
478 )
479 }
480}
481
482impl From<jsonwebtoken::errors::Error> for ControllerError {
483 fn from(err: jsonwebtoken::errors::Error) -> Self {
484 Self::new(
485 ControllerErrorType::InternalServerError,
486 err.to_string(),
487 None,
488 )
489 }
490}
491
492impl From<ModelError> for ControllerError {
493 fn from(err: ModelError) -> Self {
494 let backtrace: Backtrace =
495 match headless_lms_utils::error::backend_error::BackendError::backtrace(&err) {
496 Some(backtrace) => backtrace.clone(),
497 _ => Backtrace::new(),
498 };
499 let span_trace = err.span_trace().clone();
500 match err.error_type() {
501 ModelErrorType::RecordNotFound => Self::new_with_traces(
502 ControllerErrorType::NotFound,
503 err.to_string(),
504 Some(err.into()),
505 backtrace,
506 span_trace,
507 ),
508 ModelErrorType::NotFound => Self::new_with_traces(
509 ControllerErrorType::NotFound,
510 err.to_string(),
511 Some(err.into()),
512 backtrace,
513 span_trace,
514 ),
515 ModelErrorType::PreconditionFailed => Self::new_with_traces(
516 ControllerErrorType::BadRequest,
517 err.message().to_string(),
518 Some(err.into()),
519 backtrace,
520 span_trace,
521 ),
522 ModelErrorType::PreconditionFailedWithCMSAnchorBlockId { description, id } => {
523 Self::new_with_traces(
524 ControllerErrorType::BadRequestWithData(ErrorData::BlockId(*id)),
525 description.to_string(),
526 Some(err.into()),
527 backtrace,
528 span_trace,
529 )
530 }
531 ModelErrorType::DatabaseConstraint { description, .. } => Self::new_with_traces(
532 ControllerErrorType::BadRequest,
533 description.to_string(),
534 Some(err.into()),
535 backtrace,
536 span_trace,
537 ),
538 ModelErrorType::InvalidRequest => Self::new_with_traces(
539 ControllerErrorType::BadRequest,
540 err.message().to_string(),
541 Some(err.into()),
542 backtrace,
543 span_trace,
544 ),
545 _ => Self::new_with_traces(
546 ControllerErrorType::InternalServerError,
547 err.to_string(),
548 Some(err.into()),
549 backtrace,
550 span_trace,
551 ),
552 }
553 }
554}
555
556impl From<UtilError> for ControllerError {
557 fn from(err: UtilError) -> Self {
558 let backtrace: Backtrace =
559 match headless_lms_utils::error::backend_error::BackendError::backtrace(&err) {
560 Some(backtrace) => backtrace.clone(),
561 _ => Backtrace::new(),
562 };
563 let span_trace = err.span_trace().clone();
564 Self::new_with_traces(
565 ControllerErrorType::InternalServerError,
566 err.to_string(),
567 Some(err.into()),
568 backtrace,
569 span_trace,
570 )
571 }
572}
573
574impl From<serde_json::Error> for ControllerError {
575 fn from(err: serde_json::Error) -> Self {
576 Self::new(
577 ControllerErrorType::InternalServerError,
578 err.to_string(),
579 Some(err.into()),
580 )
581 }
582}
583
584impl From<base64::DecodeError> for ControllerError {
585 fn from(err: base64::DecodeError) -> Self {
586 Self::new(
587 ControllerErrorType::InternalServerError,
588 err.to_string(),
589 Some(err.into()),
590 )
591 }
592}
593
594impl From<std::string::FromUtf8Error> for ControllerError {
595 fn from(err: std::string::FromUtf8Error) -> Self {
596 Self::new(
597 ControllerErrorType::InternalServerError,
598 err.to_string(),
599 Some(err.into()),
600 )
601 }
602}
603
604impl From<pkcs8::spki::Error> for ControllerError {
605 fn from(err: pkcs8::spki::Error) -> Self {
606 Self::new(
607 ControllerErrorType::InternalServerError,
608 err.to_string(),
609 Some(err.into()),
610 )
611 }
612}
613
614impl From<dpop_verifier::error::DpopError> for ControllerError {
615 fn from(err: DpopError) -> Self {
616 let oauth_error = match &err {
617 DpopError::MultipleDpopHeaders
618 | DpopError::InvalidDpopHeader
619 | DpopError::MissingDpopHeader
620 | DpopError::MalformedJws
621 | DpopError::InvalidAlg(_)
622 | DpopError::UnsupportedAlg(_)
623 | DpopError::InvalidSignature
624 | DpopError::BadJwk(_)
625 | DpopError::MissingClaim(_)
626 | DpopError::InvalidMethod
627 | DpopError::HtmMismatch
628 | DpopError::MalformedHtu
629 | DpopError::HtuMismatch
630 | DpopError::AthMalformed
631 | DpopError::MissingAth
632 | DpopError::AthMismatch
633 | DpopError::FutureSkew
634 | DpopError::Stale
635 | DpopError::Replay
636 | DpopError::JtiTooLong
637 | DpopError::NonceMismatch
638 | DpopError::NonceStale
639 | DpopError::InvalidHmacConfig
640 | DpopError::MissingNonce => OAuthErrorData {
641 error: OAuthErrorCode::InvalidDpopProof.as_str().into(),
642 error_description: err.to_string(),
643 redirect_uri: None,
644 state: None,
645 nonce: None,
646 },
647
648 DpopError::Store(e) => OAuthErrorData {
649 error: OAuthErrorCode::ServerError.as_str().into(),
650 error_description: format!("DPoP storage error: {e}"),
651 redirect_uri: None,
652 state: None,
653 nonce: None,
654 },
655
656 DpopError::UseDpopNonce { nonce } => OAuthErrorData {
657 error: OAuthErrorCode::UseDpopNonce.as_str().into(), error_description: "Server requires DPoP nonce".into(),
659 redirect_uri: None,
660 state: None,
661 nonce: Some(nonce.clone()),
662 },
663 };
664
665 ControllerError::new(
666 ControllerErrorType::OAuthError(Box::new(oauth_error)),
667 err.to_string(),
668 Some(err.into()),
669 )
670 }
671}
672
673#[derive(Debug, thiserror::Error)]
674pub enum PkceFlowError {
675 #[error("{0}")]
677 InvalidRequest(&'static str),
678
679 #[error("{0}")]
681 InvalidGrant(&'static str),
682
683 #[error("{0}")]
685 ServerError(&'static str),
686}
687
688impl From<PkceFlowError> for ControllerError {
689 fn from(err: PkceFlowError) -> Self {
690 let data = match &err {
691 PkceFlowError::InvalidRequest(msg) => OAuthErrorData {
692 error: OAuthErrorCode::InvalidRequest.as_str().into(),
693 error_description: (*msg).into(),
694 redirect_uri: None,
695 state: None,
696 nonce: None,
697 },
698 PkceFlowError::InvalidGrant(msg) => OAuthErrorData {
699 error: OAuthErrorCode::InvalidGrant.as_str().into(),
700 error_description: (*msg).into(),
701 redirect_uri: None,
702 state: None,
703 nonce: None,
704 },
705 PkceFlowError::ServerError(msg) => OAuthErrorData {
706 error: OAuthErrorCode::ServerError.as_str().into(),
707 error_description: (*msg).into(),
708 redirect_uri: None,
709 state: None,
710 nonce: None,
711 },
712 };
713
714 ControllerError::new(
715 ControllerErrorType::OAuthError(Box::new(data)),
716 err.to_string(),
717 Some(anyhow::anyhow!(err)),
718 )
719 }
720}
721
722impl From<crate::domain::oauth::pkce::PkceError> for PkceFlowError {
723 fn from(_err: crate::domain::oauth::pkce::PkceError) -> Self {
724 PkceFlowError::InvalidRequest("invalid code_verifier")
726 }
727}
728
729impl From<crate::domain::oauth::pkce::PkceError> for ControllerError {
730 fn from(err: crate::domain::oauth::pkce::PkceError) -> Self {
731 PkceFlowError::from(err).into()
732 }
733}
734
735impl From<ChatbotError> for ControllerError {
736 fn from(err: ChatbotError) -> Self {
737 ControllerError::new(
738 ControllerErrorType::InternalServerError,
739 err.message().to_string(),
740 Some(err.into()),
741 )
742 }
743}