Skip to content

Commit 16699d2

Browse files
Fixed: Blank page issue after authentication for some users
Improved handling of custom URL parameters by storing them as instance variables Simplified _setup_routes() method for better maintainability Applied fix from PR #2 for more robust URL parameter handling Fixed: Route removal logic now correctly removes all default documentation routes Properly removes /docs, /redoc, and /openapi.json endpoints Prevents 500 errors when accessing old endpoints Improved: Example files and documentation Fixed custom_styling.py to work with uvicorn by adding default app variable Standardized credentials across all custom styling examples Added python-multipart to dev dependencies for form data handling Added clear run instructions in example files
1 parent b77e71f commit 16699d2

File tree

8 files changed

+142
-49
lines changed

8 files changed

+142
-49
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,6 @@ dmypy.json
7171
# OS specific files
7272
.DS_Store
7373
**/.claude/settings.local.json
74+
75+
# Claude Code memory file
76+
CLAUDE.md

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,20 @@ pytest --cov=fastapi_docshield
223223

224224
## Changelog
225225

226+
### Version 0.2.1 (2025-08-17)
227+
- **Fixed**: Blank page issue after authentication for some users
228+
- Improved handling of custom URL parameters by storing them as instance variables
229+
- Simplified `_setup_routes()` method for better maintainability
230+
- Applied fix from PR #2 for more robust URL parameter handling
231+
- **Fixed**: Route removal logic now correctly removes all default documentation routes
232+
- Properly removes `/docs`, `/redoc`, and `/openapi.json` endpoints
233+
- Prevents 500 errors when accessing old endpoints
234+
- **Improved**: Example files and documentation
235+
- Fixed `custom_styling.py` to work with uvicorn by adding default app variable
236+
- Standardized credentials across all custom styling examples
237+
- Added `python-multipart` to dev dependencies for form data handling
238+
- Added clear run instructions in example files
239+
226240
### Version 0.2.0 (2025-08-17)
227241
- **Added**: Custom CSS and JavaScript injection support
228242
- New `custom_css` parameter to inject custom styles into documentation pages

examples/all_features.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""
2-
Comprehensive example showcasing all DocShield v0.2.0 features.
2+
Comprehensive example showcasing all DocShield v0.2.1 features.
33
44
This example demonstrates:
55
- Multiple user credentials
@@ -22,7 +22,7 @@
2222
# Create FastAPI app with comprehensive API
2323
app = FastAPI(
2424
title="DocShield Feature Showcase",
25-
description="Demonstrating all features of FastAPI DocShield v0.2.0",
25+
description="Demonstrating all features of FastAPI DocShield v0.2.1",
2626
version="2.0.0"
2727
)
2828

@@ -349,7 +349,7 @@ def get_stats():
349349
import uvicorn
350350

351351
print("=" * 80)
352-
print("🛡️ DocShield v0.2.0 - All Features Showcase")
352+
print("🛡️ DocShield v0.2.1 - All Features Showcase")
353353
print("=" * 80)
354354
print()
355355
print("📍 Server: http://localhost:8000")

examples/custom_styling.py

Lines changed: 90 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,21 @@
44
This module demonstrates various ways to customize the appearance
55
and behavior of your protected documentation pages.
66
7+
How to run:
8+
-----------
9+
1. With uvicorn (default dark theme):
10+
uvicorn custom_styling:app --reload
11+
12+
2. Run specific themes directly:
13+
python custom_styling.py dark # Dark theme with gradient
14+
python custom_styling.py minimal # Clean minimal theme
15+
python custom_styling.py corporate # Corporate theme with analytics
16+
python custom_styling.py redoc # ReDoc customization
17+
18+
Credentials for all examples:
19+
Username: demo
20+
Password: style123
21+
722
Author: George Khananaev
823
"""
924

@@ -85,15 +100,15 @@ def root():
85100

86101
DocShield(
87102
app=app,
88-
credentials={"admin": "darkmode123"},
103+
credentials={"demo": "style123"},
89104
custom_css=custom_css,
90105
custom_js=custom_js
91106
)
92107

93108
print("=" * 70)
94109
print("Dark Theme Example")
95110
print("Access at: http://localhost:8000/docs")
96-
print("Credentials: admin/darkmode123")
111+
print("Credentials: demo/style123")
97112
print("=" * 70)
98113

99114
uvicorn.run(app, host="0.0.0.0", port=8000)
@@ -205,15 +220,15 @@ def get_users():
205220

206221
DocShield(
207222
app=app,
208-
credentials={"viewer": "minimal123"},
223+
credentials={"demo": "style123"},
209224
custom_css=custom_css,
210225
custom_js=custom_js
211226
)
212227

213228
print("=" * 70)
214229
print("Minimal Theme Example")
215230
print("Access at: http://localhost:8001/docs")
216-
print("Credentials: viewer/minimal123")
231+
print("Credentials: demo/style123")
217232
print("=" * 70)
218233

219234
uvicorn.run(app, host="0.0.0.0", port=8001)
@@ -363,19 +378,15 @@ def get_analytics():
363378

364379
DocShield(
365380
app=app,
366-
credentials={
367-
"admin": "corp@2025",
368-
"developer": "dev@2025",
369-
"auditor": "audit@2025"
370-
},
381+
credentials={"demo": "style123"},
371382
custom_css=custom_css,
372383
custom_js=custom_js
373384
)
374385

375386
print("=" * 70)
376387
print("Corporate Theme Example")
377388
print("Access at: http://localhost:8002/docs")
378-
print("Credentials: admin/corp@2025, developer/dev@2025, or auditor/audit@2025")
389+
print("Credentials: demo/style123")
379390
print("Features: Usage analytics, corporate branding, compliance notices")
380391
print("=" * 70)
381392

@@ -483,7 +494,7 @@ def get_item(item_id: int):
483494

484495
DocShield(
485496
app=app,
486-
credentials={"user": "redoc123"},
497+
credentials={"demo": "style123"},
487498
custom_css=custom_css,
488499
custom_js=custom_js,
489500
prefer_local=True # Use local files for faster loading
@@ -492,13 +503,80 @@ def get_item(item_id: int):
492503
print("=" * 70)
493504
print("ReDoc Custom Example")
494505
print("Access at: http://localhost:8003/redoc")
495-
print("Credentials: user/redoc123")
506+
print("Credentials: demo/style123")
496507
print("Features: Reading progress, copy buttons, custom styling")
497508
print("=" * 70)
498509

499510
uvicorn.run(app, host="0.0.0.0", port=8003)
500511

501512

513+
# Create a default app for uvicorn
514+
# This will be used when running: uvicorn custom_styling:app
515+
app = FastAPI(
516+
title="Custom Styling Examples",
517+
description="Examples of DocShield with custom CSS and JavaScript",
518+
version="1.0.0"
519+
)
520+
521+
@app.get("/")
522+
def root():
523+
return {
524+
"message": "DocShield Custom Styling Examples",
525+
"examples": [
526+
"python custom_styling.py dark",
527+
"python custom_styling.py minimal",
528+
"python custom_styling.py corporate",
529+
"python custom_styling.py redoc"
530+
]
531+
}
532+
533+
@app.get("/examples")
534+
def list_examples():
535+
return {
536+
"dark": "Dark theme with gradient backgrounds",
537+
"minimal": "Clean minimal theme",
538+
"corporate": "Corporate theme with analytics",
539+
"redoc": "ReDoc customization example"
540+
}
541+
542+
# Apply dark theme by default to the app
543+
custom_css = """
544+
/* Dark theme customization */
545+
.swagger-ui {
546+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
547+
}
548+
549+
.swagger-ui .topbar {
550+
background-color: #1a1a2e;
551+
border-bottom: 2px solid #764ba2;
552+
}
553+
554+
.swagger-ui .btn.authorize {
555+
background-color: #764ba2;
556+
border-color: #764ba2;
557+
}
558+
"""
559+
560+
custom_js = """
561+
console.log('🎨 Custom Styling Examples - Default Dark Theme');
562+
document.addEventListener('DOMContentLoaded', function() {
563+
const info = document.querySelector('.info');
564+
if (info) {
565+
const note = document.createElement('div');
566+
note.style.cssText = 'background:#764ba2;color:white;padding:15px;margin-bottom:20px;border-radius:5px;';
567+
note.innerHTML = '📝 Note: Run with different themes using: python custom_styling.py [dark|minimal|corporate|redoc]';
568+
info.insertBefore(note, info.firstChild);
569+
}
570+
});
571+
"""
572+
573+
DocShield(
574+
app=app,
575+
credentials={"demo": "style123"},
576+
custom_css=custom_css,
577+
custom_js=custom_js
578+
)
579+
502580
if __name__ == "__main__":
503581
import sys
504582

fastapi_docshield/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99
Copyright (c) 2025 George Khananaev
1010
"""
1111

12-
from .docshield import DocShield
12+
from .docshield import DocShield, __version__
1313

14-
__version__ = "0.2.0"
14+
__version__ = __version__
1515
__author__ = "George Khananaev"
1616
__license__ = "MIT"
1717
__copyright__ = "Copyright (c) 2025 George Khananaev"

fastapi_docshield/docshield.py

Lines changed: 27 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@
66
Copyright (c) 2025 George Khananaev
77
"""
88

9-
from typing import Dict, Optional, Tuple
10-
from fastapi import FastAPI, HTTPException, status, Request, Depends
11-
from fastapi.security.utils import get_authorization_scheme_param
9+
__version__ = "0.2.1"
10+
11+
from typing import Dict, Optional
12+
from fastapi import FastAPI, HTTPException, status, Depends
1213
from fastapi.openapi.docs import get_swagger_ui_html, get_redoc_html
1314
from fastapi.security import HTTPBasic, HTTPBasicCredentials
1415
from fastapi.responses import HTMLResponse
1516
import secrets
16-
import base64
1717
from .static_handler import StaticHandler
1818

1919

@@ -67,6 +67,9 @@ def __init__(
6767
self.docs_url = docs_url
6868
self.redoc_url = redoc_url
6969
self.openapi_url = openapi_url
70+
self.swagger_js_url = swagger_js_url
71+
self.swagger_css_url = swagger_css_url
72+
self.redoc_js_url = redoc_js_url
7073
self.use_cdn_fallback = use_cdn_fallback
7174
self.prefer_local = prefer_local
7275
self.custom_css = custom_css
@@ -89,11 +92,7 @@ def __init__(
8992
app.openapi_url = None
9093

9194
# Set up protected documentation routes
92-
self._setup_routes(
93-
swagger_js_url=swagger_js_url,
94-
swagger_css_url=swagger_css_url,
95-
redoc_js_url=redoc_js_url
96-
)
95+
self._setup_routes()
9796

9897
def _remove_existing_docs_routes(self) -> None:
9998
"""
@@ -108,12 +107,18 @@ def _remove_existing_docs_routes(self) -> None:
108107
for route in self.app.routes:
109108
path = getattr(route, "path", "")
110109

111-
# Skip documentation routes
112-
if (path == self.docs_url or
110+
# Skip both original and new documentation routes
111+
if (path == self.original_docs_url or
112+
path == self.original_redoc_url or
113+
path == self.original_openapi_url or
114+
path == self.docs_url or
113115
path == self.redoc_url or
114116
path == self.openapi_url or
115-
path.startswith(f"{self.docs_url}/") or # Docs static files
116-
path == "/openapi.json"):
117+
path == "/docs" or # Default docs
118+
path == "/redoc" or # Default redoc
119+
path == "/openapi.json" or # Default openapi
120+
path.startswith("/docs/") or # Docs static files
121+
path.startswith(f"{self.docs_url}/")): # New docs static files
117122
continue
118123

119124
routes_to_keep.append(route)
@@ -154,19 +159,11 @@ def _verify_credentials(self, credentials: HTTPBasicCredentials) -> str:
154159
headers={"WWW-Authenticate": "Basic"},
155160
)
156161

157-
def _setup_routes(
158-
self,
159-
swagger_js_url: Optional[str],
160-
swagger_css_url: Optional[str],
161-
redoc_js_url: Optional[str],
162-
) -> None:
162+
def _setup_routes(self) -> None:
163163
"""
164164
Set up all protected documentation endpoints.
165165
166-
Args:
167-
swagger_js_url: Custom Swagger UI JavaScript URL
168-
swagger_css_url: Custom Swagger UI CSS URL
169-
redoc_js_url: Custom ReDoc JavaScript URL
166+
Uses the stored swagger_js_url, swagger_css_url, and redoc_js_url.
170167
"""
171168
# Set up OpenAPI JSON endpoint
172169
@self.app.get(self.openapi_url, include_in_schema=False)
@@ -186,16 +183,16 @@ async def get_docs(credentials: HTTPBasicCredentials = Depends(self.security)):
186183
self._verify_credentials(credentials)
187184

188185
# Determine which URLs to use
189-
if swagger_js_url is not None or swagger_css_url is not None:
186+
if self.swagger_js_url is not None or self.swagger_css_url is not None:
190187
# User provided custom URLs, use them
191188
kwargs = {
192189
"openapi_url": self.openapi_url,
193190
"title": self.app.title + " - Swagger UI",
194191
}
195-
if swagger_js_url is not None:
196-
kwargs["swagger_js_url"] = swagger_js_url
197-
if swagger_css_url is not None:
198-
kwargs["swagger_css_url"] = swagger_css_url
192+
if self.swagger_js_url is not None:
193+
kwargs["swagger_js_url"] = self.swagger_js_url
194+
if self.swagger_css_url is not None:
195+
kwargs["swagger_css_url"] = self.swagger_css_url
199196
elif self.static_handler and self.prefer_local:
200197
# Prefer local files
201198
js_url, css_url = self.static_handler.get_swagger_urls(prefer_local=True)
@@ -229,12 +226,12 @@ async def get_redoc(credentials: HTTPBasicCredentials = Depends(self.security)):
229226
self._verify_credentials(credentials)
230227

231228
# Determine which URL to use
232-
if redoc_js_url is not None:
229+
if self.redoc_js_url is not None:
233230
# User provided custom URL, use it
234231
kwargs = {
235232
"openapi_url": self.openapi_url,
236233
"title": self.app.title + " - ReDoc",
237-
"redoc_js_url": redoc_js_url,
234+
"redoc_js_url": self.redoc_js_url,
238235
}
239236
elif self.static_handler and self.prefer_local:
240237
# Prefer local files

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "fastapi-docshield"
7-
version = "0.2.0"
7+
version = "0.2.1"
88
description = "A simple FastAPI integration to protect documentation endpoints with authentication"
99
readme = "README.md"
1010
requires-python = ">=3.7"
@@ -42,6 +42,7 @@ dev = [
4242
"httpx>=0.23.0",
4343
"uvicorn>=0.18.0",
4444
"requests>=2.28.0",
45+
"python-multipart>=0.0.5",
4546
]
4647

4748
[project.urls]

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)