mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 08:13:50 +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
58 lines
2.3 KiB
TypeScript
58 lines
2.3 KiB
TypeScript
'use client';
|
|
|
|
import { useTranslations } from 'next-intl';
|
|
import { AlertTriangle } from '@/components/icons';
|
|
import { cn } from '@/lib/utils';
|
|
import { HIGH_RISK_PERMISSIONS, permissionKey, visiblePermissions } from '@/lib/plugin-sandbox/permission-text';
|
|
|
|
interface PluginPermissionListProps {
|
|
permissions: readonly string[];
|
|
/** Sites the plugin may send requests to (manifest httpOrigins). */
|
|
httpOrigins?: readonly string[];
|
|
/** Server endpoints it may call with the user's login (apiPostPaths). */
|
|
apiPostPaths?: readonly string[];
|
|
compact?: boolean;
|
|
}
|
|
|
|
/** Plain-language list of what a plugin may do, high-risk items first. */
|
|
export function PluginPermissionList({ permissions, httpOrigins, apiPostPaths, compact }: PluginPermissionListProps) {
|
|
const t = useTranslations('plugins.permissions');
|
|
const perms = visiblePermissions(permissions);
|
|
if (perms.length === 0) {
|
|
return <p className="text-xs text-muted-foreground">{t('none')}</p>;
|
|
}
|
|
return (
|
|
<ul className={cn('space-y-1.5', compact && 'space-y-1')}>
|
|
{perms.map((perm) => {
|
|
const key = permissionKey(perm);
|
|
const risky = HIGH_RISK_PERMISSIONS.has(perm);
|
|
const targets = perm === 'http:fetch' ? httpOrigins : perm === 'http:post' ? apiPostPaths : undefined;
|
|
return (
|
|
<li
|
|
key={perm}
|
|
className={cn(
|
|
'rounded-md border px-3 py-2',
|
|
risky ? 'border-warning/40 bg-warning/10' : 'border-border bg-muted/40',
|
|
)}
|
|
>
|
|
<div className="flex items-start gap-2">
|
|
{risky && <AlertTriangle className="mt-0.5 h-3.5 w-3.5 flex-shrink-0 text-warning" aria-label={t('high_risk')} />}
|
|
<div className="min-w-0">
|
|
<div className="text-sm font-medium text-foreground">{t(`${key}.title`)}</div>
|
|
{!compact && <div className="text-xs text-muted-foreground">{t(`${key}.body`)}</div>}
|
|
{targets && targets.length > 0 && (
|
|
<ul className="mt-1 space-y-0.5">
|
|
{targets.map((target) => (
|
|
<li key={target} className="break-all font-mono text-[11px] text-foreground">{target}</li>
|
|
))}
|
|
</ul>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</li>
|
|
);
|
|
})}
|
|
</ul>
|
|
);
|
|
}
|