validate secret provider config before save
This commit is contained in:
8 files changed
+106
-31
No files matched your search
@@ -3,9 +3,17 @@ import type { RequestHandler } from './$types';
|
||||
import { getSecretProviderById } from '$lib/server/db';
|
||||
import { authorize } from '$lib/server/authorize';
|
||||
import { testProviderConnection } from '$lib/server/secretproviders';
|
||||
import { mergeProviderConfigForWrite } from '$lib/server/secretproviders/shared';
|
||||
import type { SecretProviderConfig } from '$lib/server/secretproviders/shared';
|
||||
|
||||
/** Test a stored provider using its persisted (decrypted) config. */
|
||||
export const POST: RequestHandler = async ({ params, cookies }) => {
|
||||
/**
|
||||
* Test a stored provider. With no body it tests the persisted config. With an optional
|
||||
* `{ config }` override (the edit form's typed non-secret fields) it tests exactly what a
|
||||
* Save would persist: the incoming config merged over the stored one, with a blank token
|
||||
* falling back to the stored secret. The secret is NEVER supplied by the client - it comes
|
||||
* from storage server-side - so this stays a `secrets:view` operation.
|
||||
*/
|
||||
export const POST: RequestHandler = async ({ params, cookies, request }) => {
|
||||
const auth = await authorize(cookies);
|
||||
if (auth.authEnabled && !(await auth.can('secrets', 'view'))) {
|
||||
return json({ error: 'Permission denied' }, { status: 403 });
|
||||
@@ -21,7 +29,20 @@ export const POST: RequestHandler = async ({ params, cookies }) => {
|
||||
return json({ error: 'Secret provider not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
const result = await testProviderConnection(provider.type, provider.config);
|
||||
let configToTest: SecretProviderConfig = provider.config;
|
||||
try {
|
||||
const body = await request.json();
|
||||
if (body && typeof body.config === 'object' && body.config !== null) {
|
||||
configToTest = mergeProviderConfigForWrite(
|
||||
body.config as Record<string, unknown>,
|
||||
provider.config as unknown as Record<string, unknown>
|
||||
) as unknown as SecretProviderConfig;
|
||||
}
|
||||
} catch {
|
||||
// No/invalid body: test the stored config as-is.
|
||||
}
|
||||
|
||||
const result = await testProviderConnection(provider.type, configToTest);
|
||||
if (!result.ok) {
|
||||
return json({ ok: false, error: result.error }, { status: 200 });
|
||||
}
|
||||
|
||||
@@ -212,23 +212,24 @@
|
||||
formError = '';
|
||||
try {
|
||||
const config = collectConfig();
|
||||
// On edit the token is server-side (blank field = keep stored), so if the
|
||||
// user hasn't typed a new one, test the STORED config; the pre-filled
|
||||
// non-secret fields aren't enough to re-test from the client.
|
||||
const hasNewSecret = fields.some((f) => f.type === 'password' && (config[f.key] ?? '') !== '');
|
||||
const missing = missingRequired(config, isEditing);
|
||||
if (missing) {
|
||||
formError = missing;
|
||||
return;
|
||||
}
|
||||
|
||||
let response: Response;
|
||||
if (isEditing && !hasNewSecret) {
|
||||
// Test the stored (server-side) config.
|
||||
if (isEditing) {
|
||||
// Test EXACTLY what a Save would persist: the typed non-secret fields, merged
|
||||
// server-side over the stored config (a blank token keeps the stored one). This
|
||||
// makes an edited address/mount/namespace actually get tested - not the old
|
||||
// stored config.
|
||||
response = await fetch(`/api/secret-providers/${provider!.id}/test`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ config }),
|
||||
});
|
||||
} else {
|
||||
const missing = missingRequired(config, isEditing);
|
||||
if (missing) {
|
||||
formError = missing;
|
||||
return;
|
||||
}
|
||||
response = await fetch('/api/secret-providers/test', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
|
||||
Reference in new issue
Block a user