From e5e34152a0db89a4a291c71013f084776c3e8745 Mon Sep 17 00:00:00 2001 From: Gamosoft Date: Fri, 14 Nov 2025 14:34:19 +0100 Subject: [PATCH] added basic authentication --- AUTHENTICATION.md | 311 ++++++++++++++++++++++++++++++++++++++ Dockerfile | 6 +- README.md | 17 ++- backend/main.py | 175 ++++++++++++++++++--- config.yaml | 22 ++- frontend/index.html | 15 +- frontend/login.html | 181 ++++++++++++++++++++++ generate_password.py | 61 ++++++++ generate_password_hash.sh | 59 ++++++++ requirements.txt | 2 + 10 files changed, 818 insertions(+), 31 deletions(-) create mode 100644 AUTHENTICATION.md create mode 100644 frontend/login.html create mode 100644 generate_password.py create mode 100644 generate_password_hash.sh diff --git a/AUTHENTICATION.md b/AUTHENTICATION.md new file mode 100644 index 0000000..0d358cc --- /dev/null +++ b/AUTHENTICATION.md @@ -0,0 +1,311 @@ +# šŸ”’ NoteDiscovery Authentication Guide + +## Overview + +NoteDiscovery includes a simple, secure authentication system for single-user deployments. When enabled, users must log in with a password before accessing the application. + +## ✨ Features + +- āœ… **Single User** - Perfect for personal/self-hosted use +- āœ… **Secure** - Passwords hashed with bcrypt +- āœ… **Session-based** - Stay logged in for 7 days (configurable) +- āœ… **Theme-aware** - Login page matches your selected theme +- āœ… **Simple Setup** - Just 3 steps to enable + +--- + +## šŸš€ Quick Setup + +**āš ļø IMPORTANT:** Authentication is **disabled by default**. Follow these steps to enable it: + +### Step 1: Generate a Password Hash + +Choose one of these methods: + +**Option A: Using Docker (Recommended - No local Python needed)** + +If you're running NoteDiscovery in Docker: + +```bash +# Docker Compose +docker-compose exec notediscovery /app/generate_password_hash.sh + +# Or with docker run +docker exec -it notediscovery /app/generate_password_hash.sh +``` + +The script will prompt you for your password and output the hash. + +**Option B: Using Local Python** + +If you're running NoteDiscovery locally: + +```bash +# Install bcrypt if not already installed +pip install bcrypt + +# Run the password generator +python generate_password.py +``` + +Enter your desired password when prompted. The script will output a bcrypt hash. + +**Copy the hash** - you'll need it for the next step. + +### Step 2: Generate a Secret Key + +Generate a random secret key for session encryption: + +**Using Docker:** +```bash +docker-compose exec notediscovery python -c "import secrets; print(secrets.token_hex(32))" + +# Or with docker run +docker exec -it notediscovery python -c "import secrets; print(secrets.token_hex(32))" +``` + +**Using Local Python:** +```bash +python -c "import secrets; print(secrets.token_hex(32))" +``` + +**Copy the key** - you'll need it for the next step. + +### Step 3: Update `config.yaml` + +Edit your `config.yaml` and update the security section: + +```yaml +security: + # Enable authentication + enabled: true + + # Session secret key (paste the output from Step 3) + secret_key: "your_generated_secret_key_here" + + # Password hash (paste the output from Step 2) + password_hash: "$2b$12$..." + + # Session expiry in seconds (7 days by default) + session_max_age: 604800 +``` + +### Step 4: Restart the Application + +```bash +# If running locally +uvicorn backend.main:app --reload + +# If using Docker Compose +docker-compose restart + +# Or with docker run +docker restart notediscovery +``` + +### Step 5: Test Login + +Navigate to `http://localhost:8000` and you'll be redirected to the login page. + +Enter the password you chose in Step 2. + +--- + +## šŸ” Usage + +### Logging In + +1. Navigate to `http://localhost:8000` +2. You'll be **automatically redirected** to the login page if not authenticated +3. Enter your password (the one you generated in setup) +4. Click "šŸ”“ Unlock" +5. You'll be redirected back to the main app + +**Note:** The login page **automatically inherits the theme** you selected in the main app. It uses the same theme loading system as the main app (fetches CSS from `/api/themes` and stores preference in localStorage as `noteDiscoveryTheme`). + +**What happens if I'm not logged in?** +- 🌐 **Page requests** (like `/`, `/MATHJAX`, etc.) → **Automatically redirected to login page** +- šŸ”Œ **API requests** (like `/api/notes`) → **Return JSON error** `{"detail": "Not authenticated"}` + +This smart routing ensures: +- āœ… Users always see the login page (never a JSON error) +- āœ… API calls get proper JSON errors (for programmatic access) +- āœ… No broken page loads or error messages + +### Logging Out + +**Option 1: Use the built-in logout button** + +A logout button (šŸ”’ Logout) automatically appears in the sidebar header when authentication is enabled. Simply click it to log out. + +**Option 2: Navigate directly** + +Visit `http://localhost:8000/logout` in your browser. + +### Changing Password + +1. Generate a new password hash: + - **Docker**: `docker-compose exec notediscovery /app/generate_password_hash.sh` + - **Local**: `python generate_password.py` +2. Update `password_hash` in `config.yaml` with the new hash +3. Restart the application: + - **Docker**: `docker-compose restart` + - **Local**: Restart uvicorn +4. All existing sessions will remain valid until they expire + +### Session Expiry + +- **Default**: 7 days (604800 seconds) +- **Custom**: Change `session_max_age` in `config.yaml` +- Sessions are cleared when you log out + +--- + +## šŸ”’ Security Considerations + +### āœ… What This Protects + +- Unauthorized access to your notes +- Viewing, creating, editing, and deleting notes +- All API endpoints + +### āš ļø What This Doesn't Protect + +This is a **simple authentication system** designed for **self-hosted, single-user** deployments. It is **NOT** suitable for: + +- āŒ Multi-user environments +- āŒ Public internet exposure without HTTPS +- āŒ Production SaaS applications +- āŒ Compliance requirements (HIPAA, GDPR, etc.) + +### šŸ›”ļø Best Practices + +1. **Use HTTPS** - Always run behind a reverse proxy (nginx, Caddy) with SSL/TLS +2. **Strong Password** - Use at least 12 characters with mixed case, numbers, and symbols +3. **Unique Secret Key** - Never reuse secret keys across applications +4. **Keep Config Secure** - Don't commit `config.yaml` with real credentials to version control +5. **VPN/Private Network** - Keep NoteDiscovery on a private network or behind a VPN + +--- + +## 🚫 Disabling Authentication + +To disable authentication and allow open access: + +```yaml +security: + enabled: false +``` + +Restart the application, and authentication will be bypassed. + +--- + +## šŸ› Troubleshooting + +### "Invalid Password" Error + +- **Check password hash**: Ensure the hash in `config.yaml` matches your password +- **Regenerate hash**: Run `python generate_password.py` again +- **Check encoding**: Password must be UTF-8 encoded + +### "Not authenticated" Error + +- **Check session**: Your session may have expired (default: 7 days) +- **Clear cookies**: Clear your browser cookies and log in again +- **Check config**: Ensure `enabled: true` in `config.yaml` + +### Login Page Not Showing + +- **Check config**: Verify `enabled: true` in `config.yaml` +- **Check routes**: Visit `/login` directly +- **Check logs**: Look for errors in the console + +### Can't Access `/logout` + +- You must be logged in to access `/logout` +- Clear your browser cookies manually if needed + +--- + +## šŸŽØ Customizing Login Page + +The login page (`frontend/login.html`) can be customized: + +- **Theme**: Automatically inherits the theme you selected in the main app (from localStorage) +- **Logo**: Uses `/static/logo.svg` +- **Styling**: Edit the CSS in the ` + + +
+ +

NoteDiscovery

+

Your Self-Hosted Knowledge Base

+ + + +
+ + +
+ + +
+ + + diff --git a/generate_password.py b/generate_password.py new file mode 100644 index 0000000..8941883 --- /dev/null +++ b/generate_password.py @@ -0,0 +1,61 @@ +#!/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(""" +security: + 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}") + diff --git a/generate_password_hash.sh b/generate_password_hash.sh new file mode 100644 index 0000000..c5399a5 --- /dev/null +++ b/generate_password_hash.sh @@ -0,0 +1,59 @@ +#!/bin/bash +# Generate password hash inside Docker container +# Usage: docker exec -it notediscovery /app/generate_password_hash.sh + +echo "==========================================" +echo " NoteDiscovery Password Hash Generator" +echo "==========================================" +echo "" +echo "This will generate a bcrypt hash for your password." +echo "" + +# Prompt for password +read -s -p "Enter your password: " PASSWORD +echo "" +read -s -p "Confirm password: " PASSWORD2 +echo "" + +# Check if passwords match +if [ "$PASSWORD" != "$PASSWORD2" ]; then + echo "" + echo "āŒ Passwords do not match!" + exit 1 +fi + +# Check if password is empty +if [ -z "$PASSWORD" ]; then + echo "" + echo "āŒ Password cannot be empty!" + exit 1 +fi + +echo "" +echo "Generating hash..." +echo "" + +# Generate hash using Python +HASH=$(python3 -c "import bcrypt; print(bcrypt.hashpw(b'$PASSWORD', bcrypt.gensalt()).decode())") + +if [ $? -eq 0 ]; then + echo "==========================================" + echo " āœ… Password hash generated!" + echo "==========================================" + echo "" + echo "Copy this hash to your config.yaml:" + echo "" + echo "password_hash: \"$HASH\"" + echo "" + echo "==========================================" + echo "" + echo "Next steps:" + echo "1. Update config.yaml with the hash above" + echo "2. Set enabled: true in the security section" + echo "3. Restart the container: docker-compose restart" + echo "==========================================" +else + echo "āŒ Failed to generate hash" + exit 1 +fi + diff --git a/requirements.txt b/requirements.txt index 56d3204..6bbe856 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,4 +5,6 @@ markdown==3.5.1 pyyaml==6.0.1 aiofiles==23.2.1 cryptography==41.0.7 +bcrypt==4.1.2 +itsdangerous==2.1.2