wasm_oidc_plugin/
error.rs1use proxy_wasm::traits::HttpContext;
3
4use html_escape::encode_safe;
6
7use thiserror::Error;
9
10use crate::auth::OidcHttpContext;
12
13#[derive(Error, Debug)]
15#[allow(clippy::enum_variant_names)]
16pub enum PluginError {
17 #[error("url is not valid: {0}")]
19 UrlError(#[from] url::ParseError),
20 #[error("error while parsing the configuration file: {0}")]
21 YamlError(#[from] serde_yaml::Error),
22 #[error("error while parsing from json: {0}")]
23 JsonError(#[from] serde_json::Error),
24 #[error("error parsing utf8: {0}")]
25 Utf8Error(#[from] std::string::FromUtf8Error),
26 #[error("error while parsing from base64: {0}")]
27 DecodeError(#[from] base64::DecodeError),
28
29 #[error("error while getting code from callback: {0}")]
31 CodeNotFoundInCallbackError(#[from] serde_urlencoded::de::Error),
32 #[error("the code is coming from an unknown provider: {0}")]
33 ProviderNotFoundError(String),
34 #[error("token response is not in the required format: {0}")]
35 TokenResponseFormatError(String),
36 #[error("token validation failed: {0}")]
37 TokenValidationError(#[from] jwt_simple::Error),
38 #[error("no key worked for validation")]
39 NoKeyError,
40
41 #[error("dispatch failed, maybe you forgot to add the upstream?")]
43 DispatchError,
44 #[error("token_id mismatch")]
45 TokenIdMismatchError,
46 #[error("no body in response")]
47 NoBodyError,
48
49 #[error("encryption or decryption failed: {0}")]
51 AesError(#[from] aes_gcm::Error),
52 #[error("token could not be stored in cookie: {0}")]
53 CookieStoreError(String),
54 #[error("cookie is not valid: {0}")]
55 CookieValidationError(String),
56 #[error("session cookie not found")]
57 SessionCookieNotFoundError,
58 #[error("nonce cookie not found")]
59 NonceCookieNotFoundError,
60 #[error("authorization state not found")]
61 AuthorizationStateNotFoundError,
62 #[error("state does not match")]
63 StateMismatchError,
64}
65
66impl OidcHttpContext {
67 pub fn show_error_page(
68 &self,
69 status_code: u32,
70 title: &str,
71 message: &str,
72 show_reset_button: bool,
73 ) {
74 let headers = vec![("cache-control", "no-cache"), ("content-type", "text/html")];
75 let title = encode_safe(title);
76 let message = encode_safe(message);
77 let request_id = encode_safe(&self.request_id);
78 let reset_button = if show_reset_button {
79 r#"<a class="reset-button" href="/_wasm-oidc-plugin/clear-cookies">Reset Cookies</a>"#
80 } else {
81 ""
82 };
83
84 self.send_http_response(
85 status_code,
86 headers,
87 Some(
88 format!(
89 r#"
90 <!DOCTYPE html>
91 <html lang="en">
92 <head>
93 <meta charset="UTF-8">
94 <meta name="viewport" content="width=device-width, initial-scale=1.0">
95 <title>Error - {status_code}</title>
96 <style>
97 :root {{
98 --bg-color: #f0f2f5;
99 --text-color: #333;
100 --card-bg: #ffffff;
101 --card-border: #e9ecef;
102 --toggle-bg: #e2e8f0;
103 --toggle-border: #cbd5e1;
104 --button-bg: #3498db;
105 --button-text: #ffffff;
106 --button-hover: #2980b9;
107 }}
108 .dark-mode {{
109 --bg-color: #1a1a1a;
110 --text-color: #ffffff;
111 --card-bg: #2c2c2c;
112 --card-border: #4a4a4a;
113 --toggle-bg: #4a5568;
114 --toggle-border: #2d3748;
115 --button-bg: #2980b9;
116 --button-text: #ffffff;
117 --button-hover: #3498db;
118 }}
119 body {{
120 font-family: Helvetica, sans-serif;
121 display: flex;
122 flex-direction: column;
123 justify-content: center;
124 align-items: center;
125 min-height: 100vh;
126 margin: 0;
127 background-color: var(--bg-color);
128 color: var(--text-color);
129 transition: background-color 0.3s ease, color 0.3s ease;
130 }}
131 .error-container {{
132 text-align: center;
133 padding: 40px;
134 background-color: var(--card-bg);
135 border-radius: 12px;
136 box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
137 max-width: 600px;
138 width: 90%;
139 }}
140 h1 {{
141 margin-bottom: 10px;
142 }}
143 h2 {{
144 margin-bottom: 20px;
145 color: #e74c3c;
146 }}
147 p {{
148 margin-bottom: 10px;
149 }}
150 .request-id {{
151 font-size: 0.8em;
152 color: #888;
153 }}
154 .dark-mode-toggle {{
155 position: fixed;
156 top: 20px;
157 right: 20px;
158 }}
159 .toggle-switch {{
160 position: relative;
161 display: inline-block;
162 width: 60px;
163 height: 34px;
164 }}
165 .toggle-switch input {{
166 opacity: 0;
167 width: 0;
168 height: 0;
169 }}
170 .toggle-slider {{
171 position: absolute;
172 cursor: pointer;
173 top: 0;
174 left: 0;
175 right: 0;
176 bottom: 0;
177 background-color: var(--toggle-bg);
178 border: 2px solid var(--toggle-border);
179 transition: .4s;
180 border-radius: 34px;
181 }}
182 .toggle-slider:before {{
183 position: absolute;
184 content: "☀️";
185 display: flex;
186 align-items: center;
187 justify-content: center;
188 height: 26px;
189 width: 26px;
190 left: 4px;
191 bottom: 2px;
192 background-color: white;
193 transition: .4s;
194 border-radius: 50%;
195 }}
196 input:checked + .toggle-slider {{
197 background-color: var(--toggle-bg);
198 }}
199 input:checked + .toggle-slider:before {{
200 transform: translateX(26px);
201 content: "🌙";
202 background-color: #2c3e50;
203 }}
204 .reset-button {{
205 display: inline-block;
206 margin-top: 20px;
207 padding: 10px 20px;
208 background-color: var(--button-bg);
209 color: var(--button-text);
210 border: none;
211 border-radius: 5px;
212 cursor: pointer;
213 font-size: 16px;
214 text-decoration: none;
215 transition: background-color 0.3s ease;
216 }}
217 .reset-button:hover {{
218 background-color: var(--button-hover);
219 }}
220 </style>
221 </head>
222 <body>
223 <div class="dark-mode-toggle">
224 <label class="toggle-switch">
225 <input type="checkbox" id="darkModeToggle">
226 <span class="toggle-slider"></span>
227 </label>
228 </div>
229 <div class="error-container">
230 <h1>Error {status_code}</h1>
231 <h2>{title}</h2>
232 <p>{message}</p>
233 <p class="request-id">Request-ID: {request_id}</p>
234 {reset_button}
235 </div>
236 <script>
237 const darkModeToggle = document.getElementById('darkModeToggle');
238 const body = document.body;
239
240 function getDarkModePreference() {{
241 try {{
242 return localStorage.getItem('darkMode');
243 }} catch (error) {{
244 console.warn('localStorage is not available', error);
245 return null;
246 }}
247 }}
248
249 function setDarkModePreference(value) {{
250 try {{
251 if (value === null) {{
252 localStorage.removeItem('darkMode');
253 }} else {{
254 localStorage.setItem('darkMode', value);
255 }}
256 }} catch (error) {{
257 console.warn('localStorage is not available', error);
258 }}
259 }}
260
261 darkModeToggle.addEventListener('change', () => {{
262 body.classList.toggle('dark-mode');
263 }});
264
265 // Check for saved dark mode preference
266 if (getDarkModePreference() === 'enabled') {{
267 body.classList.add('dark-mode');
268 darkModeToggle.checked = true;
269 }}
270
271 // Save dark mode preference
272 darkModeToggle.addEventListener('change', () => {{
273 if (body.classList.contains('dark-mode')) {{
274 setDarkModePreference('enabled');
275 }} else {{
276 setDarkModePreference(null);
277 }}
278 }});
279 </script>
280 </body>
281 </html>
282 "#,
283 )
284 .as_bytes(),
285 ),
286 );
287 }
288}