added api-key/removed hashed password support
This commit is contained in:
parent
a175246a1e
commit
bcdbb6b065
|
|
@ -63,7 +63,6 @@ COPY VERSION .
|
||||||
COPY plugins ./plugins
|
COPY plugins ./plugins
|
||||||
COPY themes ./themes
|
COPY themes ./themes
|
||||||
COPY locales ./locales
|
COPY locales ./locales
|
||||||
COPY generate_password.py .
|
|
||||||
|
|
||||||
# Create data directory
|
# Create data directory
|
||||||
RUN mkdir -p data
|
RUN mkdir -p data
|
||||||
|
|
|
||||||
|
|
@ -267,7 +267,6 @@ NoteDiscovery is designed for **self-hosted, private use**. Please keep these se
|
||||||
- ⚠️ **ENABLE AUTHENTICATION AND CHANGE THE DEFAULT PASSWORD** if exposing to a network!
|
- ⚠️ **ENABLE AUTHENTICATION AND CHANGE THE DEFAULT PASSWORD** if exposing to a network!
|
||||||
- See **[AUTHENTICATION.md](documentation/AUTHENTICATION.md)** for complete setup instructions
|
- See **[AUTHENTICATION.md](documentation/AUTHENTICATION.md)** for complete setup instructions
|
||||||
- To disable auth, set `authentication.enabled: false` in `config.yaml`
|
- To disable auth, set `authentication.enabled: false` in `config.yaml`
|
||||||
- Change password with Docker: `docker-compose exec notediscovery python generate_password.py`
|
|
||||||
- Perfect for single-user or small team deployments
|
- Perfect for single-user or small team deployments
|
||||||
- For multi-user setups, consider a reverse proxy with OAuth/SSO
|
- For multi-user setups, consider a reverse proxy with OAuth/SSO
|
||||||
|
|
||||||
|
|
|
||||||
137
backend/main.py
137
backend/main.py
|
|
@ -7,6 +7,7 @@ from fastapi import FastAPI, HTTPException, UploadFile, File, Request, Form, Dep
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
from fastapi.responses import HTMLResponse, JSONResponse, FileResponse, RedirectResponse
|
from fastapi.responses import HTMLResponse, JSONResponse, FileResponse, RedirectResponse
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials, APIKeyHeader
|
||||||
from starlette.middleware.sessions import SessionMiddleware
|
from starlette.middleware.sessions import SessionMiddleware
|
||||||
import os
|
import os
|
||||||
import yaml
|
import yaml
|
||||||
|
|
@ -16,6 +17,7 @@ from typing import List, Optional
|
||||||
import aiofiles
|
import aiofiles
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import bcrypt
|
import bcrypt
|
||||||
|
import secrets
|
||||||
from slowapi import Limiter, _rate_limit_exceeded_handler
|
from slowapi import Limiter, _rate_limit_exceeded_handler
|
||||||
from slowapi.util import get_remote_address
|
from slowapi.util import get_remote_address
|
||||||
from slowapi.errors import RateLimitExceeded
|
from slowapi.errors import RateLimitExceeded
|
||||||
|
|
@ -78,39 +80,65 @@ else:
|
||||||
print(f"🔐 Authentication {'ENABLED' if config.get('authentication', {}).get('enabled', False) else 'DISABLED'} (from config.yaml)")
|
print(f"🔐 Authentication {'ENABLED' if config.get('authentication', {}).get('enabled', False) else 'DISABLED'} (from config.yaml)")
|
||||||
|
|
||||||
# Password configuration priority:
|
# Password configuration priority:
|
||||||
# 1. AUTHENTICATION_PASSWORD env var (plain text, hashed at startup)
|
# 1. AUTHENTICATION_PASSWORD env var (hashed at startup)
|
||||||
# 2. AUTHENTICATION_PASSWORD_HASH env var (pre-hashed)
|
# 2. authentication.password in config.yaml (hashed at startup)
|
||||||
# 3. authentication.password in config.yaml (plain text, hashed at startup)
|
|
||||||
# 4. authentication.password_hash in config.yaml (pre-hashed)
|
|
||||||
# Default password is "admin" if nothing is configured
|
# Default password is "admin" if nothing is configured
|
||||||
if 'AUTHENTICATION_PASSWORD' in os.environ:
|
if 'AUTHENTICATION_PASSWORD' in os.environ:
|
||||||
# Plain text password from env var - hash it
|
plain_password = os.getenv('AUTHENTICATION_PASSWORD', '').strip()
|
||||||
plain_password = os.getenv('AUTHENTICATION_PASSWORD')
|
if plain_password:
|
||||||
config['authentication']['password_hash'] = bcrypt.hashpw(
|
config['authentication']['password_hash'] = bcrypt.hashpw(
|
||||||
plain_password.encode('utf-8'),
|
plain_password.encode('utf-8'),
|
||||||
bcrypt.gensalt()
|
bcrypt.gensalt()
|
||||||
).decode('utf-8')
|
).decode('utf-8')
|
||||||
print("🔑 Password loaded from AUTHENTICATION_PASSWORD env var (hashed at startup)")
|
print("🔑 Password loaded from AUTHENTICATION_PASSWORD env var")
|
||||||
elif 'AUTHENTICATION_PASSWORD_HASH' in os.environ:
|
else:
|
||||||
# Pre-hashed password from env var
|
print("⚠️ WARNING: AUTHENTICATION_PASSWORD env var is empty - ignoring")
|
||||||
config['authentication']['password_hash'] = os.getenv('AUTHENTICATION_PASSWORD_HASH')
|
elif config.get('authentication', {}).get('password', '').strip():
|
||||||
print("🔑 Password hash loaded from AUTHENTICATION_PASSWORD_HASH env var")
|
plain_password = config['authentication']['password'].strip()
|
||||||
elif config.get('authentication', {}).get('password'):
|
|
||||||
# Plain text password from config.yaml - hash it
|
|
||||||
plain_password = config['authentication']['password']
|
|
||||||
config['authentication']['password_hash'] = bcrypt.hashpw(
|
config['authentication']['password_hash'] = bcrypt.hashpw(
|
||||||
plain_password.encode('utf-8'),
|
plain_password.encode('utf-8'),
|
||||||
bcrypt.gensalt()
|
bcrypt.gensalt()
|
||||||
).decode('utf-8')
|
).decode('utf-8')
|
||||||
# Clear the plain text password from config for security
|
|
||||||
del config['authentication']['password']
|
del config['authentication']['password']
|
||||||
print("🔑 Password loaded from config.yaml (hashed at startup)")
|
print("🔑 Password loaded from config.yaml")
|
||||||
|
|
||||||
# Allow secret key to be set via environment variable (for session security)
|
# Allow secret key to be set via environment variable (for session security)
|
||||||
if 'AUTHENTICATION_SECRET_KEY' in os.environ:
|
if 'AUTHENTICATION_SECRET_KEY' in os.environ:
|
||||||
config['authentication']['secret_key'] = os.getenv('AUTHENTICATION_SECRET_KEY')
|
config['authentication']['secret_key'] = os.getenv('AUTHENTICATION_SECRET_KEY')
|
||||||
print("🔐 Secret key loaded from AUTHENTICATION_SECRET_KEY env var")
|
print("🔐 Secret key loaded from AUTHENTICATION_SECRET_KEY env var")
|
||||||
|
|
||||||
|
# API key configuration for external integrations (MCP servers, scripts, etc.)
|
||||||
|
# Priority: AUTHENTICATION_API_KEY env var > authentication.api_key in config.yaml
|
||||||
|
if 'AUTHENTICATION_API_KEY' in os.environ:
|
||||||
|
api_key_value = os.getenv('AUTHENTICATION_API_KEY', '').strip()
|
||||||
|
if api_key_value:
|
||||||
|
config['authentication']['api_key'] = api_key_value
|
||||||
|
print("🔑 API key loaded from AUTHENTICATION_API_KEY env var")
|
||||||
|
else:
|
||||||
|
config['authentication']['api_key'] = ''
|
||||||
|
elif config.get('authentication', {}).get('api_key', '').strip():
|
||||||
|
print("🔑 API key loaded from config.yaml")
|
||||||
|
else:
|
||||||
|
config['authentication']['api_key'] = ''
|
||||||
|
|
||||||
|
# Warnings for missing authentication methods (only when auth is enabled)
|
||||||
|
if config.get('authentication', {}).get('enabled', False):
|
||||||
|
_has_password = bool(config.get('authentication', {}).get('password_hash', ''))
|
||||||
|
_has_api_key = bool(config.get('authentication', {}).get('api_key', '').strip())
|
||||||
|
_secret_key = config.get('authentication', {}).get('secret_key', '')
|
||||||
|
_is_default_secret = _secret_key in ('', 'change_this_to_a_random_secret_key_in_production')
|
||||||
|
|
||||||
|
if not _has_password and not _has_api_key:
|
||||||
|
print("🚨 CRITICAL: Authentication enabled but NO auth methods configured - ALL access will be denied!")
|
||||||
|
else:
|
||||||
|
if not _has_password:
|
||||||
|
print("⚠️ WARNING: No password configured - web UI login will not work")
|
||||||
|
if not _has_api_key:
|
||||||
|
print("⚠️ WARNING: No API key configured - external integrations will require session cookies")
|
||||||
|
|
||||||
|
if _is_default_secret:
|
||||||
|
print("🚨 SECURITY WARNING: Using default secret_key - sessions can be forged! Change it in config.yaml")
|
||||||
|
|
||||||
# OpenAPI tag metadata for grouping endpoints in Swagger UI
|
# OpenAPI tag metadata for grouping endpoints in Swagger UI
|
||||||
tags_metadata = [
|
tags_metadata = [
|
||||||
{"name": "Notes", "description": "Create, read, update, delete notes"},
|
{"name": "Notes", "description": "Create, read, update, delete notes"},
|
||||||
|
|
@ -274,18 +302,87 @@ async def http_exception_handler(request: Request, exc: HTTPException):
|
||||||
# Authentication Helpers
|
# Authentication Helpers
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
|
# Security schemes for API authentication (auto_error=False for optional auth)
|
||||||
|
# These are automatically added to OpenAPI docs (/api)
|
||||||
|
bearer_scheme = HTTPBearer(auto_error=False, description="Bearer token authentication")
|
||||||
|
api_key_header = APIKeyHeader(name="X-API-Key", auto_error=False, description="API key header authentication")
|
||||||
|
|
||||||
|
|
||||||
def auth_enabled() -> bool:
|
def auth_enabled() -> bool:
|
||||||
"""Check if authentication is enabled in config"""
|
"""Check if authentication is enabled in config"""
|
||||||
return config.get('authentication', {}).get('enabled', False)
|
return config.get('authentication', {}).get('enabled', False)
|
||||||
|
|
||||||
|
|
||||||
async def require_auth(request: Request):
|
def get_api_key() -> str:
|
||||||
"""Dependency to require authentication on protected routes"""
|
"""Get the configured API key (empty string if not set)"""
|
||||||
|
return config.get('authentication', {}).get('api_key', '').strip()
|
||||||
|
|
||||||
|
|
||||||
|
def verify_api_key(provided_key: str) -> bool:
|
||||||
|
"""
|
||||||
|
Verify an API key using constant-time comparison.
|
||||||
|
|
||||||
|
Uses secrets.compare_digest to prevent timing attacks where an attacker
|
||||||
|
could determine the correct key by measuring response times.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
provided_key: The API key provided in the request
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if the key is valid, False otherwise
|
||||||
|
"""
|
||||||
|
configured_key = get_api_key()
|
||||||
|
|
||||||
|
# No API key configured = API key auth disabled
|
||||||
|
if not configured_key:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Empty provided key is always invalid
|
||||||
|
if not provided_key:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Constant-time comparison to prevent timing attacks
|
||||||
|
try:
|
||||||
|
return secrets.compare_digest(provided_key.encode('utf-8'), configured_key.encode('utf-8'))
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
async def require_auth(
|
||||||
|
request: Request,
|
||||||
|
bearer_credentials: Optional[HTTPAuthorizationCredentials] = Depends(bearer_scheme),
|
||||||
|
x_api_key: Optional[str] = Depends(api_key_header)
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Dependency to require authentication on protected routes.
|
||||||
|
|
||||||
|
Supports two authentication methods:
|
||||||
|
1. Session-based auth (web UI login with password)
|
||||||
|
2. API key auth (for external integrations like MCP servers)
|
||||||
|
|
||||||
|
API key can be provided via:
|
||||||
|
- Authorization: Bearer YOUR_API_KEY
|
||||||
|
- X-API-Key: YOUR_API_KEY
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
HTTPException: 401 if authentication fails
|
||||||
|
"""
|
||||||
if not auth_enabled():
|
if not auth_enabled():
|
||||||
return # Auth disabled, allow all
|
return # Auth disabled, allow all
|
||||||
|
|
||||||
if not request.session.get('authenticated'):
|
# Method 1: Check Bearer token (parsed by FastAPI's HTTPBearer)
|
||||||
# Always raise exception - route handlers will catch and redirect as needed
|
if bearer_credentials and verify_api_key(bearer_credentials.credentials):
|
||||||
|
return # Valid Bearer token - authenticated
|
||||||
|
|
||||||
|
# Method 2: Check X-API-Key header (parsed by FastAPI's APIKeyHeader)
|
||||||
|
if x_api_key and verify_api_key(x_api_key):
|
||||||
|
return # Valid API key header - authenticated
|
||||||
|
|
||||||
|
# Method 3: Check session-based authentication (web UI)
|
||||||
|
if request.session.get('authenticated'):
|
||||||
|
return # Valid session - authenticated
|
||||||
|
|
||||||
|
# No valid authentication method - deny access
|
||||||
raise HTTPException(status_code=401, detail="Not authenticated")
|
raise HTTPException(status_code=401, detail="Not authenticated")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
20
config.yaml
20
config.yaml
|
|
@ -35,17 +35,21 @@ authentication:
|
||||||
# Generate with: python -c "import secrets; print(secrets.token_hex(32))"
|
# Generate with: python -c "import secrets; print(secrets.token_hex(32))"
|
||||||
secret_key: "change_this_to_a_random_secret_key_in_production"
|
secret_key: "change_this_to_a_random_secret_key_in_production"
|
||||||
|
|
||||||
# Password configuration - choose ONE of the following options:
|
# Password (hashed automatically at startup)
|
||||||
#
|
|
||||||
# Option 1: Plain text password (recommended for ease of use)
|
|
||||||
# The password is hashed automatically at startup
|
|
||||||
# ⚠️ Default password is "admin" - CHANGE THIS for production!
|
# ⚠️ Default password is "admin" - CHANGE THIS for production!
|
||||||
password: "admin"
|
password: "admin"
|
||||||
|
|
||||||
# Option 2: Pre-hashed password (for advanced users)
|
|
||||||
# Generate with: python generate_password.py
|
|
||||||
# password_hash: "$2b$12$..."
|
|
||||||
|
|
||||||
# Session expiry in seconds (default: 7 days)
|
# Session expiry in seconds (default: 7 days)
|
||||||
session_max_age: 604800
|
session_max_age: 604800
|
||||||
|
|
||||||
|
# API Key Authentication (for external integrations)
|
||||||
|
# Usage (choose one):
|
||||||
|
# Authorization: Bearer YOUR_API_KEY
|
||||||
|
# X-API-Key: YOUR_API_KEY
|
||||||
|
#
|
||||||
|
# Generate a secure key with:
|
||||||
|
# python -c "import secrets; print(secrets.token_hex(32))"
|
||||||
|
#
|
||||||
|
# Leave empty to disable API key authentication (only session auth works)
|
||||||
|
api_key: ""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -50,11 +50,7 @@ python -c "import secrets; print(secrets.token_hex(32))"
|
||||||
|
|
||||||
### Step 2: Configure Authentication
|
### Step 2: Configure Authentication
|
||||||
|
|
||||||
Choose **one** of these options:
|
Your password is automatically hashed at startup using bcrypt.
|
||||||
|
|
||||||
#### Option A: Plain Text Password (Recommended)
|
|
||||||
|
|
||||||
The easiest approach. Your password is automatically hashed at startup.
|
|
||||||
|
|
||||||
**Via Environment Variables (Docker):**
|
**Via Environment Variables (Docker):**
|
||||||
```bash
|
```bash
|
||||||
|
|
@ -75,29 +71,6 @@ authentication:
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
#### Option B: Pre-Hashed Password (Advanced)
|
|
||||||
|
|
||||||
For users who prefer to hash passwords themselves.
|
|
||||||
|
|
||||||
**Generate a hash:**
|
|
||||||
```bash
|
|
||||||
# Docker
|
|
||||||
docker exec -it notediscovery python generate_password.py
|
|
||||||
|
|
||||||
# Local
|
|
||||||
python generate_password.py
|
|
||||||
```
|
|
||||||
|
|
||||||
**Then configure:**
|
|
||||||
```yaml
|
|
||||||
authentication:
|
|
||||||
enabled: true
|
|
||||||
password_hash: "$2b$12$..." # paste your hash here
|
|
||||||
secret_key: "your_generated_secret_key"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Step 3: Restart & Test
|
### Step 3: Restart & Test
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
@ -117,16 +90,14 @@ Navigate to `http://localhost:8000` — you'll be redirected to the login page.
|
||||||
|
|
||||||
## Configuration Priority
|
## Configuration Priority
|
||||||
|
|
||||||
If multiple sources are configured, this priority applies (first wins):
|
Environment variables override config.yaml:
|
||||||
|
|
||||||
| Priority | Source | Type |
|
| Priority | Source |
|
||||||
|----------|--------|------|
|
|----------|--------|
|
||||||
| 1st | `AUTHENTICATION_PASSWORD` env var | Plain text |
|
| 1st | `AUTHENTICATION_PASSWORD` env var |
|
||||||
| 2nd | `AUTHENTICATION_PASSWORD_HASH` env var | Pre-hashed |
|
| 2nd | `password` in config.yaml |
|
||||||
| 3rd | `password` in config.yaml | Plain text |
|
|
||||||
| 4th | `password_hash` in config.yaml | Pre-hashed |
|
|
||||||
|
|
||||||
**Example:** If you set `AUTHENTICATION_PASSWORD` as an env var, it overrides anything in config.yaml.
|
**Example:** If you set `AUTHENTICATION_PASSWORD` as an env var, it overrides config.yaml.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -155,6 +126,42 @@ This is a **simple single-user** system. NOT suitable for:
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## API Key Authentication
|
||||||
|
|
||||||
|
For external integrations (MCP servers, scripts, automation), use an API key instead of session cookies.
|
||||||
|
|
||||||
|
### Setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Generate a secure key
|
||||||
|
python -c "import secrets; print(secrets.token_hex(32))"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Via Environment Variable:**
|
||||||
|
```bash
|
||||||
|
docker run -e AUTHENTICATION_API_KEY=your_api_key ...
|
||||||
|
```
|
||||||
|
|
||||||
|
**Via config.yaml:**
|
||||||
|
```yaml
|
||||||
|
authentication:
|
||||||
|
api_key: "your_64_character_hex_key"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Option 1: Bearer token
|
||||||
|
curl -H "Authorization: Bearer YOUR_API_KEY" http://localhost:8000/api/notes
|
||||||
|
|
||||||
|
# Option 2: X-API-Key header
|
||||||
|
curl -H "X-API-Key: YOUR_API_KEY" http://localhost:8000/api/notes
|
||||||
|
```
|
||||||
|
|
||||||
|
Both session auth (web UI) and API key auth work simultaneously when enabled.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Disabling Authentication
|
## Disabling Authentication
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
|
|
||||||
|
|
@ -17,11 +17,9 @@ NoteDiscovery supports environment variables to override configuration settings,
|
||||||
| Variable | Type | Default | Description |
|
| Variable | Type | Default | Description |
|
||||||
|----------|------|---------|-------------|
|
|----------|------|---------|-------------|
|
||||||
| `AUTHENTICATION_ENABLED` | boolean | `config.yaml` | Enable/disable authentication |
|
| `AUTHENTICATION_ENABLED` | boolean | `config.yaml` | Enable/disable authentication |
|
||||||
| `AUTHENTICATION_PASSWORD` | string | `admin` | Plain text password (hashed automatically at startup) |
|
| `AUTHENTICATION_PASSWORD` | string | `admin` | Password (hashed automatically at startup) |
|
||||||
| `AUTHENTICATION_PASSWORD_HASH` | string | - | Pre-hashed bcrypt password (for advanced users) |
|
|
||||||
| `AUTHENTICATION_SECRET_KEY` | string | `config.yaml` | Session secret key (for session security) |
|
| `AUTHENTICATION_SECRET_KEY` | string | `config.yaml` | Session secret key (for session security) |
|
||||||
|
| `AUTHENTICATION_API_KEY` | string | - | API key for external integrations (MCP, scripts) |
|
||||||
> **Password Priority:** `AUTHENTICATION_PASSWORD` takes precedence over `AUTHENTICATION_PASSWORD_HASH`. If both are set, the plain text password is used.
|
|
||||||
|
|
||||||
#### Example: Setting password via environment variable
|
#### Example: Setting password via environment variable
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -347,6 +347,15 @@ NoteDiscovery can be installed as a standalone app on your device:
|
||||||
- **Instant switch** - Change language in Settings without reload
|
- **Instant switch** - Change language in Settings without reload
|
||||||
- **Community translations** - Contributions welcome!
|
- **Community translations** - Contributions welcome!
|
||||||
|
|
||||||
|
## 🔐 Authentication
|
||||||
|
|
||||||
|
- **Password protection** - Single-user login system
|
||||||
|
- **Session-based auth** - Secure cookie-based sessions (7 days default)
|
||||||
|
- **API key support** - Bearer token or `X-API-Key` header for external integrations
|
||||||
|
- **Environment overrides** - Configure via env vars for Docker deployments
|
||||||
|
|
||||||
|
📄 **See [AUTHENTICATION.md](AUTHENTICATION.md)** for setup guide.
|
||||||
|
|
||||||
## 🚀 Performance
|
## 🚀 Performance
|
||||||
|
|
||||||
- **Instant loading** - No lag, no loading spinners
|
- **Instant loading** - No lag, no loading spinners
|
||||||
|
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Helper script to generate a password hash for NoteDiscovery authentication.
|
|
||||||
Usage: python generate_password.py
|
|
||||||
"""
|
|
||||||
|
|
||||||
import getpass
|
|
||||||
import bcrypt
|
|
||||||
|
|
||||||
def generate_password_hash():
|
|
||||||
"""Generate a bcrypt password hash"""
|
|
||||||
print("=== NoteDiscovery Password Hash Generator ===\n")
|
|
||||||
|
|
||||||
# Get password from user (hidden input)
|
|
||||||
password = getpass.getpass("Enter your password: ")
|
|
||||||
password_confirm = getpass.getpass("Confirm your password: ")
|
|
||||||
|
|
||||||
# Check if passwords match
|
|
||||||
if password != password_confirm:
|
|
||||||
print("\n❌ Error: Passwords do not match!")
|
|
||||||
return
|
|
||||||
|
|
||||||
if len(password) < 4:
|
|
||||||
print("\n⚠️ Warning: Password is too short! Use at least 8 characters for better security.")
|
|
||||||
proceed = input("Continue anyway? (y/N): ")
|
|
||||||
if proceed.lower() != 'y':
|
|
||||||
return
|
|
||||||
|
|
||||||
# Generate hash
|
|
||||||
print("\n🔐 Generating password hash...")
|
|
||||||
password_hash = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8')
|
|
||||||
|
|
||||||
print("\n✅ Password hash generated successfully!")
|
|
||||||
print("\n" + "="*60)
|
|
||||||
print("Copy this hash to your config.yaml:")
|
|
||||||
print("="*60)
|
|
||||||
print(f"\npassword_hash: \"{password_hash}\"")
|
|
||||||
print("\n" + "="*60)
|
|
||||||
print("\nExample config.yaml:")
|
|
||||||
print("="*60)
|
|
||||||
print("""
|
|
||||||
authentication:
|
|
||||||
enabled: true
|
|
||||||
secret_key: "your_secret_key_here"
|
|
||||||
password_hash: "{}"
|
|
||||||
session_max_age: 604800
|
|
||||||
""".format(password_hash))
|
|
||||||
print("="*60)
|
|
||||||
|
|
||||||
print("\n💡 Don't forget to also set a secret key!")
|
|
||||||
print(" Generate one with: python -c \"import secrets; print(secrets.token_hex(32))\"")
|
|
||||||
print("\n🔒 Keep your password and secret key secure!")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
try:
|
|
||||||
generate_password_hash()
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
print("\n\n❌ Cancelled.")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"\n❌ Error: {e}")
|
|
||||||
|
|
||||||
|
|
@ -27,9 +27,8 @@ services:
|
||||||
|
|
||||||
# ⚠️ DEMO CREDENTIALS - DO NOT USE IN PRODUCTION! ⚠️
|
# ⚠️ DEMO CREDENTIALS - DO NOT USE IN PRODUCTION! ⚠️
|
||||||
# These are public demo credentials for testing only.
|
# These are public demo credentials for testing only.
|
||||||
# Password: "admin"
|
- key: AUTHENTICATION_PASSWORD
|
||||||
- key: AUTHENTICATION_PASSWORD_HASH
|
value: "admin"
|
||||||
value: "$2b$12$t/6PGExFzdpU2PUta0iVY.eDQwvu63kH.c/d4bEnnHaQ5CspH1yrG"
|
|
||||||
|
|
||||||
# ⚠️ PUBLIC SECRET KEY - DEMO ONLY! ⚠️
|
# ⚠️ PUBLIC SECRET KEY - DEMO ONLY! ⚠️
|
||||||
# For production, generate a new key with:
|
# For production, generate a new key with:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue