diff --git a/app/api/push/preview/route.ts b/app/api/push/preview/route.ts index 150ce009a..8e48073ba 100644 --- a/app/api/push/preview/route.ts +++ b/app/api/push/preview/route.ts @@ -13,6 +13,7 @@ import { rebaseApiUrl } from '@/lib/stalwart/jmap-api'; import { forwardedClientHeaders, loginClientAddress } from '@/lib/auth/client-address'; import { DisallowedUrlError } from '@/lib/security/url-guard'; import { UPSTREAM_JSON_MAX_BYTES, UpstreamBodyTooLargeError, readJsonCapped } from '@/lib/security/capped-body'; +import { serverDevicePolicy } from '@/lib/admin/server-device-policy'; export const runtime = 'nodejs'; export const dynamic = 'force-dynamic'; @@ -23,6 +24,10 @@ interface ResolvedTarget { accountId: string; /** See StalwartCredentials.trusted - false routes through the guarded fetch. */ trusted: boolean; + /** The login, for its device policy. */ + serverUrl: string; + username: string; + accountName?: string; /** * Cookie slot of the login that owns the account. Handed back so a click * on the notification opens the message in that login: the worker knows @@ -75,7 +80,7 @@ async function resolveTargetForAccount(accountId: string, forwarded: Record Devices > Push without sender and subject, for this + // login's scopes (worked out from its own session): the message's + // sender, subject and text are never read, so they can't reach the + // notification, the push service or the lock screen. Unknown counts as on. + const redacted = await serverDevicePolicy({ + serverUrl: target.serverUrl, + authHeader, + username: target.username, + accountName: target.accountName, + trusted, + }).then((devices) => devices.pushWithoutContent, () => true); + const emailProperties = redacted + ? ['id', 'threadId', 'receivedAt'] + : ['id', 'threadId', 'from', 'subject', 'preview', 'receivedAt']; if (!inboxId && !requestedEmailId) { return NextResponse.json({ @@ -333,10 +351,13 @@ export async function GET(request: NextRequest) { // Some servers build the preview from HTML without skipping