diff --git a/src/commands/plugin.ts b/src/commands/plugin.ts index af26f8b..63779fe 100644 --- a/src/commands/plugin.ts +++ b/src/commands/plugin.ts @@ -278,6 +278,61 @@ export function pluginCommand(): Command { }) ); + // ---- CSP apiOrigins management ---- + const csp = cmd.command('csp').description('Manage plugin CSP apiOrigins'); + + csp + .command('list') + .description('Show the union of all plugins\' apiOrigins (effective CSP connect-src extras)') + .action( + run(async (ctx) => { + const { origins } = await ctx.client.getCspOrigins(); + if (ctx.json) printJson(origins); + else if (!origins.length) process.stdout.write('(no apiOrigins declared by any plugin)\n'); + else origins.forEach((o) => process.stdout.write(`${o}\n`)); + }) + ); + + csp + .command('set ') + .description('Set the external API origins a plugin may fetch (bundle plugins included)') + .requiredOption('--origins ', 'JSON array of origins, e.g. \'["http://192.168.124.92:8001"]\'') + .option('--clear', 'Remove all origins (pass "[]" works too)') + .action( + run(async (ctx, args, o) => { + const opts = o as { origins: string; clear?: boolean }; + const pluginId = args[0] as string; + let origins: string[]; + try { + const parsed = JSON.parse(opts.origins) as unknown; + if (!Array.isArray(parsed) || parsed.some((x) => typeof x !== 'string')) { + throw new Error('not a string array'); + } + origins = parsed as string[]; + } catch { + process.stderr.write(`✗ --origins must be a JSON string array, got: ${opts.origins}\n`); + process.exitCode = 1; + return; + } + if (opts.clear) origins = []; + // updatePlugin requires name + positions (full-vo update): fetch current values. + const cur = await ctx.client.getPlugin(pluginId); + await ctx.client.updatePlugin(pluginId, { + name: cur.name, + positions: cur.positions, + description: cur.description, + detailDesc: cur.detailDesc, + url: cur.url, + logo: cur.logo, + helpUrl: cur.helpUrl, + apiOrigins: origins, + }); + process.stdout.write( + `✓ apiOrigins for ${pluginId} set to: ${JSON.stringify(origins)}\n` + ); + }) + ); + // Wire feature subcommands addDevCommands(cmd); addInstallCommands(cmd); diff --git a/src/lib/client.ts b/src/lib/client.ts index 3b59b5c..0805feb 100644 --- a/src/lib/client.ts +++ b/src/lib/client.ts @@ -271,6 +271,24 @@ export class Client { getPlugins() { return this.call('GET', '/api/plugin'); } + getPlugin(pluginId: string) { + return this.call<{ + id: string; + name: string; + positions: string[]; + description?: string; + detailDesc?: string; + url?: string; + logo?: string; + helpUrl?: string; + }>('GET', `/api/plugin/${encodeURIComponent(pluginId)}`); + } + updatePlugin(pluginId: string, body: Record) { + return this.call('PUT', `/api/plugin/${encodeURIComponent(pluginId)}`, body); + } + getCspOrigins() { + return this.call<{ origins: string[] }>('GET', '/api/plugin/csp-origins'); + } registerPlugin(body: Record) { return this.call('POST', '/api/plugin', body); }