generated from nholtware/service-template-express
feat: change password (revokes other sessions) and logout/login regression test
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
afbc5e5254
commit
e69a02c6df
5 changed files with 247 additions and 10 deletions
|
|
@ -61,6 +61,14 @@ export class PgSessionStore extends session.Store {
|
|||
);
|
||||
}
|
||||
|
||||
/** Sign a user out everywhere except `exceptSid` (e.g. after a password change). */
|
||||
async destroyForUser(userId: string, exceptSid: string): Promise<void> {
|
||||
await this.db.query(
|
||||
"DELETE FROM sessions WHERE sess->'passport'->>'user' = $1 AND sid <> $2",
|
||||
[userId, exceptSid],
|
||||
);
|
||||
}
|
||||
|
||||
destroy(sid: string, cb?: (err?: unknown) => void) {
|
||||
this.db.query("DELETE FROM sessions WHERE sid = $1", [sid]).then(
|
||||
() => cb?.(),
|
||||
|
|
@ -68,3 +76,24 @@ export class PgSessionStore extends session.Store {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
/** In-memory sessions (tests / no database), with the same revoke hook. */
|
||||
export class MemorySessionStore extends session.MemoryStore {
|
||||
async destroyForUser(userId: string, exceptSid: string): Promise<void> {
|
||||
const all = await new Promise<Record<string, session.SessionData>>(
|
||||
(resolve, reject) =>
|
||||
this.all((err, sessions) =>
|
||||
err ? reject(err) : resolve(sessions ?? {}),
|
||||
),
|
||||
);
|
||||
for (const [sid, sess] of Object.entries(all)) {
|
||||
const passport = (sess as { passport?: { user?: string } }).passport;
|
||||
if (sid !== exceptSid && passport?.user === userId) {
|
||||
await new Promise<void>((r) => this.destroy(sid, () => r()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type RevocableStore = (PgSessionStore | MemorySessionStore) &
|
||||
session.Store;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue