Remove hostname check — DEV_MODE env var is sufficient as explicit opt-in. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
198 lines
5.3 KiB
PHP
198 lines
5.3 KiB
PHP
<?php
|
|
/**
|
|
* API Authentication Module
|
|
*
|
|
* Provides simple API key authentication for REST API endpoints
|
|
* Supports multiple authentication methods:
|
|
* - Authorization: Bearer <token>
|
|
* - X-API-Key: <key>
|
|
* - Query parameter: ?api_key=<key> (dev only)
|
|
*/
|
|
|
|
/**
|
|
* Check if request is authenticated
|
|
*
|
|
* @return bool True if authenticated, false otherwise
|
|
*/
|
|
function authenticate() {
|
|
// Development mode: allow localhost without auth
|
|
if (isDevelopmentMode()) {
|
|
return true;
|
|
}
|
|
|
|
$api_key = extractApiKey();
|
|
|
|
if (!$api_key) {
|
|
return false;
|
|
}
|
|
|
|
// Validate against configured keys
|
|
$valid_keys = getValidApiKeys();
|
|
|
|
return in_array($api_key, $valid_keys, true);
|
|
}
|
|
|
|
/**
|
|
* Check if running in development mode (localhost)
|
|
*
|
|
* @return bool True if development mode
|
|
*/
|
|
function isDevelopmentMode() {
|
|
// DEV_MODE env var explicitly bypasses auth (set in Apache/env config)
|
|
$dev_mode = getenv('DEV_MODE');
|
|
return ($dev_mode === 'true' || $dev_mode === '1');
|
|
}
|
|
|
|
/**
|
|
* Extract API key from request
|
|
*
|
|
* Checks multiple sources in order of security:
|
|
* 1. Authorization: Bearer header
|
|
* 2. X-API-Key header
|
|
* 3. Query parameter (least secure, for dev only)
|
|
*
|
|
* @return string|null API key or null if not found
|
|
*/
|
|
function extractApiKey() {
|
|
// Check Authorization: Bearer header
|
|
if (isset($_SERVER['HTTP_AUTHORIZATION'])) {
|
|
if (preg_match('/Bearer\s+(.*)$/i', $_SERVER['HTTP_AUTHORIZATION'], $matches)) {
|
|
return trim($matches[1]);
|
|
}
|
|
}
|
|
|
|
// Check X-API-Key header
|
|
if (isset($_SERVER['HTTP_X_API_KEY'])) {
|
|
return trim($_SERVER['HTTP_X_API_KEY']);
|
|
}
|
|
|
|
// Check query parameter (least secure - dev only)
|
|
if (isDevelopmentMode() && isset($_GET['api_key'])) {
|
|
return trim($_GET['api_key']);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Get list of valid API keys
|
|
*
|
|
* Loads keys from:
|
|
* 1. Environment variable API_KEY
|
|
* 2. .api_keys file (one key per line)
|
|
* 3. Default dev key (for development only)
|
|
*
|
|
* @return array List of valid API keys
|
|
*/
|
|
function getValidApiKeys() {
|
|
$keys = [];
|
|
|
|
// Load from environment variable
|
|
$env_key = getenv('API_KEY');
|
|
if ($env_key) {
|
|
$keys[] = $env_key;
|
|
}
|
|
|
|
// Load from .api_keys file
|
|
$config_file = __DIR__ . '/.api_keys';
|
|
if (file_exists($config_file)) {
|
|
$file_keys = file($config_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
|
if ($file_keys) {
|
|
// Filter out comments and empty lines
|
|
$file_keys = array_filter($file_keys, function($line) {
|
|
$line = trim($line);
|
|
return $line && substr($line, 0, 1) !== '#';
|
|
});
|
|
$keys = array_merge($keys, array_values($file_keys));
|
|
}
|
|
}
|
|
|
|
// Fallback to dev key only in development mode
|
|
if (empty($keys) && isDevelopmentMode()) {
|
|
error_log("WARNING: Using default dev API key. Configure proper API keys for production!");
|
|
$keys[] = 'dev_key_12345';
|
|
}
|
|
|
|
return array_unique($keys);
|
|
}
|
|
|
|
/**
|
|
* Send error response and exit
|
|
*
|
|
* @param string $message Error message
|
|
* @param int $status_code HTTP status code
|
|
*/
|
|
function sendUnauthorizedResponse($message = "Unauthorized", $status_code = 401) {
|
|
http_response_code($status_code);
|
|
header('Content-Type: application/json');
|
|
header('WWW-Authenticate: Bearer realm="API"');
|
|
|
|
echo json_encode([
|
|
'success' => false,
|
|
'error' => $message,
|
|
'status' => $status_code
|
|
]);
|
|
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Require authentication or send error
|
|
*
|
|
* Call this at the beginning of protected endpoints
|
|
*/
|
|
function requireAuth() {
|
|
if (!authenticate()) {
|
|
sendUnauthorizedResponse("Valid API key required");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generate a new random API key
|
|
*
|
|
* @return string 64-character hex API key
|
|
*/
|
|
function generateApiKey() {
|
|
return bin2hex(random_bytes(32));
|
|
}
|
|
|
|
// Example usage (for testing):
|
|
if (basename(__FILE__) == basename($_SERVER['SCRIPT_FILENAME'])) {
|
|
header('Content-Type: text/plain');
|
|
echo "PDF Accessibility Checker - Authentication Module\n";
|
|
echo "=================================================\n\n";
|
|
|
|
if (isset($_GET['generate'])) {
|
|
echo "New API Key:\n";
|
|
echo generateApiKey() . "\n\n";
|
|
echo "Add this to your .api_keys file or API_KEY environment variable.\n";
|
|
} else if (isset($_GET['test'])) {
|
|
echo "Testing authentication...\n\n";
|
|
|
|
$api_key = extractApiKey();
|
|
if ($api_key) {
|
|
echo "API Key found: " . substr($api_key, 0, 8) . "...\n";
|
|
|
|
if (authenticate()) {
|
|
echo "✅ Authentication successful!\n";
|
|
} else {
|
|
echo "❌ Authentication failed - invalid key\n";
|
|
}
|
|
} else {
|
|
echo "❌ No API key provided\n";
|
|
echo "\nTry:\n";
|
|
echo " - Add header: X-API-Key: <your-key>\n";
|
|
echo " - Or query param: ?api_key=<your-key>&test=1\n";
|
|
}
|
|
|
|
echo "\nValid keys configured: " . count(getValidApiKeys()) . "\n";
|
|
} else {
|
|
echo "Available actions:\n";
|
|
echo " ?generate - Generate new API key\n";
|
|
echo " ?test - Test authentication\n";
|
|
echo "\nExample:\n";
|
|
echo " php auth.php?generate\n";
|
|
echo " curl -H 'X-API-Key: your-key' http://localhost:8000/auth.php?test\n";
|
|
}
|
|
}
|
|
?>
|