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
35 lines
1.6 KiB
TypeScript
35 lines
1.6 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server';
|
|
import { requireAdminAuth } from '@/lib/admin/session';
|
|
import { logger } from '@/lib/logger';
|
|
import { readApprovalBundle } from '@/lib/admin/plugin-approvals';
|
|
import { readPluginPackage } from '@/lib/extension-package';
|
|
|
|
/**
|
|
* GET /api/admin/plugin-approvals/source?pluginId=…&bundleHash=…
|
|
*
|
|
* The manifest and code of a plugin a user asked to have approved, read
|
|
* from the ZIP they uploaded, so the admin decides on what will actually run.
|
|
*/
|
|
export async function GET(request: NextRequest) {
|
|
try {
|
|
const result = await requireAdminAuth(request);
|
|
if ('error' in result) return result.error;
|
|
|
|
const pluginId = request.nextUrl.searchParams.get('pluginId') ?? '';
|
|
const bundleHash = request.nextUrl.searchParams.get('bundleHash') ?? '';
|
|
if (!/^[a-z0-9][a-z0-9-]*[a-z0-9]$/.test(pluginId) || !/^[a-f0-9]{64}$/.test(bundleHash)) {
|
|
return NextResponse.json({ error: 'invalid pluginId or bundleHash' }, { status: 400 });
|
|
}
|
|
const zip = await readApprovalBundle(pluginId, bundleHash);
|
|
if (!zip) return NextResponse.json({ error: 'No package stored for this request' }, { status: 404 });
|
|
const pkg = await readPluginPackage(zip);
|
|
return NextResponse.json(
|
|
{ manifest: pkg.manifest, code: pkg.code, warnings: pkg.warnings },
|
|
{ headers: { 'Cache-Control': 'no-store' } },
|
|
);
|
|
} catch (err) {
|
|
logger.error('plugin-approvals source GET', { error: err instanceof Error ? err.message : String(err) });
|
|
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
|
|
}
|
|
}
|