diff --git a/.env b/.env index 47afb85..868b9f6 100644 --- a/.env +++ b/.env @@ -29,8 +29,9 @@ BASE_URL='http://localhost:8080' # connexion hydra HYDRA_ADMIN_BASE_URL='http://hydra:4445' APP_LOCALES="fr,en" - - +SECURITY_PATTERN= +NEW_HASH_ALGO= +HASH_ALGO_LEGACY="sha256" ###> symfony/lock ### # Choose one of the stores below # postgresql+advisory://db_user:db_password@localhost/db_name diff --git a/.gitignore b/.gitignore index 52b996a..5f6c2de 100644 --- a/.gitignore +++ b/.gitignore @@ -6,9 +6,7 @@ /config/secrets/prod/prod.decrypt.private.php /public/bundles/ /var/ -###< symfony/framework-bundle ### -supervisord.log -supervisord.pid + /vendor /tools/php-cs-fixer/vendor ###> symfony/webpack-encore-bundle ### @@ -22,5 +20,5 @@ yarn-error.log /.config /.npm /.local -supervisord.log -supervisord.pid \ No newline at end of file +/supervisord.log +/supervisord.pid \ No newline at end of file diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 8555009..81dc52e 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -10,7 +10,7 @@ security: # algorithm: 'sha256' # https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider providers: - pdo_user_provider: + sql_login_provider: id: App\Security\SQLLoginUserProvider firewalls: dev: @@ -19,7 +19,7 @@ security: main: # lazy: true stateless: false - provider: pdo_user_provider + provider: sql_login_provider custom_authenticators: - App\Security\SQLLoginUserAuthenticator diff --git a/config/services.yaml b/config/services.yaml index ccd748b..e331ae1 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -9,8 +9,12 @@ parameters: database.user: "%env(resolve:DB_USER)%" database.password: "%env(resolve:DB_PASSWORD)%" - # algorythme de hahshage utilisé "md5", "sha256", "haval160,4", etc. - hashAlgo: "sha256" + # algorythme de hashage utilisé "md5", "sha256", "haval160,4", etc. + env(HASH_ALGO_LEGACY): "sha256" + hashAlgoLegacy: '%env(resolve:HASH_ALGO_LEGACY)%' + + env(NEW_HASH_ALGO): "sha256" + newHashAlgo: '%env(resolve:NEW_HASH_ALGO)%' # adresse du site hote issuer_url: '%env(resolve:ISSUER_URL)%' @@ -21,6 +25,7 @@ parameters: default_locale: '%env(DEFAULT_LOCALE)%' env(DEFAULT_LOCALE): 'fr' + security_pattern: '%env(resolve:SECURITY_PATTERN)%' env(APP_LOCALES): "fr,en" locales: '%env(APP_LOCALES)%' app.supported_locales: ~ @@ -65,6 +70,8 @@ services: App\Security\Hasher\PasswordEncoder: arguments: $pepper: '%pepper%' - $hashAlgo: '%hashAlgo%' + $hashAlgoLegacy: '%hashAlgoLegacy%' + $newHashAlgo: '%newHashAlgo%' + $securityPattern: '%security_pattern%' # add more service definitions when explicit configuration is needed # please note that last definitions always *replace* previous ones diff --git a/containers/compose/postgres/init-db.d/fill_lasql.sql b/containers/compose/postgres/init-db.d/fill_lasql.sql index 78739ee..0284076 100644 --- a/containers/compose/postgres/init-db.d/fill_lasql.sql +++ b/containers/compose/postgres/init-db.d/fill_lasql.sql @@ -10,6 +10,6 @@ INSERT INTO usager (email, password, salt, lastname, firstname) VALUES ('test3@test.com', '504ae1c3e2f5fdaf41f868164dabcef21e17059f5f388b452718a1ce92692c67', 'cesaltestunautreexemple', 'Dupont', 'Henri'); INSERT INTO usager (email, password, lastname, firstname) VALUES - ('test4@test.com', '50626fa21f45a275cea0efff13ff78fd02234cade322da08b7191c7e9150141d', 'Durand', 'Isabelle'), + ('test4@test.com', '$2a$12$zFN0VJ..Cuu.2itWQwmHJe5EUhNHazbMfCSJFpNiEfdwpLzjjDM0u', 'Durand', 'Isabelle'), ('test2@test.com', '50626fa21f45a275cea0efff13ff78fd02234cade322da08b7191c7e9150141d', 'Dubois', 'Angela'); GRANT ALL PRIVILEGES ON DATABASE usager TO lasql diff --git a/docker-compose.yml b/docker-compose.yml index 76ae48f..8d13564 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -37,6 +37,9 @@ services: - DB_PASSWORD=lasql - DEFAULT_LOCALE=fr - DSN_REMOTE_DATABASE=pgsql:host='postgres';port=5432;dbname=lasql; + - HASH_ALGO_LEGACY=sha256 + - NEW_HASH_ALGO=bcrypt + - SECURITY_PATTERN=password,salt,pepper oidc-test: diff --git a/readme.md b/readme.md index 80da579..6852292 100644 --- a/readme.md +++ b/readme.md @@ -34,6 +34,10 @@ BASE_URL='http://localhost:8080' HYDRA_ADMIN_BASE_URL='http://hydra:4445' DSN_REMOTE_DATABASE="pgsql:host='postgres';port=5432;dbname=lasql" APP_LOCALES="fr,en" +NEW_HASH_ALGO="argon2id" +HASH_ALGO_LEGACY="sha256, bcrypt" +SECURITY_PATTERN="password,salt,pepper" +PEPPER ``` ## Tests password @@ -69,7 +73,9 @@ DSN_REMOTE_DATABASE="mysql:host=mariadb;port=3306;dbname=lasql;" |test1@test.com| 8ad4025044b77ae6a5e3fcf99e53e44b15db9a4ecf468be21cbc6b9fbdae6d9f| cesaltestunexemple| Locke|John| |test2@test.com| 50626fa21f45a275cea0efff13ff78fd02234cade322da08b7191c7e9150141d| NULL| Dubois| Angela| |test3@test.com| 504ae1c3e2f5fdaf41f868164dabcef21e17059f5f388b452718a1ce92692c67| cesaltestunautreexemple| Dupont| Henri| - |test4@test.com| 50626fa21f45a275cea0efff13ff78fd02234cade322da08b7191c7e9150141d| NULL| Durand|Isabelle| + |test4@test.com| $2a$12$zFN0VJ..Cuu.2itWQwmHJe5EUhNHazbMfCSJFpNiEfdwpLzjjDM0u| NULL| Durand|Isabelle| + +A noter que le hash de test4 est en bcrypt ``` ### mariadb (sans salt) diff --git a/src/Controller/SecurityController.php b/src/Controller/SecurityController.php index a772c9f..08a0c76 100644 --- a/src/Controller/SecurityController.php +++ b/src/Controller/SecurityController.php @@ -36,9 +36,9 @@ class SecurityController extends AbstractController $loginForm->get('password')->addError(new FormError($trans->trans('error.password', [], 'messages'))); $request->getSession()->remove(SQLLoginUserAuthenticator::ERROR_PASSWORD); } - if ($request->getSession()->has(SQLLoginUserAuthenticator::ERROR_PDO)) { - $loginForm->addError(new FormError($trans->trans('error.pdo', [], 'messages'))); - $request->getSession()->remove(SQLLoginUserAuthenticator::ERROR_PDO); + if ($request->getSession()->has(SQLLoginUserAuthenticator::ERROR_SQL_LOGIN)) { + $loginForm->addError(new FormError($trans->trans('error.sql_login', [], 'messages'))); + $request->getSession()->remove(SQLLoginUserAuthenticator::ERROR_SQL_LOGIN); } } diff --git a/src/DependencyInjection/SQLLoginExtension.php b/src/DependencyInjection/SQLLoginExtension.php index b3216f0..c1fd858 100644 --- a/src/DependencyInjection/SQLLoginExtension.php +++ b/src/DependencyInjection/SQLLoginExtension.php @@ -10,18 +10,18 @@ use Symfony\Component\DependencyInjection\Extension\Extension; class SQLLoginExtension extends Extension implements CompilerPassInterface { /** @var array */ - protected $pdoConfig; + protected $sqlLoginConfig; public function load(array $configs, ContainerBuilder $container) { $configuration = new SQLLoginConfiguration(); $config = $this->processConfiguration($configuration, $configs); - $this->pdoConfig = $config; + $this->sqlLoginConfig = $config; } public function process(ContainerBuilder $container) { $definition = $container->getDefinition(SQLLoginRequest::class); - $definition->replaceArgument('$config', $this->pdoConfig); + $definition->replaceArgument('$config', $this->sqlLoginConfig); } } diff --git a/src/SQLLogin/Exception/InvalidSQLLoginAlgoException.php b/src/SQLLogin/Exception/InvalidSQLLoginAlgoException.php new file mode 100644 index 0000000..ee9ac4c --- /dev/null +++ b/src/SQLLogin/Exception/InvalidSQLLoginAlgoException.php @@ -0,0 +1,9 @@ +password = $password; } - public function getDatabaseDsn() + public function getDatabaseDsn(): string { return $this->dsn; } - public function getDbUser() + public function getDbUser(): string { return $this->user; } - public function getDbPassword() + public function getDbPassword(): string { return $this->password; } - public function getLoginColumnName() + public function getLoginColumnName(): string { return $this->config[self::LOGIN_COLUMN_NAME]; } - public function egtPasswordColumnName() + public function getPasswordColumnName(): string { return $this->config[self::PASSWORD_COLUMN_NAME]; } - public function getSaltColumnName() + public function getSaltColumnName(): ?string { return $this->config[self::SALT_COLUMN_NAME]; } + public function getTableName(): string + { + return $this->config[self::TABLE_NAME]; + } + + public function getDataToFetch(): array + { + return $this->config[self::DATA_TO_FETCH]; + } + public function getRequestScope() { $scope = ''; @@ -60,18 +71,32 @@ class SQLLoginRequest $scope .= $data.','; } $scope = substr($scope, 0, -1); - $request = 'SELECT '.$scope.' FROM '.$this->config[self::TABLE_NAME].' WHERE '.$this->config[self::LOGIN_COLUMN_NAME].' = :'.$this->config[self::LOGIN_COLUMN_NAME].';'; - return $request; + return 'SELECT '.$scope.' FROM '.$this->getTableName().' WHERE '.$this->getLoginColumnName().' = :'.$this->getLoginColumnName().';'; } + /** + * Construction de la string pour la requête préparée selon la configuration yaml + * intègre la récupération du mot de passe hashé, du salt et de besoin d'upgrade de la méthode de hashage + */ public function getRequestPassword() { - $passwordColumns = $this->config[self::PASSWORD_COLUMN_NAME]; - if (!empty($this->config[self::SALT_COLUMN_NAME])) { - $passwordColumns .= ', '.$this->config[self::SALT_COLUMN_NAME]; + $fields = $this->getPasswordColumnName(); + if (!empty($this->getSaltColumnName())) { + $fields .= ', '.$this->getSaltColumnName(); } - return 'SELECT '.$passwordColumns.' FROM '.$this->config[self::TABLE_NAME].' WHERE '.$this->config[self::LOGIN_COLUMN_NAME].' = :'.$this->config[self::LOGIN_COLUMN_NAME].';'; + return 'SELECT '.$fields.' FROM '.$this->getTableName().' WHERE '.$this->getLoginColumnName().' = :'.$this->getLoginColumnName().';'; + } + + public function getRequestUpdatePassword() + { + $fieldsToUpdate = $this->getPasswordColumnName().'= :'.$this->getPasswordColumnName().','; + if (!empty($this->getSaltColumnName())) { + $fieldsToUpdate .= $this->getSaltColumnName().'= :'.$this->getSaltColumnName().','; + } + $fieldsToUpdate = substr($fieldsToUpdate, 0, -1); + + return 'UPDATE '.$this->getTableName().' SET '.$fieldsToUpdate.' WHERE '.$this->getLoginColumnName().' = :'.$this->getLoginColumnName().';'; } } diff --git a/src/Security/Hasher/PasswordEncoder.php b/src/Security/Hasher/PasswordEncoder.php index 7afe9d6..86be74f 100644 --- a/src/Security/Hasher/PasswordEncoder.php +++ b/src/Security/Hasher/PasswordEncoder.php @@ -2,6 +2,8 @@ namespace App\Security\Hasher; +use App\SQLLogin\Exception\InvalidSQLLoginAlgoException; +use App\SQLLogin\Exception\InvalidSQLLoginConfigurationException; use App\SQLLogin\Exception\InvalidSQLPasswordException; use Symfony\Component\PasswordHasher\Exception\InvalidPasswordException; use Symfony\Component\PasswordHasher\Hasher\CheckPasswordLengthTrait; @@ -10,23 +12,37 @@ use Symfony\Component\PasswordHasher\LegacyPasswordHasherInterface; class PasswordEncoder implements LegacyPasswordHasherInterface { use CheckPasswordLengthTrait; - protected ?string $pepper; - protected string $hashAlgo; + public const PASSWORD_PATTERN = 'password'; + public const SALT_PATTERN = 'salt'; + public const PEPPER_PATTERN = 'pepper'; - public function __construct(?string $pepper, string $hashAlgo) + protected ?string $pepper; + protected array $hashAlgoLegacy; + protected ?string $newHashAlgo; + protected array $securityPattern; + + public function __construct(?string $pepper, string $hashAlgoLegacy, ?string $newHashAlgo, string $securityPattern) { $this->pepper = $pepper; - $this->hashAlgo = $hashAlgo; + $this->hashAlgoLegacy = explode(',', $hashAlgoLegacy); + $this->newHashAlgo = $newHashAlgo; + $this->securityPattern = explode(',', $securityPattern); } + /** + * Utilise l'algo legacy + */ public function hash(string $plainPassword, string $salt = null): string { if ($this->isPasswordTooLong($plainPassword)) { throw new InvalidPasswordException(); } - $hash = hash($this->hashAlgo, $plainPassword.$salt.$this->pepper); + $completedPlainPassword = $this->getPasswordToHash($plainPassword, $salt); + if ($this->isObsoleteAlgo($this->newHashAlgo)) { + throw new InvalidSQLLoginAlgoException(); + } - return $hash; + return password_hash($completedPlainPassword, $this->getValidALgo($this->newHashAlgo)); } public function verify(string $hashedPassword, string $plainPassword, string $salt = null): bool @@ -35,7 +51,36 @@ class PasswordEncoder implements LegacyPasswordHasherInterface return false; } - if ($this->hash($plainPassword, $salt) === $hashedPassword) { + $isNewest = password_get_info($hashedPassword)['algo'] === $this->newHashAlgo; + + $completedPassword = $this->getPasswordToHash($plainPassword, $salt); + if ($isNewest) { + if (password_verify($completedPassword, $hashedPassword)) { + return true; + } else { + throw new InvalidSQLPasswordException(); + } + } + + // Si le mot de passe a besoin d'un rehash ou qu'il n'y a pas de nouvelle méthode indiquée, on sait qu'il faut utiliser l'une des méthodes legacy pour vérifier le mot de passe + if ($this->needsRehash($hashedPassword) || empty($this->newHashAlgo)) { + foreach ($this->hashAlgoLegacy as $algo) { + if ($this->isObsoleteAlgo($algo)) { + if (hash_equals(hash($algo, $completedPassword), $hashedPassword)) { + return true; + } + } else { + if (password_verify($completedPassword, $hashedPassword)) { + return true; + } + } + // On vérifie si la méthode legacy est obsolète, si oui on ne peut pas utiliser password_verify, on doit hasher et comparer + } + // si on on n'a pas encore retourné de résultat, le mot de passe doit être incorrect + throw new InvalidSQLPasswordException(); + } + // Si on n'est pas rentré dans les conditions précedéntes, on peut utiliser password_verify + if (password_verify($completedPassword, $hashedPassword)) { return true; } else { throw new InvalidSQLPasswordException(); @@ -44,6 +89,74 @@ class PasswordEncoder implements LegacyPasswordHasherInterface public function needsRehash(string $hashedPassword): bool { + // Il y a besoin de tester si on veut mettre à jour le hashage de mot de passe uniquement si le nouveau algo de hashage est indiqué et qu'il est moderne (BCRYPT, ARGON, ...) + if (!empty($this->newHashAlgo) && !$this->isObsoleteAlgo($this->newHashAlgo)) { + return password_needs_rehash($hashedPassword, $this->getValidAlgo($this->newHashAlgo)); + } + return false; } + + /** + * @param mixed $algo + * + * @return InvalidSQLLoginAlgoException|string + */ + public function getValidAlgo($algo) + { + if ($algo) { + if (in_array($algo, hash_algos())) { + return $algo; + } + $informalAlgos = [ + 'default' => PASSWORD_DEFAULT, + 'bcrypt' => PASSWORD_BCRYPT, + 'argon2i' => PASSWORD_ARGON2I, + 'argon2id' => PASSWORD_ARGON2ID, + ]; + if (in_array($algo, array_keys($informalAlgos))) { + return $informalAlgos[$algo]; + } + if (in_array($algo, password_algos())) { + return $algo; + } + + throw new InvalidSQLLoginAlgoException('Invalid Algorythme'); + } + } + + public function isObsoleteAlgo($algo): bool + { + return in_array($algo, hash_algos()); + } + + /** + * Retourne la string à hasher en fonction du pattern indiqué + * + * @param mixed $plainTextPassword + * @param mixed $salt + * + * @return string + */ + protected function getPasswordToHash($plainTextPassword, $salt) + { + $arrayRef = [ + self::PASSWORD_PATTERN => $plainTextPassword, + self::SALT_PATTERN => $salt, + self::PEPPER_PATTERN => $this->pepper, + ]; + + foreach ($this->securityPattern as $term) { + if (self::PEPPER_PATTERN !== $term && self::PASSWORD_PATTERN !== $term && self::SALT_PATTERN !== $term) { + throw new InvalidSQLLoginConfigurationException(); + } + } + $completedArray = []; + foreach ($this->securityPattern as $term) { + $completedArray[] = $arrayRef[$term]; + } + $completedPlainPassword = implode($completedArray); + + return $completedPlainPassword; + } } diff --git a/src/Security/SQLLoginUserAuthenticator.php b/src/Security/SQLLoginUserAuthenticator.php index 5b38c43..4600adc 100644 --- a/src/Security/SQLLoginUserAuthenticator.php +++ b/src/Security/SQLLoginUserAuthenticator.php @@ -25,17 +25,17 @@ class SQLLoginUserAuthenticator extends AbstractAuthenticator public const LOGIN_ROUTE = 'app_login'; public const ERROR_LOGIN = 'error_login'; public const ERROR_PASSWORD = 'error_password'; - public const ERROR_PDO = 'error_pdo'; + public const ERROR_SQL_LOGIN = 'error_sql_login'; protected string $baseUrl; - private SQLLoginService $pdoService; + private SQLLoginService $sqlLoginService; private UrlGeneratorInterface $router; private PasswordEncoder $passwordHasher; - public function __construct(string $baseUrl, SQLLoginService $pdoService, UrlGeneratorInterface $router, PasswordEncoder $passwordHasher) + public function __construct(string $baseUrl, SQLLoginService $sqlLoginService, UrlGeneratorInterface $router, PasswordEncoder $passwordHasher) { $this->baseUrl = $baseUrl; - $this->pdoService = $pdoService; + $this->sqlLoginService = $sqlLoginService; $this->router = $router; $this->passwordHasher = $passwordHasher; } @@ -72,16 +72,21 @@ class SQLLoginUserAuthenticator extends AbstractAuthenticator $rememberMe = isset($form['_remember_me']) ? true : false; try { // requête préparée - list($remoteHashedPassword, $remoteSalt) = $this->pdoService->fetchPassword($login); + list($remoteHashedPassword, $remoteSalt) = $this->sqlLoginService->fetchPassword($login); } catch (PDOException $e) { - $request->getSession()->set(self::ERROR_PDO, true); + $request->getSession()->set(self::ERROR_SQL_LOGIN, true); throw new AuthenticationException(); } if ($remoteHashedPassword) { try { // Comparaison remote hash et hash du input password + salt + // dump($remoteHashedPassword, $plaintextPassword, $remoteSalt, password_verify($plaintextPassword, $remoteHashedPassword)); $this->passwordHasher->verify($remoteHashedPassword, $plaintextPassword, $remoteSalt); - $attributes = $this->pdoService->fetchDatas($login); + if ($this->passwordHasher->needsRehash($remoteHashedPassword)) { + $hash = $this->passwordHasher->hash($plaintextPassword); + $this->sqlLoginService->updatePassword($login, $hash, null); + } + $attributes = $this->sqlLoginService->fetchDatas($login); $user = new User($login, $remoteHashedPassword, $attributes, $rememberMe); $loader = function (string $userIdentifier) use ($user) { @@ -98,7 +103,7 @@ class SQLLoginUserAuthenticator extends AbstractAuthenticator $request->getSession()->set(self::ERROR_PASSWORD, true); throw new AuthenticationException(); } catch (PDOException $e) { - $request->getSession()->set(self::ERROR_PDO, true); + $request->getSession()->set(self::ERROR_SQL_LOGIN, true); throw new AuthenticationException(); } } diff --git a/src/Security/SQLLoginUserProvider.php b/src/Security/SQLLoginUserProvider.php index cc179de..e5bef7d 100644 --- a/src/Security/SQLLoginUserProvider.php +++ b/src/Security/SQLLoginUserProvider.php @@ -4,7 +4,6 @@ namespace App\Security; use App\Entity\User; use Symfony\Component\HttpFoundation\RequestStack; -use Symfony\Component\HttpFoundation\Session\SessionInterface; use Symfony\Component\Security\Core\Exception\UnsupportedUserException; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserProviderInterface; @@ -13,10 +12,9 @@ class SQLLoginUserProvider implements UserProviderInterface { protected RequestStack $requestStack; - public function __construct(RequestStack $requestStack, SessionInterface $session) + public function __construct(RequestStack $requestStack) { $this->requestStack = $requestStack; - $this->session = $session; } public function loadUserByIdentifier(string $identifier, ?User $user): ?UserInterface diff --git a/src/Service/SQLLoginService.php b/src/Service/SQLLoginService.php index ea97d9f..a6cd637 100644 --- a/src/Service/SQLLoginService.php +++ b/src/Service/SQLLoginService.php @@ -56,12 +56,11 @@ class SQLLoginService extends AbstractController $password = $query->fetch(PDO::FETCH_ASSOC); } catch (PDOException $e) { \Sentry\captureException($e); - dd($e); throw new PDOException(); } if ($password) { return [ - $password[$this->sqlLoginRequest->egtPasswordColumnName()], + $password[$this->sqlLoginRequest->getPasswordColumnName()], isset($password[$this->sqlLoginRequest->getSaltColumnName()]) ? $password[$this->sqlLoginRequest->getSaltColumnName()] : null, ]; } @@ -69,11 +68,28 @@ class SQLLoginService extends AbstractController return false; } + public function updatePassword($login, $hashedPassword, $salt) + { + try { + $dbh = $this->getConnection(); + $request = $this->sqlLoginRequest->getRequestUpdatePassword(); + $query = $dbh->prepare($request); + $query->execute([ + $this->sqlLoginRequest->getLoginColumnName() => $login, + $this->sqlLoginRequest->getPasswordColumnName() => $hashedPassword, + $this->sqlLoginRequest->getSaltColumnName() => $salt, + ]); + } catch (PDOException $e) { + \Sentry\captureException($e); + throw new PDOException(); + } + } + public function getConnection() { // Appel du singleton - $pdo = SQLLoginConnect::getInstance(); + $sqlLogin = SQLLoginConnect::getInstance(); // Connection bdd - return $pdo->connect($this->sqlLoginRequest->getDatabaseDsn(), $this->sqlLoginRequest->getDbUser(), $this->sqlLoginRequest->getDbPassword()); + return $sqlLogin->connect($this->sqlLoginRequest->getDatabaseDsn(), $this->sqlLoginRequest->getDbUser(), $this->sqlLoginRequest->getDbPassword()); } } diff --git a/translations/messages.en.yaml b/translations/messages.en.yaml index 7908f71..cfcf961 100644 --- a/translations/messages.en.yaml +++ b/translations/messages.en.yaml @@ -1,4 +1,4 @@ error: login: 'Incorrect login' password: 'Incorrect password' - pdo: 'Connection to database encountered a problem' \ No newline at end of file + sql_login: 'Connection to database encountered a problem' \ No newline at end of file diff --git a/translations/messages.fr.yaml b/translations/messages.fr.yaml index 6128d20..d9410e2 100644 --- a/translations/messages.fr.yaml +++ b/translations/messages.fr.yaml @@ -1,4 +1,4 @@ error: login: 'Login incorrect ou inconnu' password: 'Mot de passe incorrect' - pdo: 'La connexion à la base de données a rencontré un problème' \ No newline at end of file + sql_login: 'La connexion à la base de données a rencontré un problème' \ No newline at end of file