Skip to content

Feat/webserver auth#40

Merged
cchwala merged 7 commits intomainfrom
feat/webserver-auth
Apr 30, 2026
Merged

Feat/webserver auth#40
cchwala merged 7 commits intomainfrom
feat/webserver-auth

Conversation

@cchwala
Copy link
Copy Markdown
Member

@cchwala cchwala commented Apr 29, 2026

feat/webserver-auth — Flask-Login, per-request SET LOCAL ROLE, secure views

What this PR does

Adds authentication to the webserver and enforces per-user data isolation at the database level for every request.

Changes

Authentication (Flask-Login)

  • All routes protected with @login_required; unauthenticated requests redirect to /login
  • Session-based login/logout at /login and /logout
  • Users loaded from webserver/configs/users.json (scrypt-hashed passwords via werkzeug)
  • Open-redirect protection on the next parameter

Per-request DB role scoping

  • New user_db_scope(user_id) context manager: connects as webserver_role, issues SET LOCAL ROLE <user_id> using psycopg2.sql.Identifier (not string interpolation), yields connection, commits/rolls back, closes
  • SET LOCAL is automatically reverted at transaction end — role bleed between requests is impossible
  • All data routes switched to user_db_scope and the security-barrier views (cml_data_secure, cml_data_1h_secure) instead of raw tables

Configuration

  • docker-compose.yml: webserver now connects as webserver_role, SECRET_KEY and USERS_CONFIG_PATH added as env vars, configs/ volume mounted read-only
  • webserver/requirements.txt: Flask-Login==0.6.3 added

Tests (main.py coverage 39% → 49%)

  • test_auth.py: protected route redirects, login (valid/invalid/unknown), open-redirect blocked, logout clears session
  • test_user_db_scope.py: unknown user rejected before any DB call, rollback on exception, SET LOCAL ROLE uses pgsql.Composable
  • test_helpers.py: safe_float edge cases, load_user known/unknown
  • test_api_routes.py: /api/cml-metadata, /api/cml-map, /api/data-time-range (both branches)
  • Updated test_api_cml_stats.py for new auth layer

Docs

  • webserver/README.md: user management, password hash generation, adding users
  • docs/multi-user-architecture.md: PR8 (feat/grafana-auth-proxy) planned and documented

Known gap

Grafana currently connects as myuser (superuser) and bypasses RLS — it sees all tenants' data. The /grafana/ proxy is gated by @login_required so unauthenticated access is blocked, but data isolation inside Grafana is not yet enforced. This is addressed in the next PR (feat/grafana-auth-proxy).

Security notes

  • Role name passed to SET LOCAL ROLE via pgsql.Identifier, never string-formatted — SQL injection not possible
  • user_id is allowlisted against USERS before any SQL is composed
  • Passwords stored as scrypt hashes; never logged or returned in responses
  • SECRET_KEY defaults to os.urandom(32) in dev; production should set it via env

cchwala added 3 commits April 30, 2026 00:07
… views

## What this PR does

Adds login/logout to the webserver (PR5 in the multi-user roadmap).
All routes now require authentication. DB queries are scoped to the
logged-in user via SET LOCAL ROLE, which PostgreSQL reverts automatically
at transaction end — preventing role bleed on reused connections.

### Changes

#### webserver/main.py
- Flask-Login: LoginManager, User(UserMixin), load_user, /login, /logout
- All existing routes gain @login_required
- New user_db_scope(user_id) context manager:
  connects as webserver_role, issues SET LOCAL ROLE <user_id> via
  pgsql.Identifier (injection-safe), auto-reverts at transaction end
- All DB queries switched from raw cml_data/cml_data_1h to
  cml_data_secure/cml_data_1h_secure; cml_metadata/cml_stats scoped
  through the user's role (RLS enforced automatically)
- get_db_connection() kept as admin path (webserver_role, no SET ROLE)
- get_db_connection() kept as admin path (webserver_role, no SET ROLE)
t

r/configs/users.json
- New file with scrypt-hashed passwords fo- New file with scr  d- New file with scrypt-hashed passwords fo- New file with scr  d- ve- New file with scrypt-hashed passwords fo- New file with scr  d- on- New file with scrypt-hashed passwords fo- New file with scr  d- New fiap- New file with scrypt-hashed passwords fo- New file with scr  d- webserver DATABASE_URL switched to webserver_role:webserverpassword
- SECRET_KEY and USERS_CONFIG_PATH env vars - SECRE ./webserver/configs mounted read-on- SECRET_KEY and USERS_CONFIG_PATH env vars ill connects as myuser (superuser) and sees all tenants' data.
Will be addressed in PR8 (feat/grafana-auth-proxy) using Grafana's
auth proxy mode with per-user datasources connecting as the matching
PG role.
- Update status table: PRs 3-5 marked merged, PR8 added
- Add PR8 section: Grafana auth proxy + per-user PG datasources
  - X-WEBAUTH-USER header injected by Flask proxy
  - Per-user datasources connect as matching PG role (RLS enforced)
  - Known gap section documents current state until PR8 is merged
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 29, 2026

Codecov Report

❌ Patch coverage is 74.38017% with 31 lines in your changes missing coverage. Please review.
✅ Project coverage is 81.18%. Comparing base (b269a79) to head (37e1f05).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
webserver/main.py 74.38% 31 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #40      +/-   ##
==========================================
+ Coverage   78.77%   81.18%   +2.41%     
==========================================
  Files          24       24              
  Lines        2327     2371      +44     
==========================================
+ Hits         1833     1925      +92     
+ Misses        494      446      -48     
Flag Coverage Δ
mno_simulator 86.72% <ø> (ø)
parser 85.48% <ø> (ø)
webserver 63.21% <74.38%> (+14.32%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

cchwala added 4 commits April 30, 2026 00:13
- Mock user_db_scope context manager instead of get_db_connection
- Patch current_user.id to avoid AnonymousUserMixin error
- Set LOGIN_DISABLED to bypass @login_required decorator
- test_auth.py: login/logout flow, protected route redirects,
  open-redirect prevention, invalid credentials
- test_user_db_scope.py: unknown user rejected before DB connect,
  rollback on exception, SET LOCAL ROLE uses pgsql.Identifier
… logout

- test_helpers.py: safe_float (None/valid/NaN/inf/bad-string), load_user (known/unknown)
- test_api_routes.py: /api/cml-metadata, /api/cml-map, /api/data-time-range (both branches)
- test_auth.py: logout-when-authenticated verifies session is cleared

Coverage: main.py 39% → 49%
@cchwala cchwala merged commit b60f4a4 into main Apr 30, 2026
6 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant