diff --git a/README.md b/README.md index 06fcb14c..f7b55791 100644 --- a/README.md +++ b/README.md @@ -76,3 +76,8 @@ http://localhost:9000 - Pour lancer le serveur sans docker `sbt run` (Vous pouvez regarder les variables d'environnement indispensables dans le `docker-compose.yml` et la liste des variables dans le `application.conf`) - Les commandes pour le frontend sont dans `package.json` : `npm run watch` (dev), `npm run clean` (supprime ce qui a été installé par `npm install`), `npm run build` (bundle prod) --> + + +## Attribution + +Le projet inclut le fichier `data/french_passwords_top20000.txt` sous licence [Creative Commons Attribution 4.0 International](https://creativecommons.org/licenses/by/4.0/) provenant du dépôt [tarraschk/richelieu](https://github.com/tarraschk/richelieu). diff --git a/app/controllers/LoginController.scala b/app/controllers/LoginController.scala index 9f3771cc..33c478f4 100644 --- a/app/controllers/LoginController.scala +++ b/app/controllers/LoginController.scala @@ -333,7 +333,11 @@ class LoginController @Inject() ( def passwordReinitializationEmailPage: Action[AnyContent] = Action { implicit request => - Ok(views.password.reinitializationEmailPage(PasswordRecovery.form)) + val form = request.session.get(Keys.Session.passwordEmail) match { + case None => PasswordRecovery.form + case Some(email) => PasswordRecovery.form.fill(PasswordRecovery(email)) + } + Ok(views.password.reinitializationEmailPage(form)) } def passwordReinitializationEmail: Action[AnyContent] = @@ -377,7 +381,7 @@ class LoginController @Inject() ( views.password .reinitializationEmailPage(form, errorMessage = (title, description).some) ) - .withSession(request.session - Keys.Session.passwordEmail) + .removingFromSession(Keys.Session.passwordEmail) }, expiration => { eventService.logSystem( diff --git a/app/helper/PlayFormHelpers.scala b/app/helper/PlayFormHelpers.scala index 1f18f109..fd77c3ba 100644 --- a/app/helper/PlayFormHelpers.scala +++ b/app/helper/PlayFormHelpers.scala @@ -20,6 +20,7 @@ object PlayFormHelpers { _.map(commonStringInputNormalization).filter(_.nonEmpty) ) + // Source: https://github.com/tarraschk/richelieu val commonPasswords: Set[String] = scala.io.Source.fromFile("data/french_passwords_top20000.txt").getLines().map(_.trim).toSet @@ -30,7 +31,7 @@ object PlayFormHelpers { .verifying(maxLength(1000)) .verifying( "Le mot de passe ne peut pas commencer ou terminer par une espace " + - "(Cependant, les espaces sont autorisés à l’intérieur du mot de passe).", + "(Cependant, les espaces sont autorisées à l’intérieur du mot de passe).", password => password.trim === password ) .verifying( diff --git a/app/services/PasswordService.scala b/app/services/PasswordService.scala index 04024eea..3df44b7c 100644 --- a/app/services/PasswordService.scala +++ b/app/services/PasswordService.scala @@ -52,7 +52,6 @@ class PasswordService @Inject() ( private def generateRandomToken(): String = MiscHelpers.secureRandom.alphanumeric.take(30).mkString - // TODO: rate limit the recovery emails def sendRecoverEmail(email: String, ipAddress: String): Future[Either[Error, Instant]] = userService .byEmailEither(email, includeDisabled = true) @@ -137,6 +136,7 @@ class PasswordService @Inject() ( token.expirationDate case nonExpiredTokens => val lastToken = nonExpiredTokens.sortBy(_.creationDate).reverse.head + // This rate-limits the recovery emails at 1 per 30 seconds if (lastToken.creationDate.plusSeconds(30).isBefore(Instant.now())) { val _ = notificationService.newPasswordRecoveryLinkEmail( user.name, @@ -218,10 +218,12 @@ class PasswordService @Inject() ( val _ = SQL"""INSERT INTO password ( user_id, password_hash, + creation_date, last_update ) VALUES ( ${userId}::uuid, ${hash}, + ${now}, ${now} ) ON CONFLICT (user_id) diff --git a/conf/evolutions/default/76.sql b/conf/evolutions/default/76.sql index b6fd54c2..395a4c6a 100644 --- a/conf/evolutions/default/76.sql +++ b/conf/evolutions/default/76.sql @@ -3,14 +3,15 @@ CREATE TABLE password ( user_id uuid PRIMARY KEY, password_hash varchar(10000) NOT NULL, - last_update timestamp NOT NULL + creation_date timestamptz NOT NULL, + last_update timestamptz NOT NULL ); CREATE TABLE password_recovery_token ( token varchar(100) NOT NULL PRIMARY KEY, user_id uuid NOT NULL, - creation_date timestamp NOT NULL, - expiration_date timestamp NOT NULL, + creation_date timestamptz NOT NULL, + expiration_date timestamptz NOT NULL, ip_address inet NOT NULL, used boolean DEFAULT false NOT NULL );