- Add runtime_config.php: credential store backed by runtime_config.json (gitignored). Falls back to .env values so existing envs need no migration. - Add admin_api.php: status / test_kling / update_kling endpoints gated behind ADMIN_EMAILS allowlist. Accepts Bearer idToken when SSO enabled; uses mock dev@localhost when SSO disabled. - config.php: replace KLING_ACCESS_KEY/SECRET_KEY defines with ADMIN_EMAILS - kling_api.php: read credentials via getKlingCredentials() on every request so rotations take effect immediately without a server restart - All .env templates: add ADMIN_EMAILS= (dev@localhost populated in .env.local) - AdminSettings.jsx: modal with masked status, Test Connection, Save Credentials - AppContent.jsx: admin status check on mount; Settings gear shown to admins - Fix production URL in .env.production/.env.example (optical-prod.oliver.solutions) - .gitignore: exclude backend/runtime_config.json Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
83 lines
2.7 KiB
PHP
83 lines
2.7 KiB
PHP
<?php
|
|
/**
|
|
* Runtime credential store — Kling credential rotation
|
|
*
|
|
* Reads backend/runtime_config.json (written via admin panel) and falls back
|
|
* to .env values so existing environments keep working without any migration.
|
|
*/
|
|
|
|
define('RUNTIME_CONFIG_PATH', __DIR__ . '/runtime_config.json');
|
|
|
|
/**
|
|
* Returns current Kling credentials. Checks runtime JSON first, then .env.
|
|
*/
|
|
function getKlingCredentials(): array {
|
|
if (file_exists(RUNTIME_CONFIG_PATH)) {
|
|
$data = json_decode(file_get_contents(RUNTIME_CONFIG_PATH), true);
|
|
if (!empty($data['kling']['access_key']) && !empty($data['kling']['secret_key'])) {
|
|
return [
|
|
'access_key' => $data['kling']['access_key'],
|
|
'secret_key' => $data['kling']['secret_key'],
|
|
'source' => 'runtime',
|
|
'updated_at' => $data['kling']['updated_at'] ?? null,
|
|
'updated_by' => $data['kling']['updated_by'] ?? null,
|
|
];
|
|
}
|
|
}
|
|
return [
|
|
'access_key' => getenv('KLING_ACCESS_KEY') ?: '',
|
|
'secret_key' => getenv('KLING_SECRET_KEY') ?: '',
|
|
'source' => 'env',
|
|
'updated_at' => null,
|
|
'updated_by' => null,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Writes new Kling credentials atomically (tempfile + rename).
|
|
*/
|
|
function setKlingCredentials(string $accessKey, string $secretKey, string $updatedBy): bool {
|
|
$data = [];
|
|
if (file_exists(RUNTIME_CONFIG_PATH)) {
|
|
$existing = json_decode(file_get_contents(RUNTIME_CONFIG_PATH), true);
|
|
if (is_array($existing)) {
|
|
$data = $existing;
|
|
}
|
|
}
|
|
$data['kling'] = [
|
|
'access_key' => $accessKey,
|
|
'secret_key' => $secretKey,
|
|
'updated_at' => gmdate('Y-m-d\TH:i:s\Z'),
|
|
'updated_by' => $updatedBy,
|
|
];
|
|
|
|
$tmpPath = RUNTIME_CONFIG_PATH . '.tmp.' . getmypid();
|
|
if (file_put_contents($tmpPath, json_encode($data, JSON_PRETTY_PRINT)) === false) {
|
|
return false;
|
|
}
|
|
return rename($tmpPath, RUNTIME_CONFIG_PATH);
|
|
}
|
|
|
|
/**
|
|
* Returns masked status suitable for the admin UI (never exposes raw secrets).
|
|
*/
|
|
function getKlingStatus(): array {
|
|
$creds = getKlingCredentials();
|
|
$key = $creds['access_key'];
|
|
|
|
if (strlen($key) > 8) {
|
|
$masked = substr($key, 0, 4) . '…' . substr($key, -4);
|
|
} elseif ($key !== '') {
|
|
$masked = str_repeat('*', strlen($key));
|
|
} else {
|
|
$masked = null;
|
|
}
|
|
|
|
return [
|
|
'access_key_masked' => $masked,
|
|
'secret_set' => $creds['secret_key'] !== '',
|
|
'source' => $creds['source'],
|
|
'updated_at' => $creds['updated_at'],
|
|
'updated_by' => $creds['updated_by'],
|
|
];
|
|
}
|