mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 08:13:50 +00:00
A WOPI token carries the user's credentials to the editor server and stayed valid for its whole six-hour lifetime, so an editor opened before signing out could still read and save the document afterwards. Each token now records the browser (a random id in an HttpOnly cookie, hashed) and account slot that opened it. Signing out records a revocation time for that browser and slot, or for every slot on a full sign-out, and WOPI calls with a token minted before it are refused. Signing out on another device leaves this browser's editors alone. Revocations live in the state directory, so they survive a restart and reach replicas sharing the state volume, and are dropped once the tokens they cover have expired.
38 lines
1.6 KiB
TypeScript
38 lines
1.6 KiB
TypeScript
import type { NextRequest } from 'next/server';
|
|
import { isTrustedJmapServerUrl } from '@/lib/stalwart/server-fetch';
|
|
import type { WopiJmapContext } from '@/lib/wopi/files';
|
|
import { verifyWopiToken, type WopiTokenPayload } from '@/lib/wopi/token';
|
|
import { isWopiTokenRevoked } from '@/lib/wopi/revocation';
|
|
|
|
/**
|
|
* Authenticate an incoming WOPI request (called by the editor
|
|
* server-to-server, no session cookie) from its `access_token` query
|
|
* parameter, scoped to the document id in the URL (see wopiDocumentId).
|
|
*/
|
|
export async function wopiContext(
|
|
request: NextRequest,
|
|
documentId: string,
|
|
): Promise<{ payload: WopiTokenPayload; ctx: WopiJmapContext } | null> {
|
|
const payload = verifyWopiToken(request.nextUrl.searchParams.get('access_token'), documentId);
|
|
if (!payload) return null;
|
|
// Signed out since the editor was opened.
|
|
if (await isWopiTokenRevoked(payload)) return null;
|
|
// Trust is re-derived from the current config, not persisted in the token
|
|
// (mirrors lib/stalwart/server-fetch.ts): removing a server from the config
|
|
// immediately drops its tokens to the rebinding-safe fetch path. A server on
|
|
// the webmail's own origin (the dev mock) is this process itself - the
|
|
// public-URL guard would refuse localhost, so it counts as trusted.
|
|
const sameOrigin = (() => {
|
|
try {
|
|
return new URL(payload.serverUrl).origin === payload.origin;
|
|
} catch {
|
|
return false;
|
|
}
|
|
})();
|
|
const trusted = sameOrigin || await isTrustedJmapServerUrl(payload.serverUrl);
|
|
return {
|
|
payload,
|
|
ctx: { serverUrl: payload.serverUrl, authHeader: payload.authHeader, trusted },
|
|
};
|
|
}
|