Add local admin login alongside Entra ID authentication
This commit is contained in:
1 parent
ac4f2b0d32
commit
9bda3ab14b
3 files changed
+97
-3
No files matched your search
@@ -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.
|
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
|
## Prerequisites
|
||||||
|
|
||||||
- An Azure subscription
|
- An Azure subscription
|
||||||
|
|||||||
@@ -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.
|
**Detailed setup instructions**: See [ENTRA_SETUP.md](ENTRA_SETUP.md) for comprehensive step-by-step guide to configure Microsoft Entra ID authentication.
|
||||||
|
|
||||||
### Quick Setup:
|
### Quick Setup:
|
||||||
1. Register application in Azure Portal
|
1. Register application in Azure Portal (optional for admin login)
|
||||||
2. Get Tenant ID, Client ID, and Client Secret
|
2. Get Tenant ID, Client ID, and Client Secret (for Entra login)
|
||||||
3. Set environment variables:
|
3. Set environment variables:
|
||||||
```bash
|
```bash
|
||||||
|
# For Entra ID login (optional)
|
||||||
export TENANT_ID="your-tenant-id"
|
export TENANT_ID="your-tenant-id"
|
||||||
export CLIENT_ID="your-client-id"
|
export CLIENT_ID="your-client-id"
|
||||||
export CLIENT_SECRET="your-client-secret"
|
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"
|
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
|
## Installation
|
||||||
|
|
||||||
1. **Clone the repository**:
|
1. **Clone the repository**:
|
||||||
|
|||||||
+77
-1
@@ -17,6 +17,10 @@ TENANT_ID = os.getenv('TENANT_ID', 'your-tenant-id')
|
|||||||
CLIENT_ID = os.getenv('CLIENT_ID', 'your-client-id')
|
CLIENT_ID = os.getenv('CLIENT_ID', 'your-client-id')
|
||||||
CLIENT_SECRET = os.getenv('CLIENT_SECRET', 'your-client-secret')
|
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)
|
oauth = OAuth(app)
|
||||||
microsoft = oauth.register(
|
microsoft = oauth.register(
|
||||||
name='microsoft',
|
name='microsoft',
|
||||||
@@ -31,7 +35,7 @@ ALLOWED_EXTENSIONS = {'pdf', 'zip'}
|
|||||||
def login_required(f):
|
def login_required(f):
|
||||||
@wraps(f)
|
@wraps(f)
|
||||||
def decorated_function(*args, **kwargs):
|
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 redirect(url_for('login'))
|
||||||
return f(*args, **kwargs)
|
return f(*args, **kwargs)
|
||||||
return decorated_function
|
return decorated_function
|
||||||
@@ -54,9 +58,80 @@ def extract_names_from_pdf(pdf_path):
|
|||||||
|
|
||||||
@app.route('/login')
|
@app.route('/login')
|
||||||
def 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)
|
redirect_uri = url_for('auth', _external=True)
|
||||||
return microsoft.authorize_redirect(redirect_uri)
|
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')
|
@app.route('/auth')
|
||||||
def auth():
|
def auth():
|
||||||
token = microsoft.authorize_access_token()
|
token = microsoft.authorize_access_token()
|
||||||
@@ -68,6 +143,7 @@ def auth():
|
|||||||
@login_required
|
@login_required
|
||||||
def logout():
|
def logout():
|
||||||
session.pop('user', None)
|
session.pop('user', None)
|
||||||
|
session.pop('admin', None)
|
||||||
return redirect(url_for('login'))
|
return redirect(url_for('login'))
|
||||||
|
|
||||||
@app.route('/', methods=['GET', 'POST'])
|
@app.route('/', methods=['GET', 'POST'])
|
||||||
|
|||||||
Reference in new issue
Block a user