fix: show the app name and plugin name in a marketplace plugin's permissions

This commit is contained in:
Linus Rath
2026-11-02 23:33:57 +01:00
parent f6e0b760d5
commit 27e84b3c93
3 changed files with 25 additions and 4 deletions
@@ -693,7 +693,7 @@ export function MarketplaceDetailPage({ slug }: { slug: string }) {
title="Permissions"
description={manifestPerms.length === 0
? 'This plugin requests no permissions.'
: `What ${preview ? `v${preview.version}` : 'the plugin'} may do in each user's mailbox.`}
: `What ${ext.name}${preview ? ` ${preview.version}` : ''} may do in each user's mailbox.`}
>
{manifestPerms.length > 0 && (
<ul className="space-y-1.5">
+5 -1
View File
@@ -24,6 +24,8 @@ import {
type DirectoryVersion,
} from '@/lib/admin/marketplace-directory';
import { MAX_PLUGIN_SIZE, MAX_THEME_SIZE } from '@/lib/plugin-types';
import { productName } from '@/lib/product-name';
import { withAppName } from '@/lib/settings-search';
const MAX_PREVIEW_SOURCE_LEN = 100_000;
@@ -173,6 +175,7 @@ export async function GET(
const installed = await installedExtensions();
const state = installedStateOf(installed, slug, type);
const history = await historyFor(type, slug);
const appName = productName(configManager.get<string>('appName'));
return NextResponse.json(
{
@@ -185,7 +188,8 @@ export async function GET(
screenshots: detail.screenshots ?? [],
themePreviews: detail.themePreviews ?? [],
advisories: detail.advisories ?? [],
permissionDetails: detail.permissions ?? [],
// The directory ships the webmail's permission texts unfilled.
permissionDetails: (detail.permissions ?? []).map((p) => withAppName(p, appName) as typeof p),
source: detail.source,
createdAt: detail.createdAt ?? null,
updatedAt: detail.updatedAt ?? null,
@@ -5,6 +5,8 @@ import { afterEach, describe, expect, it, vi } from 'vitest';
import { NextRequest } from 'next/server';
import JSZip from 'jszip';
const appName = vi.hoisted(() => ({ value: 'Webmail' }));
vi.mock('@/lib/admin/session', () => ({
requireAdminAuth: vi.fn(async () => ({ session: { sub: 'admin' } })),
getClientIP: () => '127.0.0.1',
@@ -12,7 +14,7 @@ vi.mock('@/lib/admin/session', () => ({
vi.mock('@/lib/admin/config-manager', () => ({
configManager: {
ensureLoaded: vi.fn(async () => {}),
get: vi.fn((key: string) => (key === 'extensionDirectoryUrl' ? 'https://extensions.test' : undefined)),
get: vi.fn((key: string) => (key === 'extensionDirectoryUrl' ? 'https://extensions.test' : key === 'appName' ? appName.value : undefined)),
},
}));
vi.mock('@/lib/admin/plugin-registry', () => ({
@@ -54,7 +56,9 @@ function directory() {
const u = new URL(String(url));
calls.push(u.pathname + u.search);
if (u.pathname === '/api/v2/extensions/pretty-dates') {
return Response.json({ data: { ...summary, latest: versions[0], newest: versions[0], longDescription: 'long', versions: versions.slice(0, 2), screenshots: [], themePreviews: [], advisories: [], permissions: [] } });
return Response.json({ data: { ...summary, latest: versions[0], newest: versions[0], longDescription: 'long', versions: versions.slice(0, 2), screenshots: [], themePreviews: [], advisories: [], permissions: [
{ id: 'http:proxy', title: 'Use accounts you connect', body: "Send requests through {appName}'s server.", highRisk: true },
] } });
}
if (u.pathname === '/api/v2/extensions/pretty-dates/versions') return Response.json({ data: versions });
const m = /\/versions\/([^/]+)\/bundle$/.exec(u.pathname);
@@ -105,6 +109,19 @@ describe('GET /api/admin/marketplace/[slug]', () => {
expect(body.preview.version).toBe('1.2.0');
});
it('fills the app name into the permission texts the directory sends', async () => {
directory();
appName.value = 'Acme Mail';
let body = await (await preview()).json();
expect(body.extension.permissionDetails).toEqual([
{ id: 'http:proxy', title: 'Use accounts you connect', body: "Send requests through Acme Mail's server.", highRisk: true },
]);
appName.value = 'Webmail';
body = await (await preview()).json();
expect(body.extension.permissionDetails[0].body).toBe("Send requests through Bulwark's server.");
});
it('refuses a malformed version instead of forwarding it', async () => {
directory();
expect((await preview('?version=..%2F..%2Fetc')).status).toBe(400);