Files
webgis/poverty-mapping/src/config/auth.php
T
2026-06-11 17:43:01 +07:00

272 lines
9.7 KiB
PHP

<?php
include_once __DIR__ . '/config.php';
if(session_status() === PHP_SESSION_NONE) session_start();
function start_session_if_needed(){ if(session_status() === PHP_SESSION_NONE) session_start(); }
function auth_headers(){
if(function_exists('getallheaders')){
$headers = getallheaders();
if(is_array($headers)) return $headers;
}
$headers = [];
foreach($_SERVER as $key => $value){
if(strpos($key, 'HTTP_') === 0){
$name = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($key, 5)))));
$headers[$name] = $value;
}
}
return $headers;
}
function auth_header_value($name){
$headers = auth_headers();
foreach($headers as $key => $value){
if(strtolower($key) === strtolower($name)) return $value;
}
return null;
}
function auth_json_response($statusCode, $payload){
http_response_code($statusCode);
header('Content-Type: application/json; charset=utf-8');
echo json_encode($payload);
exit;
}
function base64url_encode($data){ return rtrim(strtr(base64_encode($data), '+/', '-_'), '='); }
function base64url_decode($data){ return base64_decode(strtr($data, '-_', '+/')); }
function jwt_issue($claims, $ttlSeconds = 86400){
$now = time();
$payload = array_merge([
'iss' => 'webgis',
'iat' => $now,
'exp' => $now + max(60, intval($ttlSeconds)),
], $claims);
$header = ['alg' => 'HS256', 'typ' => 'JWT'];
$encodedHeader = base64url_encode(json_encode($header));
$encodedPayload = base64url_encode(json_encode($payload));
$signature = hash_hmac('sha256', $encodedHeader . '.' . $encodedPayload, JWT_SECRET, true);
return $encodedHeader . '.' . $encodedPayload . '.' . base64url_encode($signature);
}
function jwt_verify($token){
if(!$token || strpos($token, '.') === false) return null;
$parts = explode('.', $token);
if(count($parts) !== 3) return null;
list($h, $p, $s) = $parts;
$header = json_decode(base64url_decode($h), true);
$payload = json_decode(base64url_decode($p), true);
if(!$header || !$payload) return null;
if(!isset($header['alg']) || $header['alg'] !== 'HS256') return null;
$expected = base64url_encode(hash_hmac('sha256', $h . '.' . $p, JWT_SECRET, true));
if(!hash_equals($expected, $s)) return null;
if(isset($payload['exp']) && time() > intval($payload['exp'])) return null;
return $payload;
}
function auth_basic_credentials(){
$header = auth_header_value('Authorization');
if(!$header && isset($_SERVER['PHP_AUTH_USER'])){
return [$_SERVER['PHP_AUTH_USER'], isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : ''];
}
if(!$header || stripos($header, 'Basic ') !== 0) return [null, null];
$decoded = base64_decode(substr($header, 6));
if($decoded === false || strpos($decoded, ':') === false) return [null, null];
list($user, $pass) = explode(':', $decoded, 2);
return [$user, $pass];
}
function auth_internal_cipher(){
return 'aes-256-gcm';
}
function auth_internal_key(){
return hash('sha256', INTERNAL_AUTH_KEY, true);
}
function internal_auth_issue($claims, $ttlSeconds = 300){
$payload = array_merge([
'iss' => 'webgis-internal',
'iat' => time(),
'exp' => time() + max(30, intval($ttlSeconds)),
'nonce' => bin2hex(random_bytes(8))
], $claims);
$plaintext = json_encode($payload);
$iv = random_bytes(12);
$tag = '';
$ciphertext = openssl_encrypt($plaintext, auth_internal_cipher(), auth_internal_key(), OPENSSL_RAW_DATA, $iv, $tag);
if($ciphertext === false) return null;
return base64url_encode($iv . $tag . $ciphertext);
}
function internal_auth_verify($token){
if(!$token) return null;
$raw = base64url_decode($token);
if($raw === false || strlen($raw) < 28) return null;
$iv = substr($raw, 0, 12);
$tag = substr($raw, 12, 16);
$ciphertext = substr($raw, 28);
$plaintext = openssl_decrypt($ciphertext, auth_internal_cipher(), auth_internal_key(), OPENSSL_RAW_DATA, $iv, $tag);
if($plaintext === false) return null;
$payload = json_decode($plaintext, true);
if(!$payload) return null;
if(isset($payload['exp']) && time() > intval($payload['exp'])) return null;
return $payload;
}
function get_auth_context(){
start_session_if_needed();
global $conn;
$context = ['user' => null, 'mode' => null];
$internalToken = auth_header_value('X-Internal-Auth');
if($internalToken){
$internal = internal_auth_verify($internalToken);
if($internal){
$context['mode'] = 'internal';
$context['user'] = [
'id' => isset($internal['sub']) ? intval($internal['sub']) : 0,
'email' => isset($internal['email']) ? $internal['email'] : 'internal',
'name' => isset($internal['name']) ? $internal['name'] : 'Internal',
'organization' => isset($internal['organization']) ? $internal['organization'] : 'Internal',
'role' => isset($internal['role']) ? $internal['role'] : 'admin',
'status' => 'active'
];
return $context;
}
}
$bearer = auth_header_value('Authorization');
if($bearer && stripos($bearer, 'Bearer ') === 0){
$payload = jwt_verify(trim(substr($bearer, 7)));
if($payload && isset($payload['sub'])){
$uid = intval($payload['sub']);
if($uid > 0 && $conn){
$st = $conn->prepare('SELECT id, email, name, organization, role, status FROM users WHERE id = ? LIMIT 1');
if($st){
$st->bind_param('i', $uid);
$st->execute();
$r = $st->get_result();
$row = $r ? $r->fetch_assoc() : null;
$st->close();
if($row){
$context['mode'] = 'jwt';
$context['user'] = $row;
return $context;
}
}
}
}
}
list($basicUser, $basicPass) = auth_basic_credentials();
if($basicUser !== null){
if($basicUser === BASIC_API_USER && hash_equals(BASIC_API_PASS, (string)$basicPass)){
$context['mode'] = 'basic';
$context['user'] = ['id' => 0, 'email' => 'api', 'name' => 'API', 'organization' => 'API', 'role' => 'admin', 'status' => 'active'];
return $context;
}
if($conn){
$st = $conn->prepare('SELECT id, email, name, organization, role, status, password_hash FROM users WHERE email = ? LIMIT 1');
if($st){
$st->bind_param('s', $basicUser);
$st->execute();
$r = $st->get_result();
$row = $r ? $r->fetch_assoc() : null;
$st->close();
if($row && password_verify((string)$basicPass, $row['password_hash'])){
unset($row['password_hash']);
$context['mode'] = 'basic';
$context['user'] = $row;
return $context;
}
}
}
}
if(isset($_SESSION['jwt'])){
$payload = jwt_verify($_SESSION['jwt']);
if($payload && isset($payload['sub']) && $conn){
$uid = intval($payload['sub']);
$st = $conn->prepare('SELECT id, email, name, organization, role, status FROM users WHERE id = ? LIMIT 1');
if($st){
$st->bind_param('i', $uid);
$st->execute();
$r = $st->get_result();
$row = $r ? $r->fetch_assoc() : null;
$st->close();
if($row){
$context['mode'] = 'jwt';
$context['user'] = $row;
return $context;
}
}
}
}
return $context;
}
function get_current_user_info(){
$context = get_auth_context();
return $context['user'];
}
function require_auth($allowedModes = ['jwt','basic','internal']){
$context = get_auth_context();
$user = $context['user'];
if(!$user){ auth_json_response(401, ['success'=>false,'error'=>'unauthenticated']); }
if(isset($user['status']) && $user['status'] !== 'active'){
auth_json_response(403, ['success'=>false,'error'=>'account_not_active']);
}
if($allowedModes && $context['mode'] && !in_array($context['mode'], (array)$allowedModes, true)){
auth_json_response(403, ['success'=>false,'error'=>'forbidden']);
}
return $user;
}
function require_login(){
return require_auth(['jwt','basic','internal']);
}
function require_bearer_login(){
$context = get_auth_context();
if(!$context['user'] || $context['mode'] !== 'jwt'){
auth_json_response(401, ['success'=>false,'error'=>'bearer_required']);
}
if(isset($context['user']['status']) && $context['user']['status'] !== 'active'){
auth_json_response(403, ['success'=>false,'error'=>'account_not_active']);
}
return $context['user'];
}
function require_role($roles){
$user = require_login();
if(!in_array($user['role'], (array)$roles, true)){
auth_json_response(403, ['success'=>false,'error'=>'forbidden']);
}
return $user;
}
function auth_current_mode(){
$context = get_auth_context();
return $context['mode'];
}
function auth_issue_user_token($userId, $role, $extra = [], $ttlSeconds = 86400){
return jwt_issue(array_merge(['sub' => intval($userId), 'role' => $role], $extra), $ttlSeconds);
}
function auth_set_session_jwt($token){
start_session_if_needed();
$_SESSION['jwt'] = $token;
}
function auth_clear_session(){
start_session_if_needed();
foreach(['jwt','user_id'] as $key){ if(isset($_SESSION[$key])) unset($_SESSION[$key]); }
}