oauth2: flux automat token ANAF (FR-1..FR-11) + arhivare fisiere vechi

- index.php: schimb code->token cu tratare completa erori, sesiune
  PHP pentru legare state (CSRF), stocare atomica tokenuri in fisier
- pick.php: endpoint polling pentru ROACONT (preluare automata token)
- oauth2_tokens mutat in oauth2/tokens/ + .htaccess Require all denied
  (locatia initiala "extra-docroot" era de fapt in interiorul
  public_html, deci web-accesibila fara aceasta protectie)
- index1.php/index2.php/info.php (versiuni vechi) arhivate in
  _arhiva_oauth2/ cu .htaccess care blocheaza accesul HTTP

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-07 21:10:32 +03:00
parent 6afd3840ec
commit 1b94624a6d
7 changed files with 716 additions and 403 deletions

2
_arhiva_oauth2/.htaccess Normal file
View File

@@ -0,0 +1,2 @@
# Interzice accesul HTTP la fisierele arhivate (belt-and-braces, langa extra-docroot)
Require all denied

View File

@@ -1,136 +1,384 @@
<?php <?php
// oAuth2 JWT Token // index.php — OAuth2 ANAF eFactura: obtinere token automat + compatibilitate retro
// FR-1 (propagare state), FR-2 (sesiune primara), FR-3 (stocare tokenuri),
$get = $_REQUEST; // FR-4 (retro-compat fara state), FR-10 (securitate), FR-11 (protectie stocare)
$query_string = ''; //
foreach ($get as $key => $value) { // Modificat: 2026-07-07 — implementare flux automat (stocare fisier + pagina succes)
$query_string .= $key . '=' . $value . '&';
}
// echo($query_string);
parse_str($query_string);
// === Credentiale ANAF (pastrate din versiunea anterioara) ===
$client_id = 'f4c59081e488ac326f32f8ab52620023e996dd82d7d01163'; $client_id = 'f4c59081e488ac326f32f8ab52620023e996dd82d7d01163';
$client_secret = 'e65b9127acac254c5b9bfbbe68b9a6ca07b0d6df6dbb0023e996dd82d7d01163'; $client_secret = 'e65b9127acac254c5b9bfbbe68b9a6ca07b0d6df6dbb0023e996dd82d7d01163';
$redirect_uri = 'https://romfast.ro/oauth2/'; $redirect_uri = 'https://romfast.ro/oauth2/';
// REFRESH TOKEN se apeleaza din ROA cu parametrul ?refresh_token=REFRESH_TOKEN pentru prelungirea valabilitatii tokenului // === Director tokenuri (FR-11) ===
// oauth2/tokens/ — protejat explicit cu .htaccess (Require all denied), deoarece
// se afla in interiorul docroot-ului (public_html/oauth2/tokens), nu deasupra lui.
// Codul creaza directorul si .htaccess-ul daca lipsesc (ex. prima instalare)
$tokens_dir = __DIR__ . '/tokens';
if (!is_dir($tokens_dir)) {
@mkdir($tokens_dir, 0700, true);
}
$tokens_htaccess = $tokens_dir . '/.htaccess';
if (!file_exists($tokens_htaccess)) {
@file_put_contents($tokens_htaccess, "Require all denied\n");
}
// ===================================================================
// Functii helper
// ===================================================================
// Validare format state: lungime fixa 64, doar [A-Za-z0-9] (protectie path traversal - NFR-1)
function validState($state) {
return is_string($state)
&& strlen($state) === 64
&& (bool)preg_match('/^[A-Za-z0-9]+$/', $state);
}
// Afisare pagina HTML simpla (§3.3: UTF-8 cu diacritice, H1 mare scanabil,
// font >=16px, contrast inalt, un singur job per pagina, fara linkuri/butoane)
function showPage($page_title, $h1_text, $h1_color, $body_text) {
header('Content-Type: text/html; charset=utf-8');
echo '<!DOCTYPE html>' . "\n";
echo '<html lang="ro"><head>' . "\n";
echo '<meta charset="utf-8">' . "\n";
echo '<meta name="viewport" content="width=device-width,initial-scale=1">' . "\n";
echo '<title>' . htmlspecialchars($page_title, ENT_QUOTES, 'UTF-8') . '</title>' . "\n";
echo '<style>' . "\n";
echo 'body{font-family:Arial,Helvetica,sans-serif;font-size:18px;margin:3em auto;' . "\n";
echo ' max-width:640px;padding:0 1.5em;background:#f7f7f7;color:#222}' . "\n";
echo 'h1{font-size:2em;color:' . $h1_color . ';margin-bottom:0.5em}' . "\n";
echo 'p{font-size:1em;line-height:1.7;margin:0}' . "\n";
echo '.brand{font-size:0.8em;color:#999;margin-bottom:2.5em}' . "\n";
echo '</style>' . "\n";
echo '</head><body>' . "\n";
echo '<p class="brand">ROA Romfast</p>' . "\n";
echo '<h1>' . htmlspecialchars($h1_text, ENT_QUOTES, 'UTF-8') . '</h1>' . "\n";
echo '<p>' . htmlspecialchars($body_text, ENT_QUOTES, 'UTF-8') . '</p>' . "\n";
echo '</body></html>' . "\n";
}
// ===================================================================
// Citire explicita parametri GET (FR-10: elimina parse_str care injecta variabile)
// ===================================================================
$refresh_token = isset($_GET['refresh_token']) ? (string)$_GET['refresh_token'] : '';
$code = isset($_GET['code']) ? (string)$_GET['code'] : '';
$state_param = isset($_GET['state']) ? (string)$_GET['state'] : '';
$error_param = isset($_GET['error']) ? (string)$_GET['error'] : '';
$error_description = isset($_GET['error_description']) ? (string)$_GET['error_description'] : '';
// ===================================================================
// Ramura REFRESH TOKEN — neatinsa functional (FR-4)
// Apelata din ROA direct cu ?refresh_token=... pentru prelungirea tokenului
// ===================================================================
if (!empty($refresh_token)) { if (!empty($refresh_token)) {
$url = 'https://logincert.anaf.ro/anaf-oauth2/v1/token'; $url = 'https://logincert.anaf.ro/anaf-oauth2/v1/token';
//The data you want to send via POST
$fields = [ $fields = [
'client_id' => $client_id, 'client_id' => $client_id,
'client_secret' => $client_secret, 'client_secret' => $client_secret,
'refresh_token' => $refresh_token, 'refresh_token' => $refresh_token,
'redirect_uri' => $redirect_uri, 'redirect_uri' => $redirect_uri,
'grant_type' => 'refresh_token' 'grant_type' => 'refresh_token',
]; ];
//url-ify the data for the POST
$fields_string = http_build_query($fields); $fields_string = http_build_query($fields);
//open connection
$ch = curl_init(); $ch = curl_init();
//set the url, number of POST vars, POST data
curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string); curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string);
//So that curl_exec returns the contents of the cURL; rather than echoing it
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
//execute post
$jsonobj = curl_exec($ch); $jsonobj = curl_exec($ch);
curl_close($ch);
// $arr = json_decode($jsonobj, true); header('Content-Type: application/json');
// La eroare curl: corp gol, identic cu comportamentul vechi (FR-4: ramura neatinsa)
/* $json = json_encode($jsonobj, JSON_UNESCAPED_SLASHES); echo ($jsonobj !== false) ? $jsonobj : '';
if ($json === false) {
// Avoid echo of empty string (which is invalid JSON), and
// JSONify the error message instead:
$json = json_encode(["error" => json_last_error_msg()]);
if ($json === false) {
// This should not happen, but we go all the way now:
$json = '{"error":"unknown"}';
}
// Set HTTP response status code to: 500 - Internal Server Error
http_response_code(500);
}
*/
header("Content-Type: application/json");
echo $jsonobj;
// Close handle
//curl_close($ch);
exit(); exit();
} }
// COD AUTORIZARE se apeleaza din ROA fara parametri pentru obtinerea codului de autorizare de la ANAF. ANAF apeleaza aceasta pagina (callback) cu codul de autorizare // ===================================================================
// Configurare cookie de sesiune PHP (FR-2)
// session_set_cookie_params TREBUIE sa preceada session_start pe AMBELE intrari:
// - intrarea initiala (fara ?code): browser deschis de ROACONT
// - callback-ul ANAF (cu ?code): navigare top-level cross-site
//
// SameSite=Lax este obligatoriu: callback-ul ANAF este o navigare top-level dintr-un
// alt domeniu (logincert.anaf.ro -> romfast.ro) — cu SameSite=Strict cookie-ul nu ar
// fi trimis in callback si legarea sesiune ar esua silentios.
// ===================================================================
if (PHP_VERSION_ID >= 70300) {
// Sintaxa cu array: disponibila incepand cu PHP 7.3
session_set_cookie_params([
'lifetime' => 0,
'path' => '/',
'secure' => true,
'httponly' => true,
'samesite' => 'Lax',
]);
} else {
// Fallback PHP < 7.3: SameSite injectat ca sufix al parametrului path
session_set_cookie_params(0, '/; SameSite=Lax', '', true, true);
}
session_start();
// ===================================================================
// Callback de eroare ANAF (?error=...) — FR-10
// Tratare explicita; nu se re-redirecteaza la authorize (ar produce bucla)
// ===================================================================
if (!empty($error_param)) {
// Extrage state-ul din sesiune pentru a scrie fisierul de eroare
$sess_state = null;
if (!empty($_SESSION['roa_state']) && is_array($_SESSION['roa_state'])) {
$sess = $_SESSION['roa_state'];
if (!empty($sess['value']) && (time() - (int)$sess['ts']) < 900) {
$sess_state = $sess['value'];
}
}
unset($_SESSION['roa_state']);
session_write_close();
// Scrie fisier de eroare pentru state-ul din sesiune (daca exista si valid)
if ($sess_state !== null && validState($sess_state)) {
$fname = hash('sha256', $sess_state);
$token_file = $tokens_dir . '/' . $fname . '.json';
$tmp_file = $tokens_dir . '/' . $fname . '.tmp';
// Anti-suprascriere: nu suprascrie un fisier de succes existent (FR-10)
if (!file_exists($token_file)) {
$err_data = json_encode([
'error' => $error_param,
'error_description' => $error_description,
]);
if ($err_data !== false && file_put_contents($tmp_file, $err_data) !== false) {
@rename($tmp_file, $token_file);
}
}
}
// Pagina de eroare (§3.3: un job, fara retry link, fara re-redirect)
$cause = !empty($error_description) ? $error_description : $error_param;
showPage(
'Autorizare nereușită',
'Autorizarea nu a reușit',
'#c0392b',
$cause . ' Reveniți în aplicația ROA — de acolo puteți relua operația sau folosi completarea manuală.'
);
exit();
}
// ===================================================================
// Intrare INITIALA — nicio sesiune, fara ?code: redirect catre ANAF authorize
// ===================================================================
if (empty($code)) { if (empty($code)) {
// Obtinere cod autorizare if (!empty($state_param)) {
$url = 'https://logincert.anaf.ro/anaf-oauth2/v1/authorize'; // Validare format state inainte de orice operatie (protectie path traversal)
$url .='?client_id='.$client_id; if (!validState($state_param)) {
$url .='&client_secret='.$client_secret; session_write_close();
$url .='&response_type=code'; // L1: H1 conform §3.3 ("Autorizarea nu a reușit" pentru toate paginile de eroare)
$url .='&redirect_uri='.$redirect_uri; showPage(
$url .='&token_content_type=jwt'; 'Autorizare nereușită',
'Autorizarea nu a reușit',
'#c0392b',
// Redirectionez la pagina de autorizare ANAF pentru obtinerea codului de autorizare 'Parametru de sesiune invalid. Reluați operația din aplicația ROA.'
// echo $url; );
header('Location: '.$url);
exit(); exit();
} }
else {
// NEW TOKEN
// callback ANAF: https://romfast.ro/oauth2/?code=COD_AUTORIZARE
// Obtinere token pe baza codului de autorizare // Salveaza state in sesiune cu timestamp (mecanism PRIMAR de legare)
// print_r('Codul de autorizare este: '.$code); $_SESSION['roa_state'] = ['value' => $state_param, 'ts' => time()];
session_write_close();
$url = 'https://logincert.anaf.ro/anaf-oauth2/v1/token'; } else {
// FR-2 igienă sesiune: fara state in URL = flux manual/retro-compat
// Curata orice roa_state ramas dintr-o tentativa automata anterioara esuata
// (altfel, la callback, am afisa pagina de succes fara tokenuri = fund de sac)
unset($_SESSION['roa_state']);
session_write_close();
}
//The data you want to send via POST // URL authorize ANAF — client_secret NU se trimite la authorize (FR-10)
$fields = [ // client_secret apartine doar schimbului code->token (POST, fara URL)
$url_auth = 'https://logincert.anaf.ro/anaf-oauth2/v1/authorize';
$url_auth .= '?client_id=' . urlencode($client_id);
$url_auth .= '&response_type=code';
$url_auth .= '&redirect_uri=' . urlencode($redirect_uri);
$url_auth .= '&token_content_type=jwt';
// FR-1: propagare state la ANAF daca exista
// Nota: doc ANAF (pag.23) spune "State se lasa necompletat" — propagarea e incerta
// dar trimitem oricum; legarea primara ramane sesiunea PHP
if (!empty($state_param)) {
$url_auth .= '&state=' . urlencode($state_param);
}
header('Location: ' . $url_auth);
exit();
}
// ===================================================================
// Callback cu cod de autorizare (?code=...) — schimb code -> token
// ===================================================================
// FR-2: identifica state-ul din sesiune (mecanismul PRIMAR de legare)
// state-ul din sesiune = cel generat de ROACONT si salvat la intrarea initiala
$sess_state = null;
if (!empty($_SESSION['roa_state']) && is_array($_SESSION['roa_state'])) {
$sess = $_SESSION['roa_state'];
if (!empty($sess['value']) && (time() - (int)$sess['ts']) < 900) {
// roa_state valid (sub 15 minute)
$sess_state = $sess['value'];
}
// roa_state mai vechi de 15 minute se ignora (si se sterge mai jos)
}
// Consuma state-ul din sesiune (indiferent de validitate — evita re-utilizare)
unset($_SESSION['roa_state']);
// FR-2: validare CSRF oportunistica
// Daca ANAF a propagat state in callback (comportament incert), trebuie sa coincida cu sesiunea
// Nepotrivire = posibil CSRF sau sesiune gresita -> eroare, fara stocare tokenuri
if (!empty($state_param) && $sess_state !== null) {
if ($state_param !== $sess_state) {
session_write_close();
showPage(
'Eroare de securitate',
'Autorizarea nu a reușit',
'#c0392b',
'Identificatorul de sesiune nu corespunde. Operația a fost anulată din motive de securitate. ' .
'Reveniți în aplicația ROA și reluați de acolo.'
);
exit();
}
}
// FR-11 / C10: elibereaza lock-ul de sesiune inainte de curl
// ANAF poate raspunde lent; nu tinem lock-ul de sesiune blocat in acest timp
session_write_close();
// Schimb cod de autorizare -> token (FR-10: cu timeouts + tratare erori)
$url_token = 'https://logincert.anaf.ro/anaf-oauth2/v1/token';
$fields_token = [
'client_id' => $client_id, 'client_id' => $client_id,
'client_secret' => $client_secret, 'client_secret' => $client_secret,
'code' => $code, 'code' => $code,
'redirect_uri' => $redirect_uri, 'redirect_uri' => $redirect_uri,
'grant_type' => 'authorization_code', 'grant_type' => 'authorization_code',
'token_content_type' => 'jwt' 'token_content_type' => 'jwt',
]; ];
$fields_string = http_build_query($fields_token);
//url-ify the data for the POST
$fields_string = http_build_query($fields);
//open connection
$ch = curl_init(); $ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url_token);
//set the url, number of POST vars, POST data
curl_setopt($ch,CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string); curl_setopt($ch, CURLOPT_POSTFIELDS, $fields_string);
//So that curl_exec returns the contents of the cURL; rather than echoing it
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); // FR-10: timeout conectare
curl_setopt($ch, CURLOPT_TIMEOUT, 30); // FR-10: timeout total
//execute post
$jsonobj = curl_exec($ch); $jsonobj = curl_exec($ch);
//echo $jsonobj; $curl_errno = curl_errno($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$arr = json_decode($jsonobj, true);
echo '<p>Copiati ANAF Access Token si Refresh Token de pe liniile urmatoare si completati-le in aplicatie</p>';
echo '<p><b>Access Token:</b></p><p>'.$arr["access_token"].'</p>';
echo '<p><b>Refresh Token:</b></p><p>'.$arr["refresh_token"].'</p>';
// Close handle
curl_close($ch); curl_close($ch);
// FR-10: tratare explicita erori curl / HTTP != 200 / JSON de eroare
// (inainte: un esec afisa tokenuri goale)
$error_msg = null; // text uman (pagina browser + error_description)
$err_code = null; // cod scurt ASCII citit de ROACONT (loJson.error -> M12)
$token_data = null;
if ($curl_errno !== 0) {
$err_code = 'curl_error';
$error_msg = 'Eroare de rețea la contactarea ANAF (curl errno: ' . (int)$curl_errno . ').';
} elseif ($http_code !== 200) {
$err_code = 'http_error';
$error_msg = 'ANAF a răspuns cu codul HTTP ' . (int)$http_code . '.';
} else {
$token_data = json_decode((string)$jsonobj, true);
if ($token_data === null) {
$err_code = 'json_invalid';
$error_msg = 'Răspunsul ANAF nu a putut fi decodificat (JSON invalid).';
} elseif (!empty($token_data['error'])) {
$err_code = $token_data['error']; // cod brut ANAF: invalid_grant, access_denied etc.
$err_desc = !empty($token_data['error_description']) ? $token_data['error_description'] : '';
$error_msg = 'ANAF: ' . $err_code . ($err_desc ? ' — ' . $err_desc : '') . '.';
}
} }
// ===================================================================
// Scriere fisier tokenuri (FR-3, FR-11)
// Se efectueaza DOAR daca sesiunea avea un state valid (flux automat)
// ===================================================================
if ($sess_state !== null && validState($sess_state)) {
$fname = hash('sha256', $sess_state);
$token_file = $tokens_dir . '/' . $fname . '.json';
$tmp_file = $tokens_dir . '/' . $fname . '.tmp';
?> if ($error_msg !== null) {
// Eroare: scrie fisier de eroare
// FR-10 anti-suprascriere: nu suprascrie un token de succes existent
// (ex. refresh pe pagina callback dupa succes: code expirat -> eroare -> nu stergem tokenul bun)
if (!file_exists($token_file)) {
$err_data = json_encode([
'error' => $err_code, // cod scurt ASCII citit de ROACONT (fara mojibake)
'error_description' => $error_msg, // text uman UTF-8 (diagnostic)
]);
if ($err_data !== false && file_put_contents($tmp_file, $err_data) !== false) {
@rename($tmp_file, $token_file);
}
}
} else {
// Succes: scriere atomica a tokenurilor
$json_out = json_encode($token_data, JSON_UNESCAPED_SLASHES);
$write_ok = false;
if ($json_out !== false && file_put_contents($tmp_file, $json_out) !== false) {
// @rename() pe acelasi filesystem = atomic (pick.php nu poate citi JSON partial)
if (@rename($tmp_file, $token_file)) {
$write_ok = true;
} else {
@unlink($tmp_file); // curata .tmp la esec rename; TTL il curata oricum
}
}
if (!$write_ok) {
// Scriere esuata — afisam eroare (nu succes fals); ROACONT va detecta timeout
$error_msg = 'Tokenul ANAF a fost primit dar nu a putut fi salvat temporar pe server. Reveniți în aplicație și reluați operația.';
}
}
}
// ===================================================================
// Afisare pagina rezultat (§3.3)
// ===================================================================
if ($sess_state !== null) {
// Flux automat (state in sesiune): afiseaza succes sau eroare
// Tokenurile NU apar in pagina — sunt ridicate de pick.php
if ($error_msg !== null) {
showPage(
'Autorizare nereușită',
'Autorizarea nu a reușit',
'#c0392b',
$error_msg .
' Reveniți în aplicația ROA — de acolo puteți relua operația sau folosi completarea manuală.'
);
} else {
// §3.3 pagina succes: H1 verde, corp cu instructiunea de revenire in ROA
showPage(
'Autorizare reușită',
'Autorizare reușită',
'#27ae60',
'Reveniți în aplicația ROA — tokenul se preia automat. Puteți închide această pagină.'
);
}
} else {
// FR-4 retro-compat: fara state in sesiune = versiune veche ROACONT sau sesiune expirata
// Comportamentul ACTUAL ramas neschimbat: tokenurile apar in pagina pentru copiere manuala
if ($error_msg !== null) {
echo '<p>Eroare la obtinerea tokenului: ' . htmlspecialchars($error_msg, ENT_QUOTES, 'UTF-8') . '</p>';
} else {
echo '<p>Copiati ANAF Access Token si Refresh Token de pe liniile urmatoare si completati-le in aplicatie</p>';
echo '<p><b>Access Token:</b></p><p>';
echo htmlspecialchars((string)(isset($token_data['access_token']) ? $token_data['access_token'] : ''), ENT_QUOTES, 'UTF-8');
echo '</p>';
echo '<p><b>Refresh Token:</b></p><p>';
echo htmlspecialchars((string)(isset($token_data['refresh_token']) ? $token_data['refresh_token'] : ''), ENT_QUOTES, 'UTF-8');
echo '</p>';
}
}

62
oauth2/pick.php Normal file
View File

@@ -0,0 +1,62 @@
<?php
// pick.php — Endpoint polling preluare automata token OAuth2 ANAF eFactura
// Apelat de ROACONT cu POST state=<64 hex>; raspunde JSON:
// {"status":"pending"} cat timp tokenul nu a sosit (intotdeauna HTTP 200, nu 404),
// continutul fisierului de token (succes sau eroare ANAF) o SINGURA data, apoi il sterge.
// Fara session_start() — evita serializarea pe lock-ul de sesiune PHP.
header('Content-Type: application/json; charset=utf-8');
// Director tokenuri (acelasi ca in index.php) — protejat cu .htaccess
$tokens_dir = __DIR__ . '/tokens';
if (!is_dir($tokens_dir)) {
@mkdir($tokens_dir, 0700, true);
}
$tokens_htaccess = $tokens_dir . '/.htaccess';
if (!file_exists($tokens_htaccess)) {
@file_put_contents($tokens_htaccess, "Require all denied\n");
}
// Validare stricta format state: 64 caractere [A-Za-z0-9] (protectie path traversal)
$state = isset($_POST['state']) ? (string)$_POST['state'] : '';
if (strlen($state) !== 64 || !preg_match('/^[A-Za-z0-9]+$/', $state)) {
http_response_code(400);
echo json_encode(['error' => 'invalid_state']);
exit();
}
// TTL: sterge fisierele de tokenuri (si .tmp orfane) mai vechi de 10 minute
// (aliniat cu timeout-ul de polling din ROACONT)
$now = time();
foreach (array_merge((array)glob($tokens_dir . '/*.json'), (array)glob($tokens_dir . '/*.tmp')) as $f) {
if (($now - filemtime($f)) > 600) {
@unlink($f);
}
}
// Numele fisierului = SHA-256(state): secretul nu apare in listinguri de directoare
$token_file = $tokens_dir . '/' . hash('sha256', $state) . '.json';
if (!file_exists($token_file)) {
echo json_encode(['status' => 'pending']);
exit();
}
// Claim atomic prin rename: din doua cereri concurente una singura primeste tokenul,
// cealalta "pending"; previne si citirea unui JSON partial (index.php scrie .tmp + rename)
$claim_file = $tokens_dir . '/' . hash('sha256', $state) . '_claim_' . getmypid() . '.json';
if (!@rename($token_file, $claim_file)) {
echo json_encode(['status' => 'pending']);
exit();
}
// Citire INAINTE de stergere; la esec nu stergem (TTL curata fisierul claim ramas)
$content = file_get_contents($claim_file);
if ($content === false || $content === '') {
echo json_encode(['error' => 'read_error']);
exit();
}
@unlink($claim_file);
// Continutul JSON exact cum a fost salvat de index.php (tokenuri sau eroare ANAF)
echo $content;

1
oauth2/tokens/.htaccess Normal file
View File

@@ -0,0 +1 @@
Require all denied