feat(auth): Active Devices — view/revoke mobile sessions
Adds the self-service device-session surface the mobile-SSO spec requires, on
top of the existing mobile_refresh_tokens store.
- Schema: device_name + last_used_at columns on mobile_refresh_tokens (nullable,
additive via the ALTER section; seeded to now on insert). With single-use
rotation each login/refresh inserts a fresh row, so the active row's timestamp
is the session's last activity, and the label is carried forward on refresh.
- Model: listActiveForUser (one row per live device, no token hash) +
revokeByIdForUser (ownership-scoped, idempotent).
- GET /auth/me/sessions + DELETE /auth/me/sessions/:id (role-agnostic, behind
requireAuth). Named distinctly from /auth/me/devices (push endpoints).
- device_name is an optional field on /auth/mobile/login and
/auth/mobile/sso/exchange so the app can label a device.
- Client: an "Active Devices" panel on the player account page (list + sign a
device out), plus the PlayerLogin change to honor the mobile SSO bridge's
{ redirect } deep link on a 2FA completion.
- Swagger DeviceSession schema + regenerated spec; 3 controller tests. Full
server suite green (274); client builds.
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -158,6 +158,7 @@ const doc = {
|
||||
username: { type: 'string', example: 'admin' },
|
||||
password: { type: 'string', format: 'password', example: 'super-secret' },
|
||||
code: { type: 'string', description: 'TOTP code (only when 2FA is enabled).', example: '123456' },
|
||||
device_name: { type: 'string', description: 'Optional friendly device label for Active Devices.', example: 'Pixel 8' },
|
||||
},
|
||||
},
|
||||
MobileTokenResponse: {
|
||||
@@ -197,6 +198,18 @@ const doc = {
|
||||
type: 'string',
|
||||
description: 'The PKCE verifier for the challenge sent to /auth/mobile/sso/start.',
|
||||
},
|
||||
device_name: { type: 'string', description: 'Optional friendly device label for Active Devices.', example: 'Pixel 8' },
|
||||
},
|
||||
},
|
||||
DeviceSession: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'integer', description: 'Session row id (pass to DELETE /auth/me/sessions/:id).' },
|
||||
deviceName: { type: 'string', nullable: true, example: 'Pixel 8' },
|
||||
userAgent: { type: 'string', nullable: true },
|
||||
createdAt: { type: 'string', format: 'date-time' },
|
||||
lastUsedAt: { type: 'string', format: 'date-time' },
|
||||
expiresAt: { type: 'string', format: 'date-time' },
|
||||
},
|
||||
},
|
||||
Message: {
|
||||
|
||||
Reference in New Issue
Block a user