Make API key and CORS settings per-company

- saatavuus endpoint finds company by API key, searches only that company
- config/config_update/generate_api_key now use company config
- API tab shows active company name
- Each company has own api_key and cors_origins in their config.json

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-10 11:39:35 +02:00
parent 92d52f34ad
commit 44e401f4d3
3 changed files with 65 additions and 35 deletions

93
api.php
View File

@@ -890,9 +890,35 @@ switch ($action) {
// ---------- SAATAVUUS (julkinen API) ---------- // ---------- SAATAVUUS (julkinen API) ----------
case 'saatavuus': case 'saatavuus':
// CORS - salli cuitunet.fi $providedKey = $_GET['key'] ?? ($_SERVER['HTTP_X_API_KEY'] ?? '');
$config = loadConfig(); if (empty($providedKey)) {
$allowedOrigins = $config['cors_origins'] ?? ['https://cuitunet.fi', 'https://www.cuitunet.fi']; http_response_code(403);
echo json_encode(['error' => 'API-avain puuttuu']);
break;
}
// Etsi yritys jonka API-avain täsmää
$matchedCompany = null;
$allCompanies = loadCompanies();
foreach ($allCompanies as $comp) {
$confFile = DATA_DIR . '/companies/' . $comp['id'] . '/config.json';
if (!file_exists($confFile)) continue;
$compConf = json_decode(file_get_contents($confFile), true) ?: [];
if (!empty($compConf['api_key']) && $compConf['api_key'] === $providedKey) {
$matchedCompany = $comp;
$matchedConfig = $compConf;
break;
}
}
if (!$matchedCompany) {
http_response_code(403);
echo json_encode(['error' => 'Virheellinen API-avain']);
break;
}
// CORS - yrityskohtaiset originit
$allowedOrigins = $matchedConfig['cors_origins'] ?? [];
$origin = $_SERVER['HTTP_ORIGIN'] ?? ''; $origin = $_SERVER['HTTP_ORIGIN'] ?? '';
if (in_array($origin, $allowedOrigins)) { if (in_array($origin, $allowedOrigins)) {
header("Access-Control-Allow-Origin: $origin"); header("Access-Control-Allow-Origin: $origin");
@@ -901,16 +927,7 @@ switch ($action) {
} }
if ($method === 'OPTIONS') { http_response_code(204); break; } if ($method === 'OPTIONS') { http_response_code(204); break; }
// API-avain tarkistus // Parametrit
$apiKey = $config['api_key'] ?? '';
$providedKey = $_GET['key'] ?? ($_SERVER['HTTP_X_API_KEY'] ?? '');
if (empty($apiKey) || $providedKey !== $apiKey) {
http_response_code(403);
echo json_encode(['error' => 'Virheellinen API-avain']);
break;
}
// Parametrit: osoite (kadunnimi + numero), postinumero, kaupunki
$queryOsoite = normalizeAddress($_GET['osoite'] ?? ''); $queryOsoite = normalizeAddress($_GET['osoite'] ?? '');
$queryPostinumero = trim($_GET['postinumero'] ?? ''); $queryPostinumero = trim($_GET['postinumero'] ?? '');
$queryKaupunki = strtolower(trim($_GET['kaupunki'] ?? '')); $queryKaupunki = strtolower(trim($_GET['kaupunki'] ?? ''));
@@ -921,13 +938,11 @@ switch ($action) {
break; break;
} }
// Hae kaikista yrityksistä // Hae VAIN tämän yrityksen asiakkaista
$allCompanies = loadCompanies(); $compDir = DATA_DIR . '/companies/' . $matchedCompany['id'];
$custFile = $compDir . '/customers.json';
$found = false; $found = false;
foreach ($allCompanies as $comp) { if (file_exists($custFile)) {
$compDir = DATA_DIR . '/companies/' . $comp['id'];
$custFile = $compDir . '/customers.json';
if (!file_exists($custFile)) continue;
$customers = json_decode(file_get_contents($custFile), true) ?: []; $customers = json_decode(file_get_contents($custFile), true) ?: [];
foreach ($customers as $c) { foreach ($customers as $c) {
foreach ($c['liittymat'] ?? [] as $l) { foreach ($c['liittymat'] ?? [] as $l) {
@@ -938,7 +953,7 @@ switch ($action) {
if (!empty($addr) && !empty($queryOsoite)) { if (!empty($addr) && !empty($queryOsoite)) {
if (strpos($addr, $queryOsoite) !== false || strpos($queryOsoite, $addr) !== false) { if (strpos($addr, $queryOsoite) !== false || strpos($queryOsoite, $addr) !== false) {
$found = true; $found = true;
break 3; break 2;
} }
} }
} }
@@ -946,39 +961,51 @@ switch ($action) {
} }
} }
// Palauta VAIN true/false - ei osoitteita, nopeuksia tai muuta dataa
echo json_encode(['saatavilla' => $found]); echo json_encode(['saatavilla' => $found]);
break; break;
// ---------- CONFIG (admin) ---------- // ---------- CONFIG (admin, yrityskohtainen) ----------
case 'config': case 'config':
requireAdmin(); requireAdmin();
echo json_encode(loadConfig()); requireCompany();
$compConf = loadCompanyConfig();
echo json_encode([
'api_key' => $compConf['api_key'] ?? '',
'cors_origins' => $compConf['cors_origins'] ?? [],
]);
break; break;
case 'config_update': case 'config_update':
requireAdmin(); requireAdmin();
requireCompany();
if ($method !== 'POST') break; if ($method !== 'POST') break;
$input = json_decode(file_get_contents('php://input'), true); $input = json_decode(file_get_contents('php://input'), true);
$config = loadConfig(); $compConf = loadCompanyConfig();
if (isset($input['api_key'])) $config['api_key'] = trim($input['api_key']); if (isset($input['api_key'])) $compConf['api_key'] = trim($input['api_key']);
if (isset($input['cors_origins'])) { if (isset($input['cors_origins'])) {
$origins = array_filter(array_map('trim', explode("\n", $input['cors_origins']))); $origins = array_filter(array_map('trim', explode("\n", $input['cors_origins'])));
$config['cors_origins'] = array_values($origins); $compConf['cors_origins'] = array_values($origins);
} }
saveConfig($config); saveCompanyConfig($compConf);
addLog('config_update', '', '', 'Päivitti asetukset'); addLog('config_update', '', '', 'Päivitti API-asetukset');
echo json_encode($config); echo json_encode([
'api_key' => $compConf['api_key'] ?? '',
'cors_origins' => $compConf['cors_origins'] ?? [],
]);
break; break;
case 'generate_api_key': case 'generate_api_key':
requireAdmin(); requireAdmin();
requireCompany();
if ($method !== 'POST') break; if ($method !== 'POST') break;
$config = loadConfig(); $compConf = loadCompanyConfig();
$config['api_key'] = bin2hex(random_bytes(16)); $compConf['api_key'] = bin2hex(random_bytes(16));
saveConfig($config); saveCompanyConfig($compConf);
addLog('config_update', '', '', 'Generoi uuden API-avaimen'); addLog('config_update', '', '', 'Generoi uuden API-avaimen');
echo json_encode($config); echo json_encode([
'api_key' => $compConf['api_key'] ?? '',
'cors_origins' => $compConf['cors_origins'] ?? [],
]);
break; break;
// ---------- CAPTCHA ---------- // ---------- CAPTCHA ----------

View File

@@ -437,7 +437,7 @@
<div class="tab-content" id="tab-content-settings"> <div class="tab-content" id="tab-content-settings">
<div class="main-container"> <div class="main-container">
<div class="table-card" style="padding:1.5rem;"> <div class="table-card" style="padding:1.5rem;">
<h3 style="color:#0f3460;margin-bottom:1rem;border-bottom:2px solid #f0f2f5;padding-bottom:0.5rem;">Saatavuus-API</h3> <h3 style="color:#0f3460;margin-bottom:1rem;border-bottom:2px solid #f0f2f5;padding-bottom:0.5rem;"><span id="api-company-name"></span>Saatavuus-API</h3>
<p style="color:#666;font-size:0.85rem;margin-bottom:1rem;">Julkinen API jolla cuitunet.fi voi tarkistaa kuituverkon saatavuuden osoitteessa. Palauttaa vain osoite + nopeus - ei asiakastietoja.</p> <p style="color:#666;font-size:0.85rem;margin-bottom:1rem;">Julkinen API jolla cuitunet.fi voi tarkistaa kuituverkon saatavuuden osoitteessa. Palauttaa vain osoite + nopeus - ei asiakastietoja.</p>
<div class="form-grid" style="max-width:600px;"> <div class="form-grid" style="max-width:600px;">
<div class="form-group full-width"> <div class="form-group full-width">

View File

@@ -1643,7 +1643,10 @@ async function loadSettings() {
try { try {
const config = await apiCall('config'); const config = await apiCall('config');
document.getElementById('settings-api-key').value = config.api_key || ''; document.getElementById('settings-api-key').value = config.api_key || '';
document.getElementById('settings-cors').value = (config.cors_origins || ['https://cuitunet.fi', 'https://www.cuitunet.fi']).join('\n'); document.getElementById('settings-cors').value = (config.cors_origins || []).join('\n');
// Näytä yrityksen nimi API-otsikossa
const apiTitle = document.getElementById('api-company-name');
if (apiTitle && currentCompany) apiTitle.textContent = currentCompany.nimi + ' — ';
const key = config.api_key || 'AVAIN'; const key = config.api_key || 'AVAIN';
document.getElementById('api-example-url').textContent = `api.php?action=saatavuus&key=${key}&osoite=Kauppakatu+5&postinumero=20100&kaupunki=Turku`; document.getElementById('api-example-url').textContent = `api.php?action=saatavuus&key=${key}&osoite=Kauppakatu+5&postinumero=20100&kaupunki=Turku`;
} catch (e) { console.error(e); } } catch (e) { console.error(e); }