Files
webmail/lib/impersonation/grant-cookie.ts
T
Linus Rath 11eb0bfb16 fix: keep the Stalwart master password out of impersonated sessions
An impersonation handoff sealed `<target>%<master>` and the master
password into the session cookie, and `PUT /api/auth/session` hands that
cookie's contents to the browser on every restore. Anyone who received
a handoff could read a credential that signs in as every mailbox and
into Stalwart administration, with no expiry and no audit trail.

The impersonate route now uses the master credential server-side, once,
to create an app password on the target mailbox that expires after
8 hours. Only that app password reaches the cookies. Signing out
revokes it through a sealed grant cookie, and PUT refuses any session
that still holds the configured master password, so cookies minted
before this change are dropped instead of returned.
2026-09-26 14:05:05 +02:00

30 lines
1.0 KiB
TypeScript

import { decryptPayload, encryptPayload } from '@/lib/auth/crypto';
/**
* Remembers which app password an impersonation handoff minted, so signing
* out can revoke it instead of leaving it valid until it expires.
* Impersonation always lands in slot 0, so one cookie is enough.
*/
export const IMPERSONATION_GRANT_COOKIE = 'jmap_impersonation';
export interface ImpersonationGrant {
serverUrl: string;
mailbox: string;
credentialId: string;
}
export function sealImpersonationGrant(grant: ImpersonationGrant): string {
return encryptPayload({ ...grant }, 'impersonation-grant');
}
export function openImpersonationGrant(token: string | undefined): ImpersonationGrant | null {
if (!token) return null;
const payload = decryptPayload(token, 'impersonation-grant');
if (!payload) return null;
const { serverUrl, mailbox, credentialId } = payload;
if (typeof serverUrl !== 'string' || typeof mailbox !== 'string' || typeof credentialId !== 'string') {
return null;
}
return { serverUrl, mailbox, credentialId };
}