Skip to content

feat: expose session and device management API (#131) - #140

Merged
3m1n3nc3 merged 2 commits into
learnault:mainfrom
Anichris-koded:feat/session-device-management-api
Aug 20, 2026
Merged

feat: expose session and device management API (#131)#140
3m1n3nc3 merged 2 commits into
learnault:mainfrom
Anichris-koded:feat/session-device-management-api

Conversation

@Anichris-koded

Copy link
Copy Markdown
Contributor

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

Method Path Description
GET /api/v1/sessions Paginated list of active sessions with device/location/current markers
DELETE /api/v1/sessions Revoke all other sessions (keep current)
DELETE /api/v1/sessions/:sessionId Revoke a specific session by ID

All endpoints require authenticate + requireActiveAccount.


Security properties

  • Cross-user protection — revoking another user's session returns 404 (no session-existence leakage)
  • Current-session guard — attempting to revoke your own current session returns 400 CURRENT_SESSION
  • Token/IP redactiontoken, refreshToken, raw ipAddress, userAgent, and raw fingerprint are never included in any response
  • Atomic auditSESSION_REVOKED and SESSION_ALL_REVOKED audit log entries are written in the same DB transaction as the revocation
  • Stable repeat behaviorrevokeAll is idempotent (returns 200 with revokedCount: 0 when nothing left to revoke)

Changes

Database

  • Migration 20260820120000_session_device_fields: adds deviceName, browser, os, country, city, fingerprint, lastUsedAt to sessions table
  • Composite index on (userId, isRevoked, lastUsedAt) for efficient per-user active-session queries

New files

  • src/types/session.types.tsSessionView, result union types, audit action constants
  • src/schemas/session.schema.ts — Zod schemas for pagination query and UUID path param
  • src/services/session.service.tsSessionService with list, revokeOne, revokeAll; redactIp, redactFingerprint, toSessionView helpers
  • src/controllers/session.controller.tsSessionController with full @openapi annotations
  • src/routes/v1/sessions.routes.ts — route definitions

Modified files

  • prisma/schema.prisma — Session model extended
  • src/routes/index.ts — registered /v1/sessions
  • src/docs/schemas.tsSessionView, SessionListResponse, RevokeSessionResponse, RevokeAllSessionsResponse OpenAPI schemas

Tests

53 tests added in tests/session.controller.test.ts, all passing:

  • redactIp — IPv4 masking, IPv6 masking, null/empty handling
  • redactFingerprint — truncation, null handling
  • toSessionView — field mapping, isCurrent flag, no sensitive fields leaked
  • SessionController.listSessions — pagination, isCurrent marker, redaction, query validation, error handling
  • SessionController.revokeSession — ok, not-found, cross-user silent 404, current-session 400, UUID validation, 500 handling
  • SessionController.revokeAllOtherSessions — count, plural/singular message, idempotency, 500 handling
  • SessionService.list — current session bubbles to top, userId scoping
  • SessionService.revokeOne — all result branches, audit entry written
  • SessionService.revokeAll — bulk revocation, current-session exclusion, audit only when count > 0

Verification evidence

✓ tests/session.controller.test.ts (53 tests) 215ms

Test Files: 39 passed
Tests:      520 passed | 3 skipped

Closes #131

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
@3m1n3nc3
3m1n3nc3 merged commit 206430c into learnault:main Aug 20, 2026
1 check 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.

Feature: Expose Session and Device Management API

2 participants