mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-08-09 10:39:11 +02:00
Merge b5c66df3da into 42da399b4d
This commit is contained in:
commit
fa638b561d
6 changed files with 27 additions and 11 deletions
|
|
@ -48,7 +48,7 @@ ADMIN_PRIVILEGES["allowed_models_restricted"] = False
|
|||
# backwards for this sentinel.
|
||||
ADMIN_PRIVILEGES["block_all_models"] = False
|
||||
|
||||
from src.constants import AUTH_FILE, PASSWORD_MIN_LENGTH
|
||||
from src.constants import AUTH_FILE, PASSWORD_MIN_LENGTH, PASSWORD_MAX_LENGTH
|
||||
DEFAULT_AUTH_PATH = AUTH_FILE
|
||||
TOKEN_TTL = 60 * 60 * 24 * 7 # 7 days
|
||||
|
||||
|
|
@ -250,6 +250,7 @@ class AuthManager:
|
|||
"""Return public auth policy constants for the frontend."""
|
||||
return {
|
||||
"password_min_length": PASSWORD_MIN_LENGTH,
|
||||
"password_max_length": PASSWORD_MAX_LENGTH,
|
||||
"reserved_usernames": sorted(RESERVED_USERNAMES),
|
||||
"signup_enabled": self.signup_enabled,
|
||||
"session_days": TOKEN_TTL // 86400,
|
||||
|
|
|
|||
|
|
@ -453,6 +453,7 @@ Odysseus is a self-hosted workspace with powerful local tools: shell access, fil
|
|||
- Do not expose it directly to the public internet without HTTPS and a trusted reverse proxy or private access layer.
|
||||
- Keep `.env`, `data/`, `logs/`, databases, uploads, generated media, backups, auth/session files, API keys, and model/provider tokens out of Git and private shares. They are ignored by default.
|
||||
- Review `data/auth.json` after first boot: disable open signup unless you intentionally want it, make only your own account admin, and keep demo/test accounts non-admin.
|
||||
- Passwords must be at least 8 characters long and no more than 72 bytes (bcrypt's hard limit). Multi-byte characters count toward that byte limit.
|
||||
- Non-admin users do not get shell/Python/file read/write by default, and admin-only routes/tools such as MCP management, API tokens, webhooks, model/cookbook serving, backup/vault, and app settings are admin-gated. Other features are controlled by per-user privileges, so review each user's privileges before exposing a deployment.
|
||||
- Rotate any API keys or tokens that were ever pasted into a shared chat, demo, screenshot, or log.
|
||||
- If you enable API tokens or webhooks, create separate tokens per integration and delete unused ones.
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@ from pathlib import Path
|
|||
|
||||
from core.atomic_io import atomic_write_json, atomic_write_text
|
||||
from core.auth import AuthManager, RESERVED_USERNAMES, SetAdminResult, TOKEN_TTL
|
||||
from src.constants import DEEP_RESEARCH_DIR, MEMORY_FILE, PASSWORD_MIN_LENGTH, SKILLS_DIR
|
||||
from src.auth_helpers import validate_password_length
|
||||
from src.constants import DEEP_RESEARCH_DIR, MEMORY_FILE, SKILLS_DIR
|
||||
from src.rate_limiter import RateLimiter
|
||||
from src.settings_scrub import scrub_settings
|
||||
from src.settings import (
|
||||
|
|
@ -102,8 +103,7 @@ def setup_auth_routes(auth_manager: AuthManager) -> APIRouter:
|
|||
raise HTTPException(429, "Too many requests — try again later")
|
||||
if auth_manager.is_configured:
|
||||
raise HTTPException(400, "Already configured")
|
||||
if len(body.password) < PASSWORD_MIN_LENGTH:
|
||||
raise HTTPException(400, f"Password must be at least {PASSWORD_MIN_LENGTH} characters")
|
||||
validate_password_length(body.password)
|
||||
if len(body.username.strip()) < 1:
|
||||
raise HTTPException(400, "Username is required")
|
||||
if body.username.lower() in RESERVED_USERNAMES:
|
||||
|
|
@ -122,8 +122,7 @@ def setup_auth_routes(auth_manager: AuthManager) -> APIRouter:
|
|||
raise HTTPException(400, "Run setup first")
|
||||
if not auth_manager.signup_enabled:
|
||||
raise HTTPException(403, "Registration is disabled. Ask an admin for an account.")
|
||||
if len(body.password) < PASSWORD_MIN_LENGTH:
|
||||
raise HTTPException(400, f"Password must be at least {PASSWORD_MIN_LENGTH} characters")
|
||||
validate_password_length(body.password)
|
||||
if len(body.username.strip()) < 1:
|
||||
raise HTTPException(400, "Username is required")
|
||||
if body.username.lower() in RESERVED_USERNAMES:
|
||||
|
|
@ -200,8 +199,7 @@ def setup_auth_routes(auth_manager: AuthManager) -> APIRouter:
|
|||
user = _get_current_user(request)
|
||||
if not user:
|
||||
raise HTTPException(401, "Not authenticated")
|
||||
if len(body.new_password) < PASSWORD_MIN_LENGTH:
|
||||
raise HTTPException(400, f"Password must be at least {PASSWORD_MIN_LENGTH} characters")
|
||||
validate_password_length(body.new_password)
|
||||
current_token = request.cookies.get(SESSION_COOKIE)
|
||||
ok = await asyncio.to_thread(auth_manager.change_password, user, body.current_password, body.new_password)
|
||||
if not ok:
|
||||
|
|
@ -281,8 +279,7 @@ def setup_auth_routes(auth_manager: AuthManager) -> APIRouter:
|
|||
user = _get_current_user(request)
|
||||
if not user or not auth_manager.is_admin(user):
|
||||
raise HTTPException(403, "Admin only")
|
||||
if len(body.password) < PASSWORD_MIN_LENGTH:
|
||||
raise HTTPException(400, f"Password must be at least {PASSWORD_MIN_LENGTH} characters")
|
||||
validate_password_length(body.password)
|
||||
if len(body.username.strip()) < 1:
|
||||
raise HTTPException(400, "Username is required")
|
||||
if body.username.lower() in RESERVED_USERNAMES:
|
||||
|
|
|
|||
8
setup.py
8
setup.py
|
|
@ -16,7 +16,7 @@ sys.path.insert(0, BASE_DIR)
|
|||
from src.constants import (
|
||||
DATA_DIR, AUTH_FILE, UPLOAD_DIR, PERSONAL_DIR, PERSONAL_UPLOADS_DIR,
|
||||
TTS_CACHE_DIR, GENERATED_IMAGES_DIR, DEEP_RESEARCH_DIR, CHROMA_DIR,
|
||||
RAG_DIR, MEMORY_VECTORS_DIR, PASSWORD_MIN_LENGTH,
|
||||
RAG_DIR, MEMORY_VECTORS_DIR, PASSWORD_MIN_LENGTH, PASSWORD_MAX_LENGTH,
|
||||
)
|
||||
from core.auth import RESERVED_USERNAMES
|
||||
|
||||
|
|
@ -77,6 +77,9 @@ def _prompt_admin_credentials():
|
|||
if len(password) < PASSWORD_MIN_LENGTH:
|
||||
print(f" Password must be at least {PASSWORD_MIN_LENGTH} characters.")
|
||||
continue
|
||||
if len(password.encode("utf-8")) > PASSWORD_MAX_LENGTH:
|
||||
print(f" Password must be {PASSWORD_MAX_LENGTH} bytes or fewer.")
|
||||
continue
|
||||
confirm = getpass.getpass(" Confirm password: ")
|
||||
if password != confirm:
|
||||
print(" Passwords don't match. Try again.")
|
||||
|
|
@ -109,6 +112,9 @@ def create_default_admin():
|
|||
if len(password) < PASSWORD_MIN_LENGTH:
|
||||
print(f" [error] ODYSSEUS_ADMIN_PASSWORD must be at least {PASSWORD_MIN_LENGTH} characters")
|
||||
return "failed"
|
||||
if len(password.encode("utf-8")) > PASSWORD_MAX_LENGTH:
|
||||
print(f" [error] ODYSSEUS_ADMIN_PASSWORD must be {PASSWORD_MAX_LENGTH} bytes or fewer")
|
||||
return "failed"
|
||||
elif sys.stdin.isatty() and not os.getenv("ODYSSEUS_SKIP_ADMIN_PROMPT"):
|
||||
# Interactive terminal — ask the user
|
||||
username, password = _prompt_admin_credentials()
|
||||
|
|
|
|||
|
|
@ -4,6 +4,16 @@ import os
|
|||
from typing import Optional
|
||||
from fastapi import Request, HTTPException
|
||||
|
||||
from src.constants import PASSWORD_MAX_LENGTH, PASSWORD_MIN_LENGTH
|
||||
|
||||
|
||||
def validate_password_length(password: str) -> None:
|
||||
"""Raise HTTPException(400, ...) if password violates min/max length policy."""
|
||||
if len(password) < PASSWORD_MIN_LENGTH:
|
||||
raise HTTPException(400, f"Password must be at least {PASSWORD_MIN_LENGTH} characters")
|
||||
if len(password.encode("utf-8")) > PASSWORD_MAX_LENGTH:
|
||||
raise HTTPException(400, f"Password must be {PASSWORD_MAX_LENGTH} bytes or fewer")
|
||||
|
||||
|
||||
def get_current_user(request: Request) -> Optional[str]:
|
||||
"""Get current username from request state (set by auth middleware)."""
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@ CLEANUP_INTERVAL_HOURS = int(os.getenv("CLEANUP_INTERVAL_HOURS", "24"))
|
|||
|
||||
# Auth policy
|
||||
PASSWORD_MIN_LENGTH = 8
|
||||
PASSWORD_MAX_LENGTH = 72
|
||||
|
||||
# Default parameters
|
||||
DEFAULT_TEMPERATURE = 1.0
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue