Coverage for .venv/lib/python3.13/site-packages/litellm/proxy/common_utils/html_forms/jwt_display_template.py: 100%
2 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 12:01 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 12:01 +0000
1from typing import Final
3# JWT display template for SSO debug callback
4jwt_display_template: Final = """
5<!DOCTYPE html>
6<html lang="en">
7<head>
8 <meta charset="UTF-8">
9 <title>LiteLLM SSO Debug - JWT Information</title>
10 <meta name="viewport" content="width=device-width, initial-scale=1.0">
11 <style>
12 body {
13 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
14 background-color: #f8fafc;
15 margin: 0;
16 padding: 20px;
17 display: flex;
18 justify-content: center;
19 align-items: flex-start;
20 min-height: 100vh;
21 color: #333;
22 }
24 .container {
25 background-color: #fff;
26 padding: 40px;
27 border-radius: 8px;
28 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
29 width: 800px;
30 max-width: 100%;
31 }
33 .logo-container {
34 text-align: center;
35 margin-bottom: 30px;
36 }
38 .logo {
39 font-size: 24px;
40 font-weight: 600;
41 color: #1e293b;
42 }
44 h2 {
45 margin: 0 0 10px;
46 color: #1e293b;
47 font-size: 28px;
48 font-weight: 600;
49 text-align: center;
50 }
52 h3 {
53 margin: 0 0 12px;
54 color: #1e293b;
55 font-size: 18px;
56 font-weight: 600;
57 }
59 .subtitle {
60 color: #64748b;
61 margin: 0 0 20px;
62 font-size: 16px;
63 text-align: center;
64 }
66 .info-box {
67 background-color: #f1f5f9;
68 border-radius: 6px;
69 padding: 20px;
70 margin-bottom: 20px;
71 border-left: 4px solid #2563eb;
72 }
74 .success-box {
75 background-color: #f0fdf4;
76 border-radius: 6px;
77 padding: 20px;
78 margin-bottom: 20px;
79 border-left: 4px solid #16a34a;
80 }
82 .info-header {
83 display: flex;
84 align-items: center;
85 margin-bottom: 12px;
86 color: #1e40af;
87 font-weight: 600;
88 font-size: 16px;
89 }
91 .success-header {
92 display: flex;
93 align-items: center;
94 margin-bottom: 12px;
95 color: #166534;
96 font-weight: 600;
97 font-size: 16px;
98 }
100 .info-header svg, .success-header svg {
101 margin-right: 8px;
102 }
104 .data-container {
105 margin-top: 20px;
106 }
108 .data-row {
109 display: flex;
110 border-bottom: 1px solid #e2e8f0;
111 padding: 12px 0;
112 }
114 .data-row:last-child {
115 border-bottom: none;
116 }
118 .data-label {
119 font-weight: 500;
120 color: #334155;
121 width: 220px;
122 flex-shrink: 0;
123 }
125 .data-value {
126 color: #475569;
127 word-break: break-all;
128 }
130 .empty-note {
131 color: #64748b;
132 font-style: italic;
133 margin: 0;
134 font-size: 14px;
135 }
137 .jwt-container {
138 background-color: #f8fafc;
139 border-radius: 6px;
140 padding: 15px;
141 margin-top: 12px;
142 overflow-x: auto;
143 border: 1px solid #e2e8f0;
144 }
146 .jwt-text {
147 font-family: monospace;
148 white-space: pre-wrap;
149 word-break: break-all;
150 margin: 0;
151 color: #334155;
152 }
154 .back-button {
155 display: inline-block;
156 background-color: #6466E9;
157 color: #fff;
158 text-decoration: none;
159 padding: 10px 16px;
160 border-radius: 6px;
161 font-weight: 500;
162 margin-top: 20px;
163 text-align: center;
164 }
166 .back-button:hover {
167 background-color: #4138C2;
168 text-decoration: none;
169 }
171 .buttons {
172 display: flex;
173 gap: 10px;
174 margin-top: 12px;
175 }
177 .copy-button {
178 background-color: #e2e8f0;
179 color: #334155;
180 border: none;
181 padding: 8px 12px;
182 border-radius: 4px;
183 cursor: pointer;
184 font-size: 14px;
185 display: flex;
186 align-items: center;
187 }
189 .copy-button:hover {
190 background-color: #cbd5e1;
191 }
193 .copy-button svg {
194 margin-right: 6px;
195 }
196 </style>
197</head>
198<body>
199 <div class="container">
200 <div class="logo-container">
201 <div class="logo">
202 🚅 LiteLLM
203 </div>
204 </div>
205 <h2>SSO Debug Information</h2>
206 <p class="subtitle">Results from the SSO authentication process.</p>
208 <div class="success-box">
209 <div class="success-header">
210 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
211 <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
212 <polyline points="22 4 12 14.01 9 11.01"></polyline>
213 </svg>
214 Authentication Successful
215 </div>
216 <p>The SSO authentication completed successfully. Below is the information returned by the provider.</p>
217 </div>
219 <div class="info-box">
220 <div class="info-header">
221 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
222 <circle cx="12" cy="12" r="10"></circle>
223 <line x1="12" y1="16" x2="12" y2="12"></line>
224 <line x1="12" y1="8" x2="12.01" y2="8"></line>
225 </svg>
226 Parsed by Proxy
227 </div>
228 <p class="empty-note">Fields the proxy extracted into its internal user model.</p>
229 <div class="data-container" id="parsedByProxy">
230 <!-- Populated by JavaScript -->
231 </div>
232 </div>
234 <div class="info-box">
235 <div class="info-header">
236 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
237 <circle cx="12" cy="12" r="10"></circle>
238 <line x1="12" y1="16" x2="12" y2="12"></line>
239 <line x1="12" y1="8" x2="12.01" y2="8"></line>
240 </svg>
241 Raw Claims (userinfo)
242 </div>
243 <p class="empty-note">Complete set of claims returned by the IdP's userinfo endpoint.</p>
244 <div class="jwt-container">
245 <pre class="jwt-text" id="rawClaims">Loading...</pre>
246 </div>
247 <div class="buttons">
248 <button class="copy-button" onclick="copyToClipboard('rawClaims')">
249 <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
250 <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
251 <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
252 </svg>
253 Copy
254 </button>
255 </div>
256 </div>
258 <div class="info-box">
259 <div class="info-header">
260 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
261 <circle cx="12" cy="12" r="10"></circle>
262 <line x1="12" y1="16" x2="12" y2="12"></line>
263 <line x1="12" y1="8" x2="12.01" y2="8"></line>
264 </svg>
265 Access Token Claims
266 </div>
267 <p class="empty-note">Decoded payload of the access token JWT (when the IdP issues one).</p>
268 <div class="jwt-container">
269 <pre class="jwt-text" id="accessTokenClaims">Loading...</pre>
270 </div>
271 <div class="buttons">
272 <button class="copy-button" onclick="copyToClipboard('accessTokenClaims')">
273 <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
274 <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
275 <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
276 </svg>
277 Copy
278 </button>
279 </div>
280 </div>
282 <a href="/sso/debug/login" class="back-button">
283 Try Another SSO Login
284 </a>
285 </div>
287 <script>
288 // This will be populated with the actual data from the server
289 const ssoData = SSO_DATA;
291 function renderParsed(container, parsed) {
292 container.innerHTML = '';
293 const entries = Object.entries(parsed || {});
294 if (entries.length === 0) {
295 const note = document.createElement('p');
296 note.className = 'empty-note';
297 note.textContent = 'No fields available.';
298 container.appendChild(note);
299 return;
300 }
301 for (const [key, value] of entries) {
302 const row = document.createElement('div');
303 row.className = 'data-row';
305 const label = document.createElement('div');
306 label.className = 'data-label';
307 label.textContent = key;
309 const dataValue = document.createElement('div');
310 dataValue.className = 'data-value';
311 if (value === null || value === undefined) {
312 dataValue.textContent = 'null';
313 } else if (typeof value === 'object') {
314 dataValue.textContent = JSON.stringify(value);
315 } else {
316 dataValue.textContent = String(value);
317 }
319 row.appendChild(label);
320 row.appendChild(dataValue);
321 container.appendChild(row);
322 }
323 }
325 function renderJson(elementId, value) {
326 const el = document.getElementById(elementId);
327 const obj = value || {};
328 if (Object.keys(obj).length === 0) {
329 el.textContent = '(empty — provider returned no claims for this section)';
330 } else {
331 el.textContent = JSON.stringify(obj, null, 2);
332 }
333 }
335 function renderUserData() {
336 renderParsed(document.getElementById('parsedByProxy'), ssoData.parsed_by_proxy);
337 renderJson('rawClaims', ssoData.raw_claims);
338 renderJson('accessTokenClaims', ssoData.access_token_claims);
339 }
341 function copyToClipboard(elementId) {
342 const text = document.getElementById(elementId).textContent;
343 navigator.clipboard.writeText(text).then(() => {
344 alert('Copied to clipboard!');
345 }).catch(err => {
346 console.error('Could not copy text: ', err);
347 });
348 }
350 // Render the data when the page loads
351 document.addEventListener('DOMContentLoaded', renderUserData);
352 </script>
353</body>
354</html>
355"""