cinema-studio-pro/backend/runtime_config.php
Vadym Samoilenko 22a5ce83af feat: admin users can rotate Kling credentials in real-time via UI
- 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>
2026-04-29 16:45:27 +01:00

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'],
];
}