Send account requests to the account's own Stalwart
Two requests ignored the domain mapping from #238 and went to STALWART_URL: - /api/account/* re-fetched the upstream session without upstreamFor(), so once the five-minute session cache expired, password, app-password and 2FA calls for a mapped domain reached the default server. - The locale lookup resolved Stalwart's apiUrl against the default server rather than the one that issued the session. Both now use the session's own server, with a test pinning the second.
This commit is contained in:
@@ -110,3 +110,32 @@ test("a shared account carrying the capability is enough to recognise the server
|
|||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* With a domain mapped to its own Stalwart (#238), everything asked about the
|
||||||
|
* account has to go to that server. The locale lookup resolved Stalwart's
|
||||||
|
* `apiUrl` against the default server instead, so a mapped account's locale
|
||||||
|
* was requested from a server that had never heard of it.
|
||||||
|
*/
|
||||||
|
test("account info is asked of the server that issued the session", async () => {
|
||||||
|
const seen: string[] = [];
|
||||||
|
const realFetch = globalThis.fetch;
|
||||||
|
globalThis.fetch = (async (input: string | URL | Request) => {
|
||||||
|
seen.push(String(input instanceof Request ? input.url : input));
|
||||||
|
return new Response(JSON.stringify({ methodResponses: [], edition: "oss" }), { status: 200, headers: { "content-type": "application/json" } });
|
||||||
|
}) as typeof fetch;
|
||||||
|
try {
|
||||||
|
const session = {
|
||||||
|
capabilities: baseCaps,
|
||||||
|
accounts: { a1: { accountCapabilities: { [STALWART]: {} } } },
|
||||||
|
primaryAccounts: { [STALWART]: "a1" },
|
||||||
|
apiUrl: "https://mail.mapped.test/jmap/",
|
||||||
|
baseUrl: "https://mail.mapped.test",
|
||||||
|
};
|
||||||
|
await getAccountInfo("session-mapped-domain", "Basic x", session as never);
|
||||||
|
} finally {
|
||||||
|
globalThis.fetch = realFetch;
|
||||||
|
}
|
||||||
|
assert.ok(seen.length >= 2, "asks for both the locale and the edition");
|
||||||
|
for (const url of seen) assert.ok(url.startsWith("https://mail.mapped.test/"), `${url} went to the wrong server`);
|
||||||
|
});
|
||||||
|
|||||||
+5
-1
@@ -459,7 +459,11 @@ export function createApp(basePath = config.basePath): Hono<Env> {
|
|||||||
*/
|
*/
|
||||||
const accountCtx = async (c: Context<Env>) => {
|
const accountCtx = async (c: Context<Env>) => {
|
||||||
const session = c.get("session");
|
const session = c.get("session");
|
||||||
const upstream = await getUpstreamSession(session.id, session.authorization);
|
// The account's own server. Without it, the first fetch after the cached
|
||||||
|
// session expires goes to STALWART_URL -- which, for a domain mapped
|
||||||
|
// elsewhere, either refuses the password or knows a different account by
|
||||||
|
// the same name (#238).
|
||||||
|
const upstream = await getUpstreamSession(session.id, session.authorization, upstreamFor(session.username));
|
||||||
return { authorization: session.authorization, session: upstream, username: session.username };
|
return { authorization: session.authorization, session: upstream, username: session.username };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -198,7 +198,9 @@ async function fetchAccountInfo(authorization: string, session: UpstreamSession)
|
|||||||
session.primaryAccounts?.["urn:ietf:params:jmap:mail"] ??
|
session.primaryAccounts?.["urn:ietf:params:jmap:mail"] ??
|
||||||
Object.keys(session.accounts ?? {})[0];
|
Object.keys(session.accounts ?? {})[0];
|
||||||
if (!accountId) return EMPTY_INFO;
|
if (!accountId) return EMPTY_INFO;
|
||||||
const res = await fetch(absoluteUpstream(session.apiUrl), {
|
// Against the server that issued this session, not the default: with a
|
||||||
|
// domain mapped elsewhere, the default has never heard of the account.
|
||||||
|
const res = await fetch(absoluteUpstream(session.apiUrl, session.baseUrl), {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { authorization, "content-type": "application/json", accept: "application/json" },
|
headers: { authorization, "content-type": "application/json", accept: "application/json" },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
|
|||||||
Reference in New Issue
Block a user