mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 16:13:15 +00:00
The admin page offered "User Plugin Uploads" and "Require Admin Approval", but nothing in Settings used them: there was no upload, no uninstall and no way to ask for approval, and the admin had no list of requests. The approval code that existed pinned a hash of the code only and showed the admin a manifest the browser sent, never the code. Now: - Settings > Plugins has an install button (when uploads are allowed), a remove button for the user's own plugins, and lists in plain language what each plugin may do, high-risk items first - an install sends the ZIP to the server, which reads the manifest and code itself and pins the request to a hash of both - the admin Plugins tab lists requests with the code and approves or denies them; a user may have 10 open requests - a user plugin is checked again on every start, so a revoked approval or turning user plugins off takes effect - the consent dialog and the plugin settings are translated, use the theme colours, and show the sites a plugin may send data to
61 lines
1.7 KiB
TypeScript
61 lines
1.7 KiB
TypeScript
// Queue for plugin permission-consent requests.
|
|
//
|
|
// On first enable the plugin store posts a ConsentRequest here; the
|
|
// `PluginConsentDialog` component renders the head and resolves the promise
|
|
// when the user accepts or rejects. The store persists the granted set in
|
|
// `plugin.grantedPermissions` so future enables don't re-prompt.
|
|
|
|
import type { Permission } from '../plugin-types';
|
|
|
|
export interface ConsentRequest {
|
|
id: string;
|
|
pluginId: string;
|
|
pluginName: string;
|
|
permissions: Permission[];
|
|
/** Where the plugin may send data, shown under http:fetch / http:post. */
|
|
httpOrigins?: string[];
|
|
apiPostPaths?: string[];
|
|
resolve: (granted: boolean) => void;
|
|
}
|
|
|
|
const queue: ConsentRequest[] = [];
|
|
const listeners = new Set<() => void>();
|
|
|
|
function notify(): void {
|
|
for (const l of listeners) {
|
|
try { l(); } catch { /* ignore */ }
|
|
}
|
|
}
|
|
|
|
function uid(): string {
|
|
return Math.random().toString(36).slice(2) + Date.now().toString(36);
|
|
}
|
|
|
|
export function requestConsent(
|
|
pluginId: string,
|
|
pluginName: string,
|
|
permissions: Permission[],
|
|
targets: { httpOrigins?: string[]; apiPostPaths?: string[] } = {},
|
|
): Promise<boolean> {
|
|
return new Promise<boolean>((resolve) => {
|
|
queue.push({ id: uid(), pluginId, pluginName, permissions, ...targets, resolve });
|
|
notify();
|
|
});
|
|
}
|
|
|
|
export function head(): ConsentRequest | null {
|
|
return queue[0] ?? null;
|
|
}
|
|
|
|
export function resolveHead(granted: boolean): void {
|
|
const entry = queue.shift();
|
|
if (!entry) return;
|
|
try { entry.resolve(granted); } catch { /* ignore */ }
|
|
notify();
|
|
}
|
|
|
|
export function subscribe(listener: () => void): () => void {
|
|
listeners.add(listener);
|
|
return () => { listeners.delete(listener); };
|
|
}
|