Skip to content

Local auth short-circuit makes sign-in, sign-out and multi-user paths untestable #208

Description

@sspickle

Summary

Local runs cannot exercise authentication at all, and that removes a large slice of the app from testing. ide/auth.py short-circuits identity whenever the server runs locally:

def is_logged_in():
    return ('user' in session) or (routes.is_running_locally())

def get_user_info():
    if routes.is_running_locally():
        return {'email': 'localuser@local.host'}
    return session.get('user') or {}

Running locally you are always signed in as localuser@local.host. You cannot be anonymous, and you cannot be a second user.

What that costs, concretely

  • Anonymous state does not exist locally. The sign-in link, the not_logged_in API state, and any "must be signed in" guard cannot be tested. Three e2e tests had to be skipped for precisely this (test(e2e): skip anonymous-state tests when the server runs locally #207) — they now skip locally and only run against a deployed server.
  • Only one identity exists. Anything involving two users — sharing a program, another user's folder, permission boundaries — is untestable locally.
  • Local diverges from production at the auth layer, which is the layer where divergence is most expensive to discover late.

The three skipped tests are just the visible symptom; the untested surface is much larger.

The fix is configuration, not a special case

Auth is raw Google OIDC through Authlib, driven by discovery:

CONF_URL = 'https://accounts.google.com/.well-known/openid-configuration'
oauth.register(..., server_metadata_url=CONF_URL, client_kwargs={'scope': 'openid email profile'})

Because it is standard OIDC discovery, a local mock OIDC provider (mock-oauth2-server, Dex, oauth2-mock-server — all run happily in Docker beside the datastore emulator) can serve that role. Point CONF_URL plus client id/secret at it for local runs and the real OAuth code path executes locally, with real sessions, sign-out, and as many identities as the tests need.

At that point the special case can simply be deleted: is_logged_in() and get_user_info() become session-only, with no environment branch at all.

⚠️ Worth stating because it is an easy wrong turn: the Firebase Auth emulator is not the right tool here. It emulates Firebase Auth, not generic OIDC. CONF_URL is currently a module constant, so the first concrete step is making it (and the client id/secret) configurable.

The same hack is in webvpython

webvpython/flaskHost/src/auth.py carries it at three sites:

line what
70 is_logged_in() returns true whenever local
73-74 get_user_info() returns localuser@local.host
88 login() sets session['user'] = {'email':'local@user'} and redirects

Line 88 is notable: it is already the right shape — a local login that populates the session — but the other two override it unconditionally, so it never takes effect. Whatever lands here should land there.

Done when

  • CONF_URL / client id / secret configurable per environment
  • a mock OIDC provider in the local compose setup
  • the local-mode branches removed from is_logged_in() and get_user_info()
  • the three tests skipped in test(e2e): skip anonymous-state tests when the server runs locally #207 run and pass locally
  • same treatment applied to webvpython/flaskHost/src/auth.py

Design notes on branch feat/local-auth-emulator (docs/LOCAL-AUTH-DESIGN.md).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions