mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 13:22:14 +00:00
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.
42 lines
1.5 KiB
TypeScript
42 lines
1.5 KiB
TypeScript
/**
|
|
* v1.23 — security-activity feed. The SHARED read both the account-security
|
|
* surface and the data-sovereignty privacy dashboard consume: a user-scoped,
|
|
* read-only window onto the `AuditLog` rows that matter for account security —
|
|
* logins, MFA events, password changes, token/session revocations, exports,
|
|
* and deletions. No new storage: it reuses the existing
|
|
* `@@index([userId, action, createdAt(sort: Desc)])`.
|
|
*/
|
|
|
|
/**
|
|
* Whether an audit action belongs in the user-facing security-activity feed.
|
|
* Pure so the contract can be unit-tested without a DB:
|
|
* - every `auth.*` event (login, mfa, password, token, bearer, session,
|
|
* register),
|
|
* - every export action (`export.download`, `health-record.export`,
|
|
* `user.export.*` — all contain "export"),
|
|
* - the two erasure actions (`user.account.delete`, `user.data.clear`).
|
|
*/
|
|
export function matchesSecurityActivity(action: string): boolean {
|
|
return (
|
|
action.startsWith("auth.") ||
|
|
action.includes("export") ||
|
|
action === "user.account.delete" ||
|
|
action === "user.data.clear"
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Prisma `where` mirror of `matchesSecurityActivity`, scoped to one user. Kept
|
|
* alongside the predicate so the two never drift.
|
|
*/
|
|
export function securityActivityWhere(userId: string) {
|
|
return {
|
|
userId,
|
|
OR: [
|
|
{ action: { startsWith: "auth." } },
|
|
{ action: { contains: "export" } },
|
|
{ action: { in: ["user.account.delete", "user.data.clear"] } },
|
|
],
|
|
};
|
|
}
|