feat: expose session and device management API (#131) - #140
Merged
3m1n3nc3 merged 2 commits intoAug 20, 2026
Conversation
Implements the session/device listing and revocation endpoints per the Phase 1 roadmap and issue learnault#131. ## What's added ### Database - Migration 20260820120000_session_device_fields: adds deviceName, browser, os, country, city, fingerprint, lastUsedAt columns to sessions table with a composite index on (userId, isRevoked, lastUsedAt). - Prisma schema updated to match. ### API endpoints GET /api/v1/sessions — paginated list of active sessions DELETE /api/v1/sessions — revoke all other sessions (keep current) DELETE /api/v1/sessions/:sessionId — revoke a specific session All three endpoints require authenticate + requireActiveAccount. ### Security properties enforced - Cross-user access returns 404 (no session-existence leakage) - Revoking the current session returns 400 CURRENT_SESSION - No token, refreshToken, raw ipAddress, userAgent, or fingerprint fields are ever included in responses - IP addresses stored in the DB are accessible only internally; the listing endpoint exposes only device/browser/OS/country/city labels - SESSION_REVOKED and SESSION_ALL_REVOKED audit entries written atomically with the revocation ### Schemas & docs - Zod schemas for query pagination and UUID path param validation - Full @openapi annotations on all three handlers - SessionView, SessionListResponse, RevokeSessionResponse, RevokeAllSessionsResponse added to OpenAPI component schemas ### Tests (53 passing) - redactIp, redactFingerprint, toSessionView unit tests - SessionController: listing, pagination, isCurrent marker, redaction, query validation, 500 handling - SessionController: revokeOne — ok, not-found, cross-user silent 404, current-session guard, UUID validation - SessionController: revokeAll — count, plural/singular message, idempotency, 500 handling - SessionService: list, revokeOne (all result branches), revokeAll, audit logging verification Closes learnault#131
- Add missing newline before return in redactFingerprint - Rename capturedOps -> _capturedOps (assigned but never used) - Add missing newlines before return in transaction mocks
7 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements the Session and Device Management API as specified in issue #131 (Phase 1 roadmap).
Depends on: #130 (Refresh Rotation and Logout API)
Blocks: #137 (Identity Profile Wallet Integration Test Suite)
Endpoints
/api/v1/sessions/api/v1/sessions/api/v1/sessions/:sessionIdAll endpoints require
authenticate+requireActiveAccount.Security properties
CURRENT_SESSIONtoken,refreshToken, rawipAddress,userAgent, and rawfingerprintare never included in any responseSESSION_REVOKEDandSESSION_ALL_REVOKEDaudit log entries are written in the same DB transaction as the revocationrevokeAllis idempotent (returns 200 withrevokedCount: 0when nothing left to revoke)Changes
Database
20260820120000_session_device_fields: addsdeviceName,browser,os,country,city,fingerprint,lastUsedAttosessionstable(userId, isRevoked, lastUsedAt)for efficient per-user active-session queriesNew files
src/types/session.types.ts—SessionView, result union types, audit action constantssrc/schemas/session.schema.ts— Zod schemas for pagination query and UUID path paramsrc/services/session.service.ts—SessionServicewithlist,revokeOne,revokeAll;redactIp,redactFingerprint,toSessionViewhelperssrc/controllers/session.controller.ts—SessionControllerwith full@openapiannotationssrc/routes/v1/sessions.routes.ts— route definitionsModified files
prisma/schema.prisma— Session model extendedsrc/routes/index.ts— registered/v1/sessionssrc/docs/schemas.ts—SessionView,SessionListResponse,RevokeSessionResponse,RevokeAllSessionsResponseOpenAPI schemasTests
53 tests added in
tests/session.controller.test.ts, all passing:redactIp— IPv4 masking, IPv6 masking, null/empty handlingredactFingerprint— truncation, null handlingtoSessionView— field mapping,isCurrentflag, no sensitive fields leakedSessionController.listSessions— pagination,isCurrentmarker, redaction, query validation, error handlingSessionController.revokeSession— ok, not-found, cross-user silent 404, current-session 400, UUID validation, 500 handlingSessionController.revokeAllOtherSessions— count, plural/singular message, idempotency, 500 handlingSessionService.list— current session bubbles to top, userId scopingSessionService.revokeOne— all result branches, audit entry writtenSessionService.revokeAll— bulk revocation, current-session exclusion, audit only when count > 0Verification evidence
Closes #131