mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 08:13:50 +00:00
Stalwart 1.0 refuses Principal/query and Principal/getAvailability with `forbidden` unless the administrator allows directory queries, which is off by default. `getPrincipals` turned that into an empty list with no signal: the share dialog said "No other users or groups found", existing shares showed bare ids, and free/busy called every attendee "Not a user on this server". - `listPrincipals` says whether the listing is complete, `restricted` or failed. On `forbidden` it falls back to Principal/get without ids, which 1.0 still answers with the user's own account, their groups and the accounts that shared something with them. - Principal/get asks for `id`, `type`, `name`, `description` and `email` only; 1.0's default list adds `capabilities` and `accounts`. - One listing per client, kept for five minutes, serves the share dialog, recipient autocomplete and free/busy, which used to download the directory three times. - The share dialog explains a restricted directory while still offering its candidates, says when the list failed to load, and shows "Unknown user" with the id for sharees it can't name. - Free/busy first reads `mayGetAvailability` from the user's own principal and stays hidden when it is false or a lookup is refused with `forbidden`. "Not a user on this server" now needs a complete listing; failures and `rateLimit` show "Couldn't check". On 0.16 with the default roles the listing is complete and the dialog behaves as before.
38 lines
1.6 KiB
TypeScript
38 lines
1.6 KiB
TypeScript
/**
|
|
* One principal listing per client, shared by everything that needs the
|
|
* server's directory: the share dialog, recipient autocomplete and free/busy.
|
|
* Each used to download the whole directory on its own - the share dialog on
|
|
* every open - and a listing pages through up to 20,000 principals.
|
|
*/
|
|
|
|
import type { IJMAPClient } from "./client-interface";
|
|
import type { PrincipalListing } from "./types";
|
|
|
|
export const PRINCIPAL_DIRECTORY_TTL_MS = 5 * 60 * 1000;
|
|
|
|
const listings = new WeakMap<IJMAPClient, { loadedAt: number; listing: Promise<PrincipalListing> }>();
|
|
|
|
function fetchListing(client: IJMAPClient): Promise<PrincipalListing> {
|
|
if (client.listPrincipals) return client.listPrincipals();
|
|
return client.getPrincipals().then((principals) => ({ principals, status: "ok" as const }));
|
|
}
|
|
|
|
/**
|
|
* The client's principal listing, shared while it is fresh; callers that
|
|
* arrive while it loads get the same request. Never rejects.
|
|
*/
|
|
export function loadPrincipalDirectory(client: IJMAPClient): Promise<PrincipalListing> {
|
|
const cached = listings.get(client);
|
|
if (cached && Date.now() - cached.loadedAt < PRINCIPAL_DIRECTORY_TTL_MS) {
|
|
return cached.listing;
|
|
}
|
|
const listing = fetchListing(client).catch((): PrincipalListing => ({ principals: [], status: "error" }));
|
|
const entry = { loadedAt: Date.now(), listing };
|
|
listings.set(client, entry);
|
|
void listing.then(({ status }) => {
|
|
// A failed listing isn't kept, so the next caller asks again.
|
|
if (status === "error" && listings.get(client) === entry) listings.delete(client);
|
|
});
|
|
return listing;
|
|
}
|