Skip to content

feat: implement multi-user auth, usage attribution, UI, and MVP hardening#123

Open
mojomast wants to merge 7 commits intodevfrom
feat/mvp-users-usage-ui
Open

feat: implement multi-user auth, usage attribution, UI, and MVP hardening#123
mojomast wants to merge 7 commits intodevfrom
feat/mvp-users-usage-ui

Conversation

@mojomast
Copy link
Collaborator

@mojomast mojomast commented Feb 14, 2026

Summary

  • add async SQLite persistence and bootstrap flows for users, API keys, and usage events, then layer session auth, API actor auth (AUTH_MODE=users|legacy|both), and role-based admin/user APIs
  • wire /v1/* endpoints to actor-based auth while preserving OpenAI and Anthropic compatibility, and emit usage attribution for non-stream and streaming paths (including fallback request-count events)
  • ship server-rendered user/admin dashboards with CSRF-protected form posts, secure session cookie handling, token redaction/error truncation hardening, and add pytest coverage for auth, API keys, auth-mode matrix, and usage recorder behavior
  • update Docker/dev docs for persisted /app/data SQLite volume and migration guidance from legacy PROXY_API_KEY auth to user API keys

Verification

  • PYTHONPATH=src .venv/bin/python -m compileall src/proxy_app scripts
  • PYTHONPATH=src .venv/bin/pytest -q
  • docker compose -f docker-compose.dev.yml config
  • local MVP demo flow run on 127.0.0.1:8011 (admin/user login, user key creation, auth-mode checks, usage summary endpoints)

Important

Implements multi-user authentication, usage attribution, and UI enhancements with async SQLite persistence, session and API key-based authentication, and role-based access control.

  • Authentication:
    • Implements multi-user authentication with async SQLite persistence in db.py and db_models.py.
    • Adds session and API key-based authentication in api_token_auth.py and auth.py.
    • Supports AUTH_MODE for users, legacy, and both modes.
  • Usage Attribution:
    • Records usage events in usage_recorder.py and usage_queries.py.
    • Handles both non-streaming and streaming requests.
  • UI Enhancements:
    • Adds server-rendered dashboards for users and admins in routers/ui.py and templates/.
    • Includes CSRF protection and secure session handling.
  • Documentation:
    • Updates Docker and development documentation for SQLite persistence and migration from legacy authentication.
  • Testing:
    • Adds tests for authentication, API keys, and usage recording in tests/test_auth.py, tests/test_api_keys.py, and tests/test_usage_attribution.py.

This description was created by Ellipsis for 250cc6c. You can customize this summary. It will automatically update as commits are pushed.

@mojomast mojomast requested a review from Mirrowel as a code owner February 14, 2026 02:11
@mojomast
Copy link
Collaborator Author

Consolidation update: I moved all follow-up hardening work from #124-#127 into this branch so review stays in one place.

Included in this PR now:

  • Security: CORS safe defaults (, ), prod secret validation (, )
  • Token hashing: HMAC-SHA256 with legacy-hash compatibility + opportunistic migration on successful auth
  • SQLite hardening: WAL/synchronous/temp_store/foreign_keys/busy_timeout + engine dispose on shutdown
  • Usage correctness: derived API key from usage events, bounded stream tracking (no unbounded chunk retention)
  • Compat/ops: Anthropic error JSON responses (no FastAPI wrapping), usage retention pruning (, startup prune + admin prune endpoint)
  • Tests/docs updated for all above.

Verification on consolidated branch:

  • ........................ [100%]
    =============================== warnings summary ===============================
    tests/test_api_keys.py: 5 warnings
    tests/test_auth.py: 14 warnings
    tests/test_usage_attribution.py: 2 warnings
    /home/mojo/projects/llmproxussy/LLM-API-Key-Proxy/.venv/lib/python3.13/site-packages/sqlalchemy/sql/schema.py:3624: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
    return util.wrap_callable(lambda ctx: fn(), fn) # type: ignore

tests/test_api_keys.py::test_create_list_revoke_api_key_hides_plaintext_at_rest
/home/mojo/projects/llmproxussy/LLM-API-Key-Proxy/src/proxy_app/routers/user_api.py:187: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
key.revoked_at = datetime.utcnow()

tests/test_auth.py::test_auth_mode_matrix[users-user-token-user_api_key]
tests/test_auth.py::test_auth_mode_matrix[users-legacy-master-None]
tests/test_auth.py::test_auth_mode_matrix[legacy-user-token-None]
tests/test_auth.py::test_auth_mode_matrix[legacy-legacy-master-legacy_master]
tests/test_auth.py::test_auth_mode_matrix[both-user-token-user_api_key]
tests/test_auth.py::test_auth_mode_matrix[both-legacy-master-legacy_master]
/home/mojo/projects/llmproxussy/LLM-API-Key-Proxy/tests/test_auth.py:82: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
expires_at=datetime.utcnow() + timedelta(days=1),

tests/test_auth.py::test_auth_mode_matrix[users-user-token-user_api_key]
tests/test_auth.py::test_auth_mode_matrix[users-legacy-master-None]
tests/test_auth.py::test_auth_mode_matrix[both-user-token-user_api_key]
tests/test_auth.py::test_auth_mode_matrix[both-legacy-master-legacy_master]
tests/test_auth.py::test_legacy_token_hash_opportunistic_migration
/home/mojo/projects/llmproxussy/LLM-API-Key-Proxy/src/proxy_app/api_token_auth.py:95: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
now = datetime.utcnow()

tests/test_auth.py::test_legacy_token_hash_opportunistic_migration
/home/mojo/projects/llmproxussy/LLM-API-Key-Proxy/tests/test_auth.py:144: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
expires_at=datetime.utcnow() + timedelta(days=1),

tests/test_usage_retention.py::test_prune_usage_events_removes_old_rows
/home/mojo/projects/llmproxussy/LLM-API-Key-Proxy/tests/test_usage_retention.py:12: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
now = datetime.utcnow()

tests/test_usage_retention.py::test_prune_usage_events_removes_old_rows
/home/mojo/projects/llmproxussy/LLM-API-Key-Proxy/src/proxy_app/usage_recorder.py:31: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
cutoff = datetime.utcnow() - timedelta(days=active_retention_days)

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
24 passed, 36 warnings in 1.22s -> 24 passed

@mojomast
Copy link
Collaborator Author

Correction (escaped formatting): consolidated hardening from #124-#127 is now included here in #123.

Included in this PR now:

  • Security: CORS safe defaults (CORS_ALLOW_ORIGINS, CORS_ALLOW_CREDENTIALS), prod secret validation (APP_ENV, ALLOW_INSECURE_DEFAULTS)
  • Token hashing: HMAC-SHA256 with legacy-hash compatibility + opportunistic migration on successful auth
  • SQLite hardening: WAL/synchronous/temp_store/foreign_keys/busy_timeout + engine dispose on shutdown
  • Usage correctness: derived API key last_used_at from usage events, bounded stream tracking (no unbounded chunk retention)
  • Compat/ops: Anthropic error JSON responses (no FastAPI detail wrapping), usage retention pruning (USAGE_RETENTION_DAYS, startup prune + admin prune endpoint)
  • Tests/docs updated for all above.

Verification on consolidated branch:

  • PYTHONPATH=src .venv/bin/pytest -q -> 24 passed

@Mirrowel Mirrowel added enhancement New feature or request Agent Monitored Monitored for AI Agent to review PR's and commits labels Feb 14, 2026
Repository owner deleted a comment from mirrobot-agent bot Feb 14, 2026
Repository owner deleted a comment from mirrobot-agent bot Feb 14, 2026
@mirrobot-agent
Copy link
Contributor

I'm starting a comprehensive review of the multi-user auth and usage attribution implementation. This is a significant update with many moving parts (auth modes, SQLite persistence, and a new dashboard), so I'll be taking a close look at the security and reliability aspects. Back soon with the full report!

Copy link
Contributor

@mirrobot-agent mirrobot-agent bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall Assessment

This is a robust implementation of multi-user authentication and usage attribution. The addition of a server-rendered dashboard, session management, and CSRF protection significantly hardens the proxy for multi-user environments. The use of an async batch recorder for usage events is a well-considered performance choice.

Architectural Feedback

  • Auth Modes: The AUTH_MODE transition strategy (users, legacy, both) is excellent for ensuring backward compatibility while migrating to a more secure model.
  • SQLite Hardening: Applying WAL mode and other pragmas shows good attention to SQLite's performance characteristics in an async environment.
  • Security: The proactive validation of secrets in production and the comprehensive redaction logic in the request logger are high-value additions.

Key Suggestions

  • Timestamp Deprecation: Update datetime.utcnow() to datetime.now(datetime.UTC) to align with modern Python standards and resolve the deprecation warnings noted in your verification.
  • Error Handling Refactor: Consider centralizing the repeated litellm exception blocks in main.py to keep the endpoint logic cleaner.
  • Usage Durability: While the async recorder is efficient, adding a retry for flush failures would make usage attribution more reliable under load.

Nitpicks and Minor Points

  • Tying the Secure cookie flag to the is_prod() check would be a safer default than false.
  • A small amount of code duplication exists between the database initialization functions.

Questions for the Author

  • None

This review was generated by an AI assistant.

password_hash: Mapped[str] = mapped_column(Text)
role: Mapped[str] = mapped_column(String(16), default="user")
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

datetime.utcnow() is deprecated since Python 3.12. Consider using datetime.now(datetime.UTC) to ensure timezone-aware objects and avoid future compatibility issues. This pattern appears in several files (e.g., auth_api.py, user_api.py).



def _get_cookie_secure() -> bool:
return os.getenv("SESSION_COOKIE_SECURE", "false").lower() in {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Secure flag for session cookies currently defaults to false. For production deployments (APP_ENV=prod), it is highly recommended to default this to true to ensure session tokens are only transmitted over HTTPS.

legacy_key = (
get_legacy_master_key() if legacy_master_key is None else legacy_master_key
)
if legacy_key and token == legacy_key:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using hmac.compare_digest(token, legacy_key) here to prevent potential timing attacks when validating the legacy master key.

session.add_all(rows)
await session.commit()
except Exception:
logger.exception("Failed to flush %d usage events", len(batch))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If _flush_batch fails (e.g., due to a transient database lock), the current batch of usage events is logged and then discarded. For more reliable usage attribution, you might consider a retry mechanism or a temporary fallback storage.

return session_maker


async def init_db_runtime(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

init_db_runtime shares most of its implementation with init_db. Consider refactoring to reduce duplication, perhaps by having init_db return the engine as well.

usage=None,
error=e,
)
raise HTTPException(status_code=400, detail=f"Invalid Request: {_safe_error_message(e, default='Invalid request')}")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The detailed litellm exception handling logic is repeated across several endpoints (chat_completions, anthropic_messages, embeddings). Refactoring this into a shared utility or a custom exception handler would significantly reduce boilerplate and improve maintainability.

Copy link
Contributor

@mirrobot-agent mirrobot-agent bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up Review

I've reviewed the latest changes, which primarily consist of the addition of the PROJECT_OVERVIEW.md file. This document provides a solid high-level summary of the project's goals, especially regarding multi-user access control and security hardening.

Assessment of New Changes:

  • PROJECT_OVERVIEW.md: The overview is clear and accurately reflects the architectural direction of the proxy. It's a great addition for maintainability and onboarding.

Overall Status:
Since there have been no other changes since my previous review, all prior feedback remains applicable. To ensure the "MVP hardening" goals are fully met, please consider addressing the following unresolved points:

  1. Timestamp Deprecation: Replace datetime.utcnow() with datetime.now(datetime.UTC) across the codebase (db_models.py, user_api.py, ui.py, etc.).
  2. Secure Cookie Defaults: Default the Secure flag for session cookies to true when is_prod() is active.
  3. Timing Attack Protection: Use hmac.compare_digest for legacy key comparisons in api_token_auth.py.
  4. Usage Recorder Reliability: Implement a retry mechanism for _flush_batch failures in usage_recorder.py to prevent data loss during transient database locks.
  5. Refactoring: Reduce code duplication in db.py (initialization logic) and main.py (exception handling).

I'm happy to re-review once these hardening points are addressed!

This review was generated by an AI assistant.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Agent Monitored Monitored for AI Agent to review PR's and commits enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants