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

1from typing import Final 

2 

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 } 

23 

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 } 

32 

33 .logo-container { 

34 text-align: center; 

35 margin-bottom: 30px; 

36 } 

37 

38 .logo { 

39 font-size: 24px; 

40 font-weight: 600; 

41 color: #1e293b; 

42 } 

43 

44 h2 { 

45 margin: 0 0 10px; 

46 color: #1e293b; 

47 font-size: 28px; 

48 font-weight: 600; 

49 text-align: center; 

50 } 

51 

52 h3 { 

53 margin: 0 0 12px; 

54 color: #1e293b; 

55 font-size: 18px; 

56 font-weight: 600; 

57 } 

58 

59 .subtitle { 

60 color: #64748b; 

61 margin: 0 0 20px; 

62 font-size: 16px; 

63 text-align: center; 

64 } 

65 

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 } 

73 

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 } 

81 

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 } 

90 

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 } 

99 

100 .info-header svg, .success-header svg { 

101 margin-right: 8px; 

102 } 

103 

104 .data-container { 

105 margin-top: 20px; 

106 } 

107 

108 .data-row { 

109 display: flex; 

110 border-bottom: 1px solid #e2e8f0; 

111 padding: 12px 0; 

112 } 

113 

114 .data-row:last-child { 

115 border-bottom: none; 

116 } 

117 

118 .data-label { 

119 font-weight: 500; 

120 color: #334155; 

121 width: 220px; 

122 flex-shrink: 0; 

123 } 

124 

125 .data-value { 

126 color: #475569; 

127 word-break: break-all; 

128 } 

129 

130 .empty-note { 

131 color: #64748b; 

132 font-style: italic; 

133 margin: 0; 

134 font-size: 14px; 

135 } 

136 

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 } 

145 

146 .jwt-text { 

147 font-family: monospace; 

148 white-space: pre-wrap; 

149 word-break: break-all; 

150 margin: 0; 

151 color: #334155; 

152 } 

153 

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 } 

165 

166 .back-button:hover { 

167 background-color: #4138C2; 

168 text-decoration: none; 

169 } 

170 

171 .buttons { 

172 display: flex; 

173 gap: 10px; 

174 margin-top: 12px; 

175 } 

176 

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 } 

188 

189 .copy-button:hover { 

190 background-color: #cbd5e1; 

191 } 

192 

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> 

207 

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> 

218 

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> 

233 

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> 

257 

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> 

281 

282 <a href="/sso/debug/login" class="back-button"> 

283 Try Another SSO Login 

284 </a> 

285 </div> 

286 

287 <script> 

288 // This will be populated with the actual data from the server 

289 const ssoData = SSO_DATA; 

290 

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'; 

304 

305 const label = document.createElement('div'); 

306 label.className = 'data-label'; 

307 label.textContent = key; 

308 

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 } 

318 

319 row.appendChild(label); 

320 row.appendChild(dataValue); 

321 container.appendChild(row); 

322 } 

323 } 

324 

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 } 

334 

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 } 

340 

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 } 

349 

350 // Render the data when the page loads 

351 document.addEventListener('DOMContentLoaded', renderUserData); 

352 </script> 

353</body> 

354</html> 

355"""