From 5f38461f1dfbd3f9de3026b09e92db8fb256c7a9 Mon Sep 17 00:00:00 2001 From: Benjamin Cavy Date: Mon, 13 Jan 2025 10:48:43 +0100 Subject: [PATCH] feat: add delay on bad credentials --- app/fr/maif/izanami/web/AuthAction.scala | 13 ++++++++++++- app/fr/maif/izanami/web/LoginController.scala | 10 ++++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/app/fr/maif/izanami/web/AuthAction.scala b/app/fr/maif/izanami/web/AuthAction.scala index e87f2b604..e6bc3b014 100644 --- a/app/fr/maif/izanami/web/AuthAction.scala +++ b/app/fr/maif/izanami/web/AuthAction.scala @@ -15,9 +15,11 @@ import play.api.libs.json._ import play.api.mvc.Results.{BadRequest, Forbidden, Unauthorized} import play.api.mvc._ +import java.time.Duration +import java.util.concurrent.{Executors, TimeUnit} import java.util.{Base64, UUID} import javax.crypto.spec.SecretKeySpec -import scala.concurrent.{ExecutionContext, Future} +import scala.concurrent.{ExecutionContext, Future, Promise} case class UserInformation(username: String, authentication: EventAuthentication) @@ -450,6 +452,15 @@ class ValidatePasswordActionFactory(bodyParser: BodyParser[AnyContent], env: Env } object AuthAction { + private val TIMER = Executors.newSingleThreadScheduledExecutor() + + def delayResponse(result: Result, duration: Duration = Duration.ofSeconds(3)): Future[Result] = { + val promise = Promise[Result]() + TIMER.schedule(() => promise.success(result), duration.toMillis, TimeUnit.MILLISECONDS) + promise.future + } + + def extractClaims[A](request: Request[A], secret: String, bodySecretKey: SecretKeySpec): Option[JwtClaim] = { request.cookies .get("token") diff --git a/app/fr/maif/izanami/web/LoginController.scala b/app/fr/maif/izanami/web/LoginController.scala index 478e98fb2..3f751f0b1 100644 --- a/app/fr/maif/izanami/web/LoginController.scala +++ b/app/fr/maif/izanami/web/LoginController.scala @@ -6,6 +6,7 @@ import fr.maif.izanami.models.OAuth2Configuration.OAuth2BASICMethod import fr.maif.izanami.models.User.userRightsWrites import fr.maif.izanami.models.{OAuth2Configuration, OIDC, Rights, User} import fr.maif.izanami.utils.syntax.implicits.BetterSyntax +import fr.maif.izanami.web.AuthAction.delayResponse import pdi.jwt.{JwtJson, JwtOptions} import play.api.libs.json.JsPath.\ import play.api.libs.json.{JsArray, JsObject, Json} @@ -14,9 +15,10 @@ import play.api.mvc.Cookie.SameSite import play.api.mvc._ import java.security.{MessageDigest, SecureRandom} -import java.util.Base64 +import java.util.concurrent.{Executors, TimeUnit} +import java.util.{Base64, Timer, TimerTask} import scala.concurrent.duration.DurationInt -import scala.concurrent.{ExecutionContext, Future} +import scala.concurrent.{ExecutionContext, Future, Promise} class LoginController( val env: Env, @@ -263,7 +265,7 @@ class LoginController( .filter(arr => arr.length == 2) match { case Some(Array(username, password, _*)) => env.datastores.users.isUserValid(username, password).flatMap { - case None => Future.successful(Forbidden(Json.obj("message" -> "Incorrect credentials"))) + case None => delayResponse(Forbidden(Json.obj("message" -> "Incorrect credentials"))) case Some(user) => for { _ <- if (user.legacy) env.datastores.users.updateLegacyUser(username, password) @@ -285,7 +287,7 @@ class LoginController( ) ) } - case _ => Future(Unauthorized(Json.obj("message" -> "Missing credentials"))) + case _ => delayResponse(Unauthorized(Json.obj("message" -> "Missing credentials"))) } } }