better_auth_core/config/
api_error.rs1use serde::{Deserialize, Serialize};
2
3use crate::{AuthContext, AuthError, AuthRequest, AuthResponse, AuthResult, AuthSchema};
4
5#[derive(Debug, Clone, Default, Serialize, Deserialize)]
7#[serde(default, rename_all = "camelCase")]
8pub struct ApiErrorConfig {
9 #[serde(rename = "throw", skip_serializing_if = "Option::is_none")]
11 pub throw_errors: Option<bool>,
12 #[serde(rename = "errorURL")]
14 pub error_url: Option<String>,
15 pub customize_default_error_page: Option<ErrorPageCustomization>,
17}
18
19#[derive(Debug, Clone, Default, Serialize, Deserialize)]
21#[serde(default, rename_all = "camelCase")]
22pub struct ErrorPageCustomization {
23 pub colors: ErrorPageColors,
24 pub size: ErrorPageSize,
25 pub font: ErrorPageFont,
26 pub disable_title_border: bool,
27 pub disable_corner_decorations: bool,
28 pub disable_background_grid: bool,
29}
30
31#[derive(Debug, Clone, Default, Serialize, Deserialize)]
32#[serde(default, rename_all = "camelCase")]
33pub struct ErrorPageColors {
34 pub background: Option<String>,
35 pub foreground: Option<String>,
36 pub primary: Option<String>,
37 pub primary_foreground: Option<String>,
38 pub muted_foreground: Option<String>,
39 pub border: Option<String>,
40 pub destructive: Option<String>,
41 pub title_border: Option<String>,
42 pub title_color: Option<String>,
43 pub grid_color: Option<String>,
44 pub card_background: Option<String>,
45 pub corner_border: Option<String>,
46}
47
48#[derive(Debug, Clone, Default, Serialize, Deserialize)]
49#[serde(default, rename_all = "camelCase")]
50pub struct ErrorPageSize {
51 pub radius_sm: Option<String>,
52 pub radius_md: Option<String>,
54 pub radius_lg: Option<String>,
56 pub text_sm: Option<String>,
57 pub text2xl: Option<String>,
58 pub text4xl: Option<String>,
59 pub text6xl: Option<String>,
60}
61
62#[derive(Debug, Clone, Default, Serialize, Deserialize)]
63#[serde(default, rename_all = "camelCase")]
64pub struct ErrorPageFont {
65 pub default_family: Option<String>,
66 pub mono_family: Option<String>,
67}
68
69impl<S: AuthSchema> AuthContext<S> {
70 pub fn error_page_response(&self, request: &AuthRequest) -> AuthResult<AuthResponse> {
72 let http =
73 crate::hooks::current_request_hook_context().is_some_and(|context| context.is_http);
74 let original = if http {
75 Some(request)
76 } else {
77 request.original_request()
78 };
79 let url = original
80 .and_then(AuthRequest::url)
81 .ok_or_else(|| AuthError::internal("Invalid URL"))?;
82 let code = url
83 .query_pairs()
84 .find(|(name, _)| name == "error")
85 .map(|(_, value)| value.into_owned())
86 .unwrap_or_default();
87 let code = if super::error_page::valid_error_code(&code) {
88 &code
89 } else {
90 "UNKNOWN"
91 };
92 let description = url
93 .query_pairs()
94 .find(|(name, _)| name == "error_description")
95 .map(|(_, value)| value.into_owned())
96 .filter(|value| !value.is_empty());
97 let mut params = url::form_urlencoded::Serializer::new(String::new());
98 let _ = params.append_pair("error", code);
99 if let Some(description) = &description {
100 let _ = params.append_pair("error_description", description);
101 }
102 let params = params.finish();
103 let policy = &self.config.api_error;
104 if let Some(target) = policy
105 .error_url
106 .as_deref()
107 .filter(|target| !target.is_empty())
108 {
109 return Ok(AuthResponse::new(302).with_header(
110 "location",
111 crate::utils::url::append_query_params(target, ¶ms)?,
112 ));
113 }
114 if self.is_production()? && policy.customize_default_error_page.is_none() {
115 return Ok(AuthResponse::new(302).with_header("location", format!("/?{params}")));
116 }
117 Ok(AuthResponse::html(
118 200,
119 super::error_page::render(
120 code,
121 description.as_deref(),
122 policy.customize_default_error_page.as_ref(),
123 ),
124 )
125 .with_header("content-type", "text/html"))
126 }
127}