Skip to content

feat(users): PR11.1 backend - user filters, last login, auth method - #22

Merged
man4ish merged 1 commit into
mainfrom
feature/pr11-user-management
Aug 5, 2026
Merged

feat(users): PR11.1 backend - user filters, last login, auth method#22
man4ish merged 1 commit into
mainfrom
feature/pr11-user-management

Conversation

@man4ish

@man4ish man4ish commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

Objective

Backend half of PR11.1 (Enterprise User Management Enhancement). Companion frontend PR: OmniBioAI/omnibioai-control-center#11.

Per the discovery doc from this same effort (omnibioai-control-center/docs/admin-console-pr11-identity-findings.md), this extends the existing /platform/users directory rather than introducing anything new — no new permission, no new authorization model.

Changes

1. GET /platform/users filters — three new, independent, optional query params: organization_id, status, role. All backward compatible (omitted = unfiltered, exactly as before). role means different things depending on whether organization_id is also present, matching how this codebase already models roles as two separate catalogs:

  • With organization_id: matches the caller's role within that org (membership_roles).
  • Without it: matches a global role (user_roles).

Still gated by the same manage_all_orgs permission as before — no new permission introduced.

2. users.last_login_at / users.authentication_method (migration 0012_user_login_metadata, additive, nullable, no backfill — a user with no login since this migration has honest null, never a fabricated value).

Both fields are written from exactly one place: auth_service.generate_tokens, the shared choke point every login flow (password/oauth/sso/license) already calls. This means every flow stays in sync by construction rather than repeating the write at 7 different call sites across routes_auth.py/routes_oauth.py/routes_sso.py/routes_license.py. Deliberately not in build_user_claims (which rotate_refresh_token also calls) — a token refresh continues an existing session, it must not look like a new login.

authentication_method uses a narrower, admin-console-facing vocabulary (password/oauth/oidc/unknown) than the JWT auth_method claim (password/oauth/sso/license), which is unchanged: ssooidc for display, licenseunknown (no dedicated slot for it in the PR11.1 spec).

Both new fields are added to PlatformUserSummary (list) and PlatformUserDetailOut (detail).

Test results

Full suite: 536 passed. New coverage: filter combinations (org/status/role, independently and combined), backward-compatibility when all three are omitted, login-metadata persistence across all three real login flows (password in test_platform_users_api.py, SSO in test_sso_login.py, consumer OAuth in test_oauth.py), null-for-never-logged-in.

Non-goals (per spec)

No MFA, domain verification, SAML, SCIM, billing, usage metering, audit center, or session management — all future PR11.x work.

🤖 Generated with Claude Code

Extends GET /platform/users with organization_id/status/role filters
(all additive, backward compatible) and adds two new persisted fields
to the user directory: last_login_at and authentication_method.

Both are written from exactly one place, generate_tokens -- the shared
choke point every login flow (password/oauth/sso/license) already
calls -- so every flow stays in sync by construction rather than
repeating the write at each call site. Never written on token refresh,
which continues a session rather than starting one. No historical data
is fabricated: a user who predates this migration, or has never logged
in since, shows null end-to-end.

No new permission, no authorization changes -- every endpoint here
still gates on the existing manage_all_orgs permission.
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