initializeAuthentikDriver(); } /** * Initialiser le driver Authentik avec la configuration de l'extension */ private function initializeAuthentikDriver() { $extension = Extension::where('extension', 'Authentik')->first(); if (!$extension || !$extension->settings) { return; } $settings = $extension->settings->pluck('value', 'key')->toArray(); if (empty($settings['enabled']) || empty($settings['client_id']) || empty($settings['client_secret']) || empty($settings['base_url'])) { return; } $this->authentik_driver = Socialite::buildProvider(\Paymenter\Extensions\Others\Authentik\Providers\AuthentikProvider::class, [ 'client_id' => $settings['client_id'], 'client_secret' => $settings['client_secret'], 'redirect' => '/oauth/authentik/callback', 'base_url' => $settings['base_url'], 'authorize_url' => $settings['base_url'] . '/application/o/authorize/', 'token_url' => $settings['base_url'] . '/application/o/token/', 'userinfo_url' => $settings['base_url'] . '/application/o/userinfo/', ]); } /** * Rediriger vers Authentik pour l'authentification */ public function redirect() { if (!$this->authentik_driver) { return redirect()->route('login')->with('error', 'Authentik n\'est pas configuré correctement.'); } return $this->authentik_driver ->scopes(['openid', 'email', 'profile']) ->with(['prompt' => 'consent']) ->redirect(); } /** * Gérer le callback d'authentification d'Authentik */ public function handle(Request $request) { if (!$this->authentik_driver) { return redirect()->route('login')->with('error', 'Authentik n\'est pas configuré correctement.'); } if ($request->input('error')) { return redirect()->route('login')->with('error', 'Erreur d\'authentification: ' . $request->input('error_description')); } try { // Obtenir le token d'accès $token = $this->authentik_driver->getAccessTokenResponse($request->get('code')); // Obtenir les données utilisateur try { $oauth_user_data = $this->authentik_driver->getUserByToken($token['access_token']); } catch (\Exception $e) { Log::error('Erreur lors de la récupération des données utilisateur Authentik: ' . $e->getMessage()); // Fallback: décoder le token ID $id_token = $token['id_token']; $oauth_user_data = $this->decodeIdToken($id_token); // Essayer d'obtenir l'email depuis l'API Authentik try { $user_email = $this->getUserEmailFromAuthentik($token['access_token'], $oauth_user_data['sub']); if ($user_email) { $oauth_user_data['email'] = $user_email; } } catch (\Exception $emailError) { Log::error('Erreur lors de la récupération de l\'email Authentik: ' . $emailError->getMessage()); } } $oauth_user = $this->authentik_driver->mapUserToObject($oauth_user_data); Log::info('Utilisateur Authentik connecté', [ 'email' => $oauth_user->email, 'name' => $oauth_user->name, 'id' => $oauth_user->id, 'raw_data' => $oauth_user_data ]); // Vérifier si l'utilisateur existe $user = null; if (!empty($oauth_user->email)) { $user = User::where('email', $oauth_user->email)->first(); } // Récupérer les paramètres de l'extension $extension = Extension::where('extension', 'Authentik')->first(); $settings = $extension ? $extension->settings->pluck('value', 'key')->toArray() : []; if (!$user) { // Vérifier si la création automatique d'utilisateurs est activée if (empty($settings['auto_create_users'])) { Log::warning('Utilisateur Authentik non trouvé et création automatique désactivée', [ 'searched_email' => $oauth_user->email, 'searched_authentik_id' => $oauth_user->id ?? null, 'user_data' => $oauth_user_data ]); return redirect()->route('login')->with('error', 'Votre compte n\'est pas enregistré. Veuillez contacter un administrateur.'); } // Créer un nouvel utilisateur $user = $this->createUserFromAuthentikData($oauth_user, $oauth_user_data); } else { // Vérifier si l'utilisateur existant est requis if (!empty($settings['require_existing_user']) && !$user) { return redirect()->route('login')->with('error', 'Votre compte n\'est pas enregistré. Veuillez contacter un administrateur.'); } } // Connecter l'utilisateur Auth::login($user, true); return redirect()->route('home'); } catch (\Exception $e) { Log::error('Erreur lors du callback Authentik: ' . $e->getMessage()); return redirect()->route('login')->with('error', 'Échec de l\'authentification: ' . $e->getMessage()); } } /** * Créer un utilisateur à partir des données Authentik */ private function createUserFromAuthentikData($oauth_user, $oauth_user_data) { // Dériver nom et prénom $rawGivenName = $oauth_user_data['given_name'] ?? ($oauth_user_data['name'] ?? null); $rawFamilyName = $oauth_user_data['family_name'] ?? null; $fullName = $oauth_user_data['name'] ?? ($oauth_user->name ?? ''); $firstName = ''; $lastName = ''; if (!empty($rawGivenName) && !empty($rawFamilyName)) { $firstName = $rawGivenName; $lastName = $rawFamilyName; } elseif (!empty($fullName)) { $parts = preg_split('/\s+/', trim($fullName)); $firstName = $parts[0] ?? ''; $lastName = trim(implode(' ', array_slice($parts, 1))); } $email = $oauth_user->email ?? ($oauth_user_data['email'] ?? null); if (empty($email)) { throw new \Exception('Authentik n\'a pas fourni d\'email pour l\'utilisateur.'); } return User::create([ 'first_name' => $firstName, 'last_name' => $lastName, 'email' => $email, 'password' => Str::random(40), 'email_verified_at' => Carbon::now(), 'provider' => 'authentik', 'provider_id' => $oauth_user->id, ]); } /** * Décoder le token ID JWT d'Authentik */ private function decodeIdToken($idToken) { // Les tokens JWT ont 3 parties séparées par des points $parts = explode('.', $idToken); if (count($parts) !== 3) { throw new \Exception('Format de token ID invalide'); } // Décoder le payload (deuxième partie) $payload = $parts[1]; // Ajouter le padding si nécessaire $payload .= str_repeat('=', (4 - strlen($payload) % 4) % 4); // Décoder base64 $decoded = base64_decode($payload); if ($decoded === false) { throw new \Exception('Échec du décodage du payload du token ID'); } $userData = json_decode($decoded, true); if (json_last_error() !== JSON_ERROR_NONE) { throw new \Exception('Échec de l\'analyse du payload du token ID: ' . json_last_error_msg()); } return $userData; } /** * Essayer d'obtenir l'email de l'utilisateur depuis l'API Authentik */ private function getUserEmailFromAuthentik($accessToken, $userId) { try { $extension = Extension::where('extension', 'Authentik')->first(); $baseUrl = $extension ? $extension->settings->where('key', 'base_url')->first()->value : null; if (!$baseUrl) { return null; } // Endpoint 1: API des utilisateurs Authentik $response = $this->getHttpClient()->get( $baseUrl . '/api/v3/core/users/' . $userId . '/', ['headers' => ['Authorization' => 'Bearer ' . $accessToken]] ); $userData = json_decode($response->getBody(), true); if (isset($userData['email']) && !empty($userData['email'])) { return $userData['email']; } // Endpoint 2: Informations de l'utilisateur actuel $response = $this->getHttpClient()->get( $baseUrl . '/api/v3/core/users/me/', ['headers' => ['Authorization' => 'Bearer ' . $accessToken]] ); $userData = json_decode($response->getBody(), true); if (isset($userData['email']) && !empty($userData['email'])) { return $userData['email']; } } catch (\Exception $e) { Log::error('Erreur API Authentik: ' . $e->getMessage()); } return null; } /** * Obtenir le client HTTP pour faire des appels aux APIs externes */ private function getHttpClient() { return new \GuzzleHttp\Client([ 'timeout' => 10, 'verify' => false, // Seulement pour le développement, en production devrait être true ]); } }