mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 13:22:14 +00:00
feat(security): list active sessions, sign out everywhere, and show security activity
Add the user-facing account-security surface in Settings: - Active sessions (closes #64): GET /api/auth/me/sessions lists each browser login with a coarse device label, masked IP, resolved location, the sliding last-active stamp, and the current-session marker. DELETE the collection signs out everywhere else (keeps the current session, revokes other sessions and native refresh tokens, leaves API tokens alone); DELETE one revokes a single session, scoped to the owner so a foreign id 404s. - A sliding last-active stamp on the session, written throttled and fire-and-forget off the request-resolution path. - Security activity: GET /api/auth/me/security-activity, the shared read over the audit log filtered to sign-ins, second-factor changes, password changes, session revocations, exports, and deletions, with masked IPs. OpenAPI regenerated for the new endpoints.
This commit is contained in:
1 parent
bed6588699
commit
9269043e91
14 files changed
+1379
-1
No files matched your search
@@ -1554,6 +1554,81 @@ paths:
|
||||
"401": *a1
|
||||
"422": *a3
|
||||
"429": *a4
|
||||
/api/auth/me/sessions:
|
||||
get:
|
||||
tags:
|
||||
- Auth
|
||||
summary: List active web sessions
|
||||
description: "v1.23 — the user-facing active-session list (issue #64). One row per browser login with a coarse device
|
||||
label, masked IP, resolved location, sliding last-active time, and the current-session marker. Distinct from
|
||||
/api/auth/me/devices (notification devices)."
|
||||
responses:
|
||||
"200":
|
||||
description: Active sessions for the caller.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/SessionListEnvelope"
|
||||
"401": *a1
|
||||
"422": *a3
|
||||
"429": *a4
|
||||
delete:
|
||||
tags:
|
||||
- Auth
|
||||
summary: Sign out everywhere else
|
||||
description: v1.23 — revokes every OTHER web session plus all native refresh tokens, keeping the caller's current
|
||||
session. API tokens are not touched (manage those under /settings/api-tokens).
|
||||
responses:
|
||||
"200":
|
||||
description: Other sessions revoked.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/SignOutEverywhereEnvelope"
|
||||
"401": *a1
|
||||
"422": *a3
|
||||
"429": *a4
|
||||
/api/auth/me/sessions/{id}:
|
||||
delete:
|
||||
tags:
|
||||
- Auth
|
||||
summary: Revoke a single web session
|
||||
description: v1.23 — revokes one session by id, scoped to the authenticated user (a foreign id returns 404, never
|
||||
another user's row).
|
||||
responses:
|
||||
"200":
|
||||
description: Session revoked.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/SessionRevokeEnvelope"
|
||||
"401": *a1
|
||||
"404":
|
||||
description: Session not found or not owned by the caller.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorEnvelope"
|
||||
"422": *a3
|
||||
"429": *a4
|
||||
/api/auth/me/security-activity:
|
||||
get:
|
||||
tags:
|
||||
- Auth
|
||||
summary: List recent account-security activity
|
||||
description: "v1.23 — the SHARED security-activity feed: the caller's recent auth + export + deletion audit events with
|
||||
timestamp, resolved location, and a host-masked IP. `limit` query param caps at 100 (default 50). Reuses the
|
||||
AuditLog store; no event detail bodies are surfaced."
|
||||
responses:
|
||||
"200":
|
||||
description: Recent security events for the caller.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/SecurityActivityEnvelope"
|
||||
"401": *a1
|
||||
"422": *a3
|
||||
"429": *a4
|
||||
/api/sync/state:
|
||||
get:
|
||||
tags:
|
||||
@@ -11011,6 +11086,177 @@ components:
|
||||
- data
|
||||
- error
|
||||
additionalProperties: false
|
||||
SessionListEnvelope:
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
$ref: "#/components/schemas/SessionListResponse"
|
||||
error:
|
||||
type: "null"
|
||||
meta:
|
||||
type: object
|
||||
properties:
|
||||
requestId:
|
||||
type: string
|
||||
additionalProperties: false
|
||||
required:
|
||||
- data
|
||||
- error
|
||||
additionalProperties: false
|
||||
SessionListResponse:
|
||||
type: object
|
||||
properties:
|
||||
sessions:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
device:
|
||||
type: string
|
||||
description: Coarse device label derived from the User-Agent.
|
||||
ipMasked:
|
||||
anyOf:
|
||||
- type: string
|
||||
- type: "null"
|
||||
description: IP with the host portion masked — never the full address.
|
||||
location:
|
||||
anyOf:
|
||||
- type: string
|
||||
- type: "null"
|
||||
description: Resolved coarse location, when available.
|
||||
lastActiveAt:
|
||||
anyOf:
|
||||
- type: string
|
||||
format: date-time
|
||||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||||
- type: "null"
|
||||
createdAt:
|
||||
type: string
|
||||
format: date-time
|
||||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||||
isCurrent:
|
||||
type: boolean
|
||||
description: True for the session making this request.
|
||||
required:
|
||||
- id
|
||||
- device
|
||||
- ipMasked
|
||||
- location
|
||||
- lastActiveAt
|
||||
- createdAt
|
||||
- isCurrent
|
||||
additionalProperties: false
|
||||
required:
|
||||
- sessions
|
||||
additionalProperties: false
|
||||
SignOutEverywhereEnvelope:
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
$ref: "#/components/schemas/SignOutEverywhereResponse"
|
||||
error:
|
||||
type: "null"
|
||||
meta:
|
||||
type: object
|
||||
properties:
|
||||
requestId:
|
||||
type: string
|
||||
additionalProperties: false
|
||||
required:
|
||||
- data
|
||||
- error
|
||||
additionalProperties: false
|
||||
SignOutEverywhereResponse:
|
||||
type: object
|
||||
properties:
|
||||
sessionsRevoked:
|
||||
type: integer
|
||||
minimum: -9007199254740991
|
||||
maximum: 9007199254740991
|
||||
description: Number of OTHER sessions removed (the current one is kept).
|
||||
required:
|
||||
- sessionsRevoked
|
||||
additionalProperties: false
|
||||
SessionRevokeEnvelope:
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
type: object
|
||||
properties:
|
||||
revoked:
|
||||
type: boolean
|
||||
required:
|
||||
- revoked
|
||||
additionalProperties: false
|
||||
error:
|
||||
type: "null"
|
||||
meta:
|
||||
type: object
|
||||
properties:
|
||||
requestId:
|
||||
type: string
|
||||
additionalProperties: false
|
||||
required:
|
||||
- data
|
||||
- error
|
||||
additionalProperties: false
|
||||
SecurityActivityEnvelope:
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
$ref: "#/components/schemas/SecurityActivityResponse"
|
||||
error:
|
||||
type: "null"
|
||||
meta:
|
||||
type: object
|
||||
properties:
|
||||
requestId:
|
||||
type: string
|
||||
additionalProperties: false
|
||||
required:
|
||||
- data
|
||||
- error
|
||||
additionalProperties: false
|
||||
SecurityActivityResponse:
|
||||
type: object
|
||||
properties:
|
||||
events:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
action:
|
||||
type: string
|
||||
description: Audit action name, e.g. auth.login.password.
|
||||
createdAt:
|
||||
type: string
|
||||
format: date-time
|
||||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||||
location:
|
||||
anyOf:
|
||||
- type: string
|
||||
- type: "null"
|
||||
ipMasked:
|
||||
anyOf:
|
||||
- type: string
|
||||
- type: "null"
|
||||
description: Host-masked IP.
|
||||
carrier:
|
||||
anyOf:
|
||||
- type: string
|
||||
- type: "null"
|
||||
required:
|
||||
- action
|
||||
- createdAt
|
||||
- location
|
||||
- ipMasked
|
||||
- carrier
|
||||
additionalProperties: false
|
||||
required:
|
||||
- events
|
||||
additionalProperties: false
|
||||
SyncStateEnvelope:
|
||||
type: object
|
||||
properties:
|
||||
|
||||
Reference in new issue
Block a user