Task #626: stop notifications after logout + tighten meeting reminder
- /auth/logout: delete all push_subscriptions rows for the user before destroying the session. Best-effort with logger.warn on failure so an operator can correlate any leaked-ring report to a real DB error. - home.tsx handleLogout: call pushSub.disable() before the logout mutation, raced against a 1.5s timeout so a stalled service worker cannot block sign-out. - executive-meeting-scheduler: switch the eligibility filter from denylist (ne cancelled + ne completed) to whitelist (eq status='scheduled'). Postponed / rescheduled / future statuses can no longer trigger false 5-minute reminders. - docker-compose.yml: pin TZ=Asia/Riyadh on postgres, api, and web services so the scheduler's naive date/time math matches the operator's wall clock instead of UTC. Gitea push failed with TLS error during this session — code committed locally, needs manual `./scripts/publish-to-gitea.sh --push` retry when the desktop-11cj93j tunnel recovers.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { and, eq, inArray, ne, sql } from "drizzle-orm";
|
||||
import { and, eq, inArray, sql } from "drizzle-orm";
|
||||
import { db } from "@workspace/db";
|
||||
import {
|
||||
executiveMeetingAlertStateTable,
|
||||
@@ -92,8 +92,13 @@ async function tickOnce(): Promise<void> {
|
||||
.where(
|
||||
and(
|
||||
inArray(executiveMeetingsTable.meetingDate, [todayKey, tomorrowKey]),
|
||||
ne(executiveMeetingsTable.status, "cancelled"),
|
||||
ne(executiveMeetingsTable.status, "completed"),
|
||||
// #626: only ring for meetings whose status is still the
|
||||
// default "scheduled". Previously we used a denylist
|
||||
// (ne cancelled + ne completed), which let postponed /
|
||||
// rescheduled / in_progress and any future status sneak past
|
||||
// and trigger a false 5-minute push reminder. Whitelist is
|
||||
// safer: any status other than "scheduled" is silenced.
|
||||
eq(executiveMeetingsTable.status, "scheduled"),
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
@@ -12,9 +12,11 @@ import {
|
||||
groupsTable,
|
||||
userGroupsTable,
|
||||
auditLogsTable,
|
||||
pushSubscriptionsTable,
|
||||
} from "@workspace/db";
|
||||
import { requireAuth, requireAdmin, getUserRoles } from "../middlewares/auth";
|
||||
import { saveSession, destroySession } from "../lib/session";
|
||||
import { logger } from "../lib/logger";
|
||||
import {
|
||||
loginLimiters,
|
||||
registerLimiters,
|
||||
@@ -354,6 +356,36 @@ router.post(
|
||||
);
|
||||
|
||||
router.post("/auth/logout", async (req, res): Promise<void> => {
|
||||
// #626: drop every Web Push subscription this user owns before
|
||||
// tearing down the session. Otherwise the scheduler keeps firing
|
||||
// pushes to the iPad after sign-out (the rows live independently of
|
||||
// the session table, keyed by user_id). The client also tries to
|
||||
// unsubscribe its own browser endpoint, but that fails closed if the
|
||||
// SW or network hiccups — this server-side wipe is the authoritative
|
||||
// stop. Pruning every endpoint (not just the caller's) is the
|
||||
// correct behaviour for a single-user-per-account product: signing
|
||||
// out on any device implies "I don't want notifications anywhere
|
||||
// until I sign back in".
|
||||
const userId = req.session.userId;
|
||||
if (typeof userId === "number" && Number.isInteger(userId) && userId > 0) {
|
||||
try {
|
||||
await db
|
||||
.delete(pushSubscriptionsTable)
|
||||
.where(eq(pushSubscriptionsTable.userId, userId));
|
||||
} catch (err) {
|
||||
// Best-effort: a failed delete must not block sign-out. Log so
|
||||
// an operator investigating "I logged out but still got a push"
|
||||
// can correlate the leaked row to a real DB failure instead of
|
||||
// chasing a silent regression. The scheduler also prunes
|
||||
// 404/410 endpoints on send, so a leaked row eventually
|
||||
// self-cleans the next time it's used (worst case: one stale
|
||||
// ring until the OS-level subscription expires).
|
||||
logger.warn(
|
||||
{ err, userId },
|
||||
"Failed to delete push_subscriptions on logout — endpoint may continue receiving pushes until next 404/410 prune",
|
||||
);
|
||||
}
|
||||
}
|
||||
await destroySession(req);
|
||||
res.json({ success: true });
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user