feat: stockage articles en fichiers Markdown, SSO intégré, URLs propres
This commit is contained in:
+36
-67
@@ -2,14 +2,12 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Infrastructure\Database;
|
||||
|
||||
if (session_status() !== PHP_SESSION_ACTIVE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
|
||||
require_once dirname(__DIR__, 2) . '/app/bootstrap.php';
|
||||
require_once dirname(__DIR__, 2) . '/bootstrap.php';
|
||||
require_once dirname(__DIR__, 2) . '/config/config.php';
|
||||
|
||||
if (!function_exists('env')) {
|
||||
@@ -25,13 +23,13 @@ if (!function_exists('env')) {
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
|
||||
$debug = (env('APP_DEBUG', '0') === '1');
|
||||
|
||||
// --- OIDC config ---
|
||||
$OIDC_ISSUER = rtrim((string)env('OIDC_ISSUER', ''), '/');
|
||||
$OIDC_CLIENT_ID = (string)env('OIDC_CLIENT_ID', '');
|
||||
$OIDC_CLIENT_SECRET = (string)env('OIDC_CLIENT_SECRET', '');
|
||||
$OIDC_REDIRECT_URI = (string)(env('OIDC_REDIRECT_URI') ?: url('oidc/callback'));
|
||||
$OIDC_ISSUER = rtrim((string)(env('OIDC_ISSUER') ?? ''), '/');
|
||||
$OIDC_CLIENT_ID = (string)(env('OIDC_CLIENT_ID') ?? '');
|
||||
$OIDC_CLIENT_SECRET = (string)(env('OIDC_CLIENT_SECRET') ?? '');
|
||||
$OIDC_REDIRECT_URI = (string)(env('OIDC_REDIRECT_URI') ?: url('oidc/callback.php'));
|
||||
|
||||
if (!$OIDC_ISSUER || !$OIDC_CLIENT_ID || !$OIDC_REDIRECT_URI) {
|
||||
http_response_code(500);
|
||||
@@ -42,7 +40,6 @@ if (!$OIDC_ISSUER || !$OIDC_CLIENT_ID || !$OIDC_REDIRECT_URI) {
|
||||
$tokenEndpoint = $OIDC_ISSUER . '/protocol/openid-connect/token';
|
||||
$userInfoEndpoint = $OIDC_ISSUER . '/protocol/openid-connect/userinfo';
|
||||
|
||||
// --- Checks retour ---
|
||||
if (!isset($_GET['state'], $_SESSION['oidc_state']) || !hash_equals((string)$_SESSION['oidc_state'], (string)$_GET['state'])) {
|
||||
http_response_code(400);
|
||||
echo $debug ? 'State invalide.' : 'Requête invalide.';
|
||||
@@ -58,9 +55,7 @@ if (empty($_GET['code'])) {
|
||||
$code = (string)$_GET['code'];
|
||||
|
||||
$codeVerifier = $_SESSION['oidc_code_verifier'] ?? null;
|
||||
unset($_SESSION['oidc_code_verifier']); // anti-replay
|
||||
$expectedNonce = $_SESSION['oidc_nonce'] ?? null;
|
||||
unset($_SESSION['oidc_nonce']); // anti-replay
|
||||
unset($_SESSION['oidc_code_verifier'], $_SESSION['oidc_nonce']);
|
||||
|
||||
if (!$codeVerifier) {
|
||||
http_response_code(400);
|
||||
@@ -68,7 +63,7 @@ if (!$codeVerifier) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// --- Échange code -> tokens ---
|
||||
// Échange code → tokens
|
||||
$post = [
|
||||
'grant_type' => 'authorization_code',
|
||||
'code' => $code,
|
||||
@@ -86,15 +81,17 @@ curl_setopt_array($ch, [
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => http_build_query($post, '', '&', PHP_QUERY_RFC3986),
|
||||
CURLOPT_TIMEOUT => 15,
|
||||
CURLOPT_SSL_VERIFYPEER => true,
|
||||
CURLOPT_SSL_VERIFYHOST => 2,
|
||||
]);
|
||||
$tokenResponse = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$err = curl_error($ch);
|
||||
$curlErr = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($tokenResponse === false || $httpCode !== 200) {
|
||||
http_response_code(500);
|
||||
echo $debug ? 'Échec échange token: ' . htmlspecialchars($err ?: (string)$tokenResponse) : 'Erreur d’authentification.';
|
||||
echo $debug ? 'Échec échange token : ' . htmlspecialchars($curlErr ?: (string)$tokenResponse) : 'Erreur d\'authentification.';
|
||||
exit;
|
||||
}
|
||||
|
||||
@@ -104,16 +101,18 @@ $idToken = $tokens['id_token'] ?? null;
|
||||
|
||||
if (!$accessToken) {
|
||||
http_response_code(500);
|
||||
echo $debug ? 'Access token manquant.' : 'Erreur d’authentification.';
|
||||
echo $debug ? 'Access token manquant.' : 'Erreur d\'authentification.';
|
||||
exit;
|
||||
}
|
||||
|
||||
// --- UserInfo ---
|
||||
// UserInfo
|
||||
$ch = curl_init($userInfoEndpoint);
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $accessToken],
|
||||
CURLOPT_TIMEOUT => 10,
|
||||
CURLOPT_SSL_VERIFYPEER => true,
|
||||
CURLOPT_SSL_VERIFYHOST => 2,
|
||||
]);
|
||||
$userInfoResponse = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
@@ -121,22 +120,17 @@ curl_close($ch);
|
||||
|
||||
if ($userInfoResponse === false || $httpCode !== 200) {
|
||||
http_response_code(500);
|
||||
echo $debug ? 'Échec UserInfo: ' . htmlspecialchars((string)$userInfoResponse) : 'Erreur d’authentification.';
|
||||
echo $debug ? 'Échec UserInfo.' : 'Erreur d\'authentification.';
|
||||
exit;
|
||||
}
|
||||
|
||||
$claims = json_decode((string)$userInfoResponse, true) ?: [];
|
||||
$email = $claims['email'] ?? null;
|
||||
|
||||
// --- Récup info utiles ---
|
||||
$email = $claims['email'] ?? null;
|
||||
$username = $claims['preferred_username'] ?? ($email ?: null);
|
||||
$firstname = $claims['given_name'] ?? null;
|
||||
$lastname = $claims['family_name'] ?? null;
|
||||
|
||||
// Fallback : lire l'email depuis le payload du id_token
|
||||
if (!$email && $idToken && substr_count($idToken, '.') === 2) {
|
||||
[, $p, ] = explode('.', $idToken, 3);
|
||||
$payloadJson = base64_decode(strtr($p, '-_', '+/'), true);
|
||||
$payload = $payloadJson ? json_decode($payloadJson, true) : null;
|
||||
$payload = json_decode((string)base64_decode(strtr($p, '-_', '+/'), true), true);
|
||||
if (is_array($payload) && !empty($payload['email'])) {
|
||||
$email = $payload['email'];
|
||||
}
|
||||
@@ -144,50 +138,25 @@ if (!$email && $idToken && substr_count($idToken, '.') === 2) {
|
||||
|
||||
if (!$email) {
|
||||
http_response_code(400);
|
||||
echo $debug ? 'Email non fourni par IdP.' : 'Impossible de récupérer votre email.';
|
||||
echo $debug ? 'Email non fourni par l\'IdP.' : 'Impossible de récupérer votre email.';
|
||||
exit;
|
||||
}
|
||||
|
||||
// --- Si l'utilisateur existe déjà -> connecter et redirect ---
|
||||
$flow = $_SESSION['oidc_flow'] ?? 'login';
|
||||
|
||||
// Vérifie existence en base
|
||||
/** @var \PDO $pdo */
|
||||
$pdo = Database::get();
|
||||
$stmt = $pdo->prepare('SELECT id FROM users WHERE email = :email LIMIT 1');
|
||||
$stmt->execute([':email' => $email]);
|
||||
$existingId = $stmt->fetchColumn();
|
||||
|
||||
// Si flow=login ET utilisateur existe → connexion directe
|
||||
if ($flow === 'login' && $existingId) {
|
||||
$_SESSION['user_id'] = (int)$existingId;
|
||||
$_SESSION['user_email'] = $email;
|
||||
$_SESSION['oidc'] = [
|
||||
'issuer' => $OIDC_ISSUER,
|
||||
'sub' => $claims['sub'] ?? null,
|
||||
'access_token' => $accessToken,
|
||||
'id_token' => $idToken,
|
||||
'expires_at' => time() + (int)($tokens['expires_in'] ?? 3600),
|
||||
];
|
||||
$target = $_SESSION['oidc_return_to'] ?? '/';
|
||||
unset($_SESSION['oidc_return_to'], $_SESSION['oidc_flow']);
|
||||
if (!is_string($target) || $target === '' || $target[0] !== '/') {
|
||||
$target = '/';
|
||||
}
|
||||
header('Location: ' . $target, true, 303);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Sinon : go formulaire d’inscription (pré-rempli)
|
||||
$_SESSION['pending_oidc'] = [
|
||||
'issuer' => $OIDC_ISSUER,
|
||||
'sub' => $claims['sub'] ?? null,
|
||||
'email' => $email,
|
||||
'username' => $claims['preferred_username'] ?? ($email ?: null),
|
||||
'firstname' => $claims['given_name'] ?? null,
|
||||
'lastname' => $claims['family_name'] ?? null,
|
||||
// Ouvre la session authentifiée
|
||||
session_regenerate_id(true);
|
||||
$_SESSION['user_email'] = strtolower(trim($email));
|
||||
$_SESSION['oidc'] = [
|
||||
'issuer' => $OIDC_ISSUER,
|
||||
'sub' => $claims['sub'] ?? null,
|
||||
'access_token' => $accessToken,
|
||||
'id_token' => $idToken,
|
||||
'expires_at' => time() + (int)($tokens['expires_in'] ?? 3600),
|
||||
];
|
||||
unset($_SESSION['oidc_flow']);
|
||||
|
||||
header('Location: ' . url('register/from-oidc'), true, 303);
|
||||
$target = $_SESSION['oidc_return_to'] ?? '/';
|
||||
unset($_SESSION['oidc_return_to'], $_SESSION['oidc_flow']);
|
||||
if (!is_string($target) || $target === '' || $target[0] !== '/') {
|
||||
$target = '/';
|
||||
}
|
||||
header('Location: ' . $target, true, 303);
|
||||
exit;
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ if (session_status() !== PHP_SESSION_ACTIVE) {
|
||||
}
|
||||
|
||||
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
|
||||
require_once dirname(__DIR__, 2) . '/app/bootstrap.php';
|
||||
require_once dirname(__DIR__, 2) . '/bootstrap.php';
|
||||
require_once dirname(__DIR__, 2) . '/config/config.php';
|
||||
|
||||
function maskToken(?string $t): string
|
||||
|
||||
@@ -7,7 +7,7 @@ if (session_status() !== PHP_SESSION_ACTIVE) {
|
||||
}
|
||||
|
||||
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
|
||||
require_once dirname(__DIR__, 2) . '/app/bootstrap.php';
|
||||
require_once dirname(__DIR__, 2) . '/bootstrap.php';
|
||||
require_once dirname(__DIR__, 2) . '/config/config.php';
|
||||
|
||||
if (!function_exists('env')) {
|
||||
|
||||
Reference in New Issue
Block a user