Skip to content

feat(BA-5733): record, expose, and filter login_history by client IP#11733

Draft
jopemachine wants to merge 9 commits into
mainfrom
feat/BA-5733-login-history-client-ip
Draft

feat(BA-5733): record, expose, and filter login_history by client IP#11733
jopemachine wants to merge 9 commits into
mainfrom
feat/BA-5733-login-history-client-ip

Conversation

@jopemachine
Copy link
Copy Markdown
Member

@jopemachine jopemachine commented May 21, 2026

Summary

  • Adds a nullable client_ip column to login_history (new Alembic migration a1b3e7c2d4f5, chained after b8a85c96607c).
  • Captures the originating IP for every user-initiated login_history event: authorize (success and failure), logout, signout, and admin/user revocation. System-driven events (EVICTED, EXPIRED) intentionally leave client_ip NULL.
  • Exposes the value via LoginHistoryV2.clientIp (Strawberry, NEXT_RELEASE_VERSION) and the v2 REST response DTO LoginHistoryNode.client_ip; supergraph schema regenerated accordingly.

Resolves BA-5733.

client_ip propagation strategy

Two patterns are used depending on whether auth_middleware has populated the request-scoped current_client_ip() contextvar by the time the service runs. The middleware only enters with_client_ip(...) for endpoints whose handler is decorated with auth_required (i.e. JWT/HMAC-authenticated routes) — see _setup_user_context in api/rest/middleware/auth.py.

Passed explicitly via Action

Endpoints that do not go through auth_required — the contextvar is unset, so the REST handler calls extract_client_ip(request) and threads the value through the Action's client_ip field.

Endpoint Action Where client_ip is set Why not auth_required
POST /auth/authorize AuthorizeAction.client_ip AuthHandler.authorizeextract_client_ip(ctx.request) Login itself — no session exists yet
POST /auth/logout LogoutAction.client_ip AuthHandler.logoutextract_client_ip(ctx.request) Authenticates via the session_token in the request body, not JWT/HMAC headers

Resolved by the service via current_client_ip()

Endpoints behind auth_requiredauth_middleware enters with_client_ip(...) inside _setup_user_context, so the service layer can call current_client_ip() directly (mirroring the existing current_user() pattern). The Action no longer carries client_ip.

Endpoint Action Where client_ip is resolved
POST /auth/signout (REST) SignoutAction AuthService.signoutcurrent_client_ip()
myRevokeLoginSession mutation (GQL) MyRevokeLoginSessionAction AuthService.my_revoke_login_sessioncurrent_client_ip()
adminRevokeLoginSession mutation (GQL) AdminRevokeLoginSessionAction AuthService.admin_revoke_login_sessioncurrent_client_ip()

Test plan

  • pants fmt --changed-since=origin/main — clean
  • pants lint --changed-since=origin/main — clean
  • pants check --changed-since=origin/main — mypy clean
  • pants test tests/unit/manager/services/auth:: — passes
  • pants test tests/unit/manager/api/auth:: — passes
  • Apply migration on a halfstack DB and confirm login_history.client_ip is populated for: authorize success, authorize failure, logout, admin revoke, user revoke
  • Confirm clientIp appears in myLoginHistoryV2 / adminLoginHistoryV2 GraphQL responses after restarting the manager and Hive (Apollo Router) containers
  • Confirm system-driven EVICTED / EXPIRED rows still have client_ip = NULL

🤖 Generated with Claude Code


📚 Documentation preview 📚: https://sorna--11733.org.readthedocs.build/en/11733/


📚 Documentation preview 📚: https://sorna-ko--11733.org.readthedocs.build/ko/11733/


📚 Documentation preview 📚: https://sorna--11733.org.readthedocs.build/en/11733/


📚 Documentation preview 📚: https://sorna-ko--11733.org.readthedocs.build/ko/11733/

@github-actions github-actions Bot added size:L 100~500 LoC area:docs Documentations comp:manager Related to Manager component comp:common Related to Common component require:db-migration Automatically set when alembic migrations are added or updated labels May 21, 2026
jopemachine and others added 4 commits May 22, 2026 13:15
Add a nullable `client_ip` column to `login_history` and populate it
from REST handlers and the GraphQL revoke adapter for every
user-initiated event (authorize success/failure, logout, signout,
admin/user revocation). System-driven events (eviction, expiration)
remain NULL. Expose the field on `LoginHistoryV2` (`clientIp`) so
admins and users can see where each event originated.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: octodog <mu001@lablup.com>
Resolve diverged Alembic heads caused by b8a85c96607c (start_command
rewrap) landing on main with the same parent revision (7a9be5b982c0)
as this branch's client_ip migration.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…flows

Drop the ``client_ip`` field from Actions whose handlers are already
authenticated (``signout``, ``my_revoke_login_session``,
``admin_revoke_login_session``) and have the service resolve it via
``current_client_ip()``, mirroring how ``current_user()`` is consumed in
service logic. The auth middleware already populates the contextvar for
authenticated routes, so handlers no longer have to thread the value
through the Action.

Unauthenticated entry points (``authorize``, ``logout``) keep the
explicit ``client_ip`` Action field — the middleware does not set the
contextvar before authentication, so the REST handler still extracts it
from the request directly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jopemachine jopemachine force-pushed the feat/BA-5733-login-history-client-ip branch from 9772d32 to e402ef3 Compare May 22, 2026 04:17
jopemachine and others added 2 commits May 22, 2026 13:28
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Expose client_ip in LoginHistoryFilter (v2 DTO + GraphQL input) and
LoginHistoryOrderField so admins and users can drill into history by
source IP. Wire the new field through the adapter and add matching
StringFilter / order helpers in the auth repository options.

Rename the news fragment to 11733.feature.md to match the PR number.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jopemachine jopemachine changed the title feat(BA-5733): record client IP on login_history entries feat(BA-5733): record, expose, and filter login_history by client IP May 22, 2026
Cover every AuthDBSource path that writes login_history (create,
record_login_history, delete_session_by_token/id, delete_sessions_by_user,
delete_sessions_by_tokens) and confirm the supplied client_ip lands on
the row verbatim. System-driven eviction without a client_ip still leaves
the column NULL, matching the production call sites.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added size:XL 500~ LoC and removed size:L 100~500 LoC labels May 22, 2026
jopemachine and others added 2 commits May 22, 2026 13:51
…rals

Inject a single ``client_ip`` fixture (``1.2.3.4``) into each test rather
than sprinkling distinct hard-coded IP strings across scenarios. The
fixture-based form keeps the assertions readable and removes incidental
variation between tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Rename and expand the eviction test to make the data model explicit:
``login_history`` is append-only, so the original ``SUCCESS`` row keeps
its ``client_ip`` even after the ``login_session`` is force-evicted. The
newly written ``EVICTED`` row carries ``client_ip = NULL`` because
eviction is system-driven and no client initiated it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jopemachine jopemachine marked this pull request as ready for review May 22, 2026 05:03
@jopemachine jopemachine requested a review from a team as a code owner May 22, 2026 05:03
Copilot AI review requested due to automatic review settings May 22, 2026 05:03
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds end-to-end support for persisting the originating client IP address on login_history rows, and exposes/filter/sort support for the new field across REST v2 and Strawberry GraphQL v2.

Changes:

  • Add nullable client_ip column to login_history and propagate it through auth session/history write paths.
  • Expose client_ip via REST v2 DTOs and Strawberry GraphQL LoginHistoryV2, including filter and order support.
  • Add repository-level unit tests ensuring client_ip is persisted (and remains NULL for system-driven events).

Reviewed changes

Copilot reviewed 21 out of 21 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/unit/manager/repositories/auth/test_login_history_client_ip.py Adds unit tests covering client_ip persistence across AuthDBSource write paths.
src/ai/backend/manager/services/auth/service.py Threads client_ip into login-history writes; uses request-scoped current_client_ip() where available.
src/ai/backend/manager/services/auth/actions/logout.py Extends LogoutAction to carry optional client_ip.
src/ai/backend/manager/services/auth/actions/authorize.py Extends AuthorizeAction to carry optional client_ip.
src/ai/backend/manager/repositories/auth/repository.py Extends repository APIs to accept and forward client_ip to DB source.
src/ai/backend/manager/repositories/auth/options.py Adds client_ip filter and order factories for login history querying.
src/ai/backend/manager/repositories/auth/db_source/db_source.py Persists client_ip in login history insert paths and delete-session history generation.
src/ai/backend/manager/models/login_session/row.py Adds client_ip column mapping and includes it in to_data().
src/ai/backend/manager/models/alembic/versions/a1b3e7c2d4f5_add_client_ip_to_login_history.py Alembic migration adding/dropping login_history.client_ip.
src/ai/backend/manager/data/auth/login_session_types.py Extends LoginHistoryData with client_ip.
src/ai/backend/manager/api/rest/auth/handler.py Extracts client IP for authorize/logout and passes it into actions.
src/ai/backend/manager/api/gql/login_history/types/order.py Adds CLIENT_IP to GraphQL order field enum.
src/ai/backend/manager/api/gql/login_history/types/node.py Exposes clientIp on LoginHistoryV2 (version-gated).
src/ai/backend/manager/api/gql/login_history/types/filter.py Adds GraphQL clientIp filter field (version-gated).
src/ai/backend/manager/api/adapters/login_history/adapter.py Converts client_ip filters/orders and maps data to response nodes.
src/ai/backend/common/dto/manager/v2/login_history/types.py Adds CLIENT_IP to REST v2 order field enum.
src/ai/backend/common/dto/manager/v2/login_history/response.py Exposes client_ip in REST v2 response node DTO.
src/ai/backend/common/dto/manager/v2/login_history/request.py Adds client_ip to REST v2 filter DTO.
docs/manager/graphql-reference/v2-schema.graphql Regenerates schema docs to include clientIp filter/field and ordering.
docs/manager/graphql-reference/supergraph.graphql Regenerates supergraph schema docs to include clientIp filter/field and ordering.
changes/11733.feature.md Adds changelog entry for the new client_ip field exposure.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 151 to 156
stoken=params.stoken,
otp=params.otp,
client_type_id=params.client_type_id,
client_ip=extract_client_ip(ctx.request),
force=params.force,
)
Comment on lines 179 to 187

async def logout(self, body: BodyParam[LogoutRequest], ctx: RequestCtx) -> APIResponse:
params = body.parsed
log.info("AUTH.LOGOUT(session_token:{}...)", params.session_token[:8])
action = LogoutAction(session_token=params.session_token)
action = LogoutAction(
session_token=params.session_token,
client_ip=extract_client_ip(ctx.request),
)
await self._auth.logout.wait_for_complete(action)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:docs Documentations comp:common Related to Common component comp:manager Related to Manager component require:db-migration Automatically set when alembic migrations are added or updated size:XL 500~ LoC

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants