58 lines
2.2 KiB
TypeScript
58 lines
2.2 KiB
TypeScript
|
|
import { NextResponse } from 'next/server';
|
||
|
|
|
||
|
|
import { withAuth, withPermission } from '@/lib/api/helpers';
|
||
|
|
import { createAuditLog } from '@/lib/audit';
|
||
|
|
import { errorResponse, NotFoundError } from '@/lib/errors';
|
||
|
|
import { registryFor } from '@/lib/settings/registry';
|
||
|
|
import { getSetting } from '@/lib/settings/resolver';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* POST /api/v1/admin/settings/:key/reveal
|
||
|
|
*
|
||
|
|
* Returns the decrypted cleartext for an encrypted / sensitive setting.
|
||
|
|
* Used by the eye-toggle on encrypted fields in the registry-driven admin
|
||
|
|
* form so the operator can verify what they saved earlier.
|
||
|
|
*
|
||
|
|
* Gated on `admin.manage_settings` (the same permission required to write
|
||
|
|
* the value — so this never widens an existing trust boundary). Every
|
||
|
|
* reveal is audit-logged with the request id so a super-admin can trace
|
||
|
|
* who looked at what and when.
|
||
|
|
*
|
||
|
|
* Refuses to reveal values resolved from `env` or `default` — those would
|
||
|
|
* leak server-process secrets via the API.
|
||
|
|
*/
|
||
|
|
export const POST = withAuth(
|
||
|
|
withPermission('admin', 'manage_settings', async (_req, ctx, params) => {
|
||
|
|
try {
|
||
|
|
const key = params.key!;
|
||
|
|
const entry = registryFor(key);
|
||
|
|
if (!entry) throw new NotFoundError(`Unknown setting: ${key}`);
|
||
|
|
if (!entry.encrypted && !entry.sensitive) {
|
||
|
|
// Non-sensitive values are already returned in the resolved-list
|
||
|
|
// endpoint, so a dedicated reveal isn't needed (and could be
|
||
|
|
// misused to bypass observability).
|
||
|
|
return NextResponse.json({ data: { revealed: false, value: null } }, { status: 200 });
|
||
|
|
}
|
||
|
|
|
||
|
|
// Resolve through the standard chain so the user sees exactly what
|
||
|
|
// the runtime would. The resolver decrypts on the way out.
|
||
|
|
const value = await getSetting<string>(key, ctx.portId);
|
||
|
|
|
||
|
|
void createAuditLog({
|
||
|
|
userId: ctx.userId,
|
||
|
|
portId: ctx.portId,
|
||
|
|
action: 'view',
|
||
|
|
entityType: 'setting',
|
||
|
|
entityId: key,
|
||
|
|
metadata: { settingKey: key, op: 'reveal' },
|
||
|
|
ipAddress: ctx.ipAddress,
|
||
|
|
userAgent: ctx.userAgent,
|
||
|
|
});
|
||
|
|
|
||
|
|
return NextResponse.json({ data: { revealed: true, value: value ?? null } });
|
||
|
|
} catch (error) {
|
||
|
|
return errorResponse(error);
|
||
|
|
}
|
||
|
|
}),
|
||
|
|
);
|