Add local admin login alongside Entra ID authentication

This commit is contained in:
nmemmert committed 2025-11-06 09:51:15 -05:00
1 parent ac4f2b0d32
commit 9bda3ab14b
3 files changed
+97 -3

No files matched your search

+2
View File
@@ -2,6 +2,8 @@
This guide provides step-by-step instructions for setting up Microsoft Entra ID (formerly Azure Active Directory) authentication for the PDF Renamer web application.
**Note**: Entra ID authentication is optional. The application also supports local admin login. Follow this guide only if you want to enable Microsoft Entra ID authentication.
## Prerequisites
- An Azure subscription
+18 -2
View File
@@ -23,16 +23,32 @@ A Python web application built with Flask that automatically renames PDF files b
**Detailed setup instructions**: See [ENTRA_SETUP.md](ENTRA_SETUP.md) for comprehensive step-by-step guide to configure Microsoft Entra ID authentication.
### Quick Setup:
1. Register application in Azure Portal
2. Get Tenant ID, Client ID, and Client Secret
1. Register application in Azure Portal (optional for admin login)
2. Get Tenant ID, Client ID, and Client Secret (for Entra login)
3. Set environment variables:
```bash
# For Entra ID login (optional)
export TENANT_ID="your-tenant-id"
export CLIENT_ID="your-client-id"
export CLIENT_SECRET="your-client-secret"
# For admin login
export ADMIN_USERNAME="admin"
export ADMIN_PASSWORD="your-secure-password"
# Required
export SECRET_KEY="your-random-secret-key"
```
## Authentication
The application supports two authentication methods:
1. **Microsoft Entra ID**: OAuth2 authentication with Azure AD
2. **Admin Login**: Local username/password authentication
Users can choose their preferred login method on the login page.
## Installation
1. **Clone the repository**:
+77 -1
View File
@@ -17,6 +17,10 @@ TENANT_ID = os.getenv('TENANT_ID', 'your-tenant-id')
CLIENT_ID = os.getenv('CLIENT_ID', 'your-client-id')
CLIENT_SECRET = os.getenv('CLIENT_SECRET', 'your-client-secret')
# Admin Configuration
ADMIN_USERNAME = os.getenv('ADMIN_USERNAME', 'admin')
ADMIN_PASSWORD = os.getenv('ADMIN_PASSWORD', 'change-this-password')
oauth = OAuth(app)
microsoft = oauth.register(
name='microsoft',
@@ -31,7 +35,7 @@ ALLOWED_EXTENSIONS = {'pdf', 'zip'}
def login_required(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if 'user' not in session:
if 'user' not in session and 'admin' not in session:
return redirect(url_for('login'))
return f(*args, **kwargs)
return decorated_function
@@ -54,9 +58,80 @@ def extract_names_from_pdf(pdf_path):
@app.route('/login')
def login():
return render_template_string('''
<!DOCTYPE html>
<html>
<head>
<title>Login</title>
<style>
body { font-family: Arial, sans-serif; background-color: #f4f4f4; margin: 0; padding: 20px; text-align: center; }
.login-container { background-color: #fff; padding: 30px; border-radius: 10px; box-shadow: 0 0 10px rgba(0,0,0,0.1); display: inline-block; margin: 20px; }
h1 { color: #333; }
.login-options { display: flex; justify-content: space-around; flex-wrap: wrap; }
.login-option { margin: 10px; }
a { color: #007bff; text-decoration: none; padding: 15px 30px; background-color: #007bff; color: white; border-radius: 5px; display: inline-block; }
a:hover { background-color: #0056b3; }
form { margin-top: 20px; }
input[type="text"], input[type="password"] { padding: 10px; margin: 5px; width: 200px; border: 1px solid #ccc; border-radius: 5px; }
input[type="submit"] { background-color: #28a745; color: white; border: none; padding: 10px 20px; border-radius: 5px; cursor: pointer; }
input[type="submit"]:hover { background-color: #218838; }
</style>
</head>
<body>
<img src="/static/trbc-logo-full.png" alt="Logo" width="200">
<h1>PDF Renamer Login</h1>
<div class="login-options">
<div class="login-option">
<h2>Microsoft Entra ID</h2>
<a href="/entra-login">Login with Microsoft</a>
</div>
<div class="login-option">
<h2>Admin Login</h2>
<form method="post" action="/admin-login">
<input type="text" name="username" placeholder="Username" required><br>
<input type="password" name="password" placeholder="Password" required><br>
<input type="submit" value="Admin Login">
</form>
</div>
</div>
</body>
</html>
''')
@app.route('/entra-login')
def entra_login():
redirect_uri = url_for('auth', _external=True)
return microsoft.authorize_redirect(redirect_uri)
@app.route('/admin-login', methods=['POST'])
def admin_login():
username = request.form.get('username')
password = request.form.get('password')
if username == ADMIN_USERNAME and password == ADMIN_PASSWORD:
session['admin'] = True
return redirect(url_for('upload_files'))
else:
return render_template_string('''
<!DOCTYPE html>
<html>
<head>
<title>Login Failed</title>
<style>
body { font-family: Arial, sans-serif; background-color: #f4f4f4; text-align: center; padding: 50px; }
.error { color: #dc3545; background-color: #f8d7da; padding: 20px; border-radius: 5px; border: 1px solid #f5c6cb; }
a { color: #007bff; text-decoration: none; }
</style>
</head>
<body>
<div class="error">
<h1>Login Failed</h1>
<p>Invalid username or password.</p>
<a href="/login">Try Again</a>
</div>
</body>
</html>
''')
@app.route('/auth')
def auth():
token = microsoft.authorize_access_token()
@@ -68,6 +143,7 @@ def auth():
@login_required
def logout():
session.pop('user', None)
session.pop('admin', None)
return redirect(url_for('login'))
@app.route('/', methods=['GET', 'POST'])