From bb14aef019f581a061c76a5e939a1333997330b7 Mon Sep 17 00:00:00 2001 From: Override-6 Date: Sun, 5 Feb 2023 16:29:58 +0100 Subject: [PATCH] added further tests to register page --- Core/src/org/tbasket/auth/Authenticator.scala | 11 +- .../src/org/tbasket/error/AuthException.scala | 8 +- .../org/tbasket/handler/HandlerUtils.scala | 8 +- .../tbasket/handler/RegisterPageHandler.scala | 12 +- tests/src/org/tbasket/test/TestUtils.scala | 13 +- .../test/pages/LoginPageHandlerTests.scala | 117 +++++++++--------- .../test/pages/RegisterPageHandlerTests.scala | 53 ++++++-- 7 files changed, 140 insertions(+), 82 deletions(-) diff --git a/Core/src/org/tbasket/auth/Authenticator.scala b/Core/src/org/tbasket/auth/Authenticator.scala index 69461b8..8da53cb 100644 --- a/Core/src/org/tbasket/auth/Authenticator.scala +++ b/Core/src/org/tbasket/auth/Authenticator.scala @@ -30,6 +30,8 @@ import scala.collection.immutable.HashMap object Authenticator: private final val LOG = LogManager.getLogger("Authentification") private final val ValidMailPattern = "^\\w+([.-]?\\w+)*@\\w+([.-]?\\w+)*(\\.\\w{2,3})+$".r + private final val ValidForenamePattern = "^[a-zA-Z\\u00C0-\\u017F-']+$".r + private final val ValidNamePattern = ValidForenamePattern private final val ValidPasswordPattern = ".{6,}".r case class JwtContent(uuid: UUID) @@ -85,10 +87,15 @@ class Authenticator(url: URL, key: PublicKey, algorithm: JwtAsymmetricAlgorithm) val hash = hashPassword(password) User(uuid, name, forename, hash, mail) } + if (!ValidMailPattern.matches(mail)) - ZIO.fail(InvalidEmail(s"email address did not satisfy regex pattern $ValidMailPattern")) + ZIO.fail(InvalidEmail(s"email address did not satisfy regex pattern")) else if (!ValidPasswordPattern.matches(password)) - ZIO.fail(InvalidPassword(s"password did not satisfy regex pattern $ValidPasswordPattern")) + ZIO.fail(InvalidPassword(s"password did not satisfy regex pattern")) + else if (!ValidForenamePattern.matches(forename)) + ZIO.fail(InvalidForename(s"email address did not satisfy regex pattern")) + else if (!ValidNamePattern.matches(name)) + ZIO.fail(InvalidName(s"password did not satisfy regex pattern")) else for _ <- findByMail(mail).none.orElse(ZIO.fail(UserAlreadyRegistered(s"an email address already exists for '$mail'"))) _ <- run(quote { diff --git a/Core/src/org/tbasket/error/AuthException.scala b/Core/src/org/tbasket/error/AuthException.scala index 24ddf00..5d6898a 100644 --- a/Core/src/org/tbasket/error/AuthException.scala +++ b/Core/src/org/tbasket/error/AuthException.scala @@ -8,4 +8,10 @@ case class InvalidEmail(msg: String) extends AuthException(msg) case class UserNotFound(msg: String) extends AuthException(msg) -case class UserAlreadyRegistered(msg: String) extends AuthException(msg) \ No newline at end of file +case class UserAlreadyRegistered(msg: String) extends AuthException(msg) + + +case class InvalidName(msg: String) extends AuthException(msg) + +case class InvalidForename(msg: String) extends AuthException(msg) + diff --git a/Core/src/org/tbasket/handler/HandlerUtils.scala b/Core/src/org/tbasket/handler/HandlerUtils.scala index 294464a..29394f7 100644 --- a/Core/src/org/tbasket/handler/HandlerUtils.scala +++ b/Core/src/org/tbasket/handler/HandlerUtils.scala @@ -14,13 +14,7 @@ import scala.jdk.CollectionConverters.* import scala.language.reflectiveCalls object HandlerUtils { - def parseAttribute[V, T <: Json {def value: V}](json: Json, name: String, cursor: JsonCursor[Json, T]): Task[V] = - ZIO.fromEither(json.get[T](cursor).map(_.value)) - .mapError(InvalidRequest(s"Missing or invalid field $name.", _)) - - def parseAttributeOpt[V, T <: Json {def value: V}](json: Json, name: String, cursor: JsonCursor[Json, T]) = - ZIO.fromEither(json.get[T](cursor).map(_.value)).option - + def errorBody(errorType: String, msg: String) = Body.fromString(s"""{"error": "$errorType","msg": "$msg"}""") diff --git a/Core/src/org/tbasket/handler/RegisterPageHandler.scala b/Core/src/org/tbasket/handler/RegisterPageHandler.scala index 934418a..11a44b7 100644 --- a/Core/src/org/tbasket/handler/RegisterPageHandler.scala +++ b/Core/src/org/tbasket/handler/RegisterPageHandler.scala @@ -4,7 +4,7 @@ import org.apache.logging.log4j.{LogManager, Logger} import org.tbasket.auth.Authenticator import org.tbasket.data.User import org.tbasket.error.* -import org.tbasket.handler.HandlerUtils.{errorBody, parseAttribute, parseRequestForm, search} +import org.tbasket.handler.HandlerUtils.{errorBody, parseRequestForm, search} import zio.ZIO import zio.http.model.{Cookie, Headers, Status} import zio.http.{Body, Request, Response, model} @@ -38,10 +38,18 @@ object RegisterPageHandler extends PageHandler { status = Status.ExpectationFailed, body = errorBody("invalid email", msg) )) - case InvalidPassword(msg) => ZIO.attempt(Response( + case InvalidPassword(msg) => ZIO.attempt(Response( status = Status.ExpectationFailed, body = errorBody("invalid password", msg) )) + case InvalidName(msg) => ZIO.attempt(Response( + status = Status.ExpectationFailed, + body = errorBody("invalid name", msg) + )) + case InvalidForename(msg) => ZIO.attempt(Response( + status = Status.ExpectationFailed, + body = errorBody("invalid forename", msg) + )) case UserAlreadyRegistered(msg) => ZIO.attempt(Response( status = Status.NotAcceptable, diff --git a/tests/src/org/tbasket/test/TestUtils.scala b/tests/src/org/tbasket/test/TestUtils.scala index bf1ee54..d7c59a7 100644 --- a/tests/src/org/tbasket/test/TestUtils.scala +++ b/tests/src/org/tbasket/test/TestUtils.scala @@ -1,14 +1,23 @@ package org.tbasket.test import io.netty.handler.codec.http.QueryStringEncoder +import org.tbasket.error.InvalidRequest import zio.* import zio.http.{Body, Response} import zio.json.* -import zio.json.ast.Json +import zio.json.ast.{Json, JsonCursor} -import scala.language.implicitConversions +import scala.language.{implicitConversions, reflectiveCalls} object TestUtils { + + def parseAttribute[V, T <: Json {def value: V}](json: Json, cursor: JsonCursor[Json, T]): Task[V] = + ZIO.fromEither(json.get[T](cursor).map(_.value)) + .mapError(InvalidRequest(s"Missing or invalid field $cursor.", _)) + + def parseAttributeOpt[V, T <: Json {def value: V}](json: Json, cursor: JsonCursor[Json, T]) = + ZIO.fromEither(json.get[T](cursor).map(_.value)).option + def getJsonBody(r: Response): Task[Json] = { for body <- r.body.asString diff --git a/tests/src/org/tbasket/test/pages/LoginPageHandlerTests.scala b/tests/src/org/tbasket/test/pages/LoginPageHandlerTests.scala index 3374e74..ace7a54 100644 --- a/tests/src/org/tbasket/test/pages/LoginPageHandlerTests.scala +++ b/tests/src/org/tbasket/test/pages/LoginPageHandlerTests.scala @@ -7,9 +7,8 @@ import org.tbasket.data.{Database, DatabaseContext} import org.tbasket.endpoint.Endpoint import org.tbasket.endpoint.Endpoint.handle import org.tbasket.error.* -import org.tbasket.handler.HandlerUtils.parseAttribute import org.tbasket.handler.LoginPageHandler -import org.tbasket.test.TestUtils.{getJsonBody, makeFormBody} +import org.tbasket.test.TestUtils.* import org.tbasket.test.{TestLayers, TestUtils} import zio.* import zio.http.* @@ -23,65 +22,65 @@ import zio.test.Assertion.* object LoginPageHandlerTests extends TBasketPageSpec("/login") { - - private def requestsSpec = suite("bad request tests")( - ZIO.attempt(Map( - "empty packet" -> Body.empty, - "with no mail attribute" -> makeFormBody("password" -> "bouhours"), - "with no password attribute" -> makeFormBody("email" -> "valid.email@not.very"), - "with invalid form data" -> Body.fromString("""this is a corrupted form data""") - )).map(_.map { case (name, body) => - test(name) { - for - response <- handle(Request.post(body, url)) - json <- getJsonBody(response) - errorType <- parseAttribute(json, "error", JsonCursor.field("error").isString) - yield - assert(response)(hasField("status", _.status, equalTo(Status.BadRequest))) - && assertTrue(errorType == "invalid request") - } - }) - ) - private def loginSpec = { - suite("login situation tests")( - test("login with unknown account") { - for - response <- handle(Request.post(makeFormBody("password" -> "bouhours", "email" -> "unknownaccount@gmail.com"), url)) - json <- getJsonBody(response) - errorType <- parseAttribute(json, "error", JsonCursor.field("error").isString) - yield - //assert that the response error is of type unauthorized and headers are Location: /register - assert(response)(hasField("status", _.status, equalTo(Status.Found))) - && assert(errorType)(equalTo("unauthorized")) - && assert(response)(hasField("headers", _.headers, contains(Headers.location("/register")))) - }, + private def requestsSpec = suite("bad request tests")( + ZIO.attempt(Map( + "empty packet" -> Body.empty, + "with no mail attribute" -> makeFormBody("password" -> "bouhours"), + "with no password attribute" -> makeFormBody("email" -> "valid.email@not.very"), + "with invalid form data" -> Body.fromString("""this is a corrupted form data""") + )).map(_.map { case (name, body) => + test(name) { + for + response <- handle(Request.post(body, url)) + json <- getJsonBody(response) + errorType <- parseAttribute(json, JsonCursor.field("error").isString) + yield + assert(response)(hasField("status", _.status, equalTo(Status.BadRequest))) + && assertTrue(errorType == "invalid request") + } + }) + ) + + private def loginSpec = { + suite("login situation tests")( + test("login with unknown account") { + for + response <- handle(Request.post(makeFormBody("password" -> "bouhours", "email" -> "unknownaccount@gmail.com"), url)) + json <- getJsonBody(response) + errorType <- parseAttribute(json, JsonCursor.field("error").isString) + yield + //assert that the response error is of type unauthorized and headers are Location: /register + assert(response)(hasField("status", _.status, equalTo(Status.Found))) + && assert(errorType)(equalTo("unauthorized")) + && assert(response)(hasField("headers", _.headers, contains(Headers.location("/register")))) + }, - test("login with known account") { - for - response <- handle(Request.post(makeFormBody("password" -> "123456", "email" -> "maximebatista18@gmail.com"), url)) - yield - assert(response)(hasField("status", _.status, equalTo(Status.Found))) - && assert(response)(hasField("body", _.body, equalTo(Body.empty))) //TODO assert that the cookie name is JWT - && assert(response)(hasField("headers", _.headers, exists(hasField("key", _.key, equalTo(HeaderNames.setCookie))))) - }, + test("login with known account") { + for + response <- handle(Request.post(makeFormBody("password" -> "123456", "email" -> "maximebatista18@gmail.com"), url)) + yield + assert(response)(hasField("status", _.status, equalTo(Status.Found))) + && assert(response)(hasField("body", _.body, equalTo(Body.empty))) //TODO assert that the cookie name is JWT + && assert(response)(hasField("headers", _.headers, exists(hasField("key", _.key, equalTo(HeaderNames.setCookie))))) + }, - test("login with known account wrong password") { - for - fiber <- handle(Request.post(makeFormBody("password" -> "wrong", "email" -> "maximebatista18@gmail.com"), url)).fork - _ <- TestClock.adjust(1.seconds) - response <- ZIO.fromFiber(fiber) - json <- getJsonBody(response) - errorType <- parseAttribute(json, "error", JsonCursor.field("error").isString) - yield - assert(response)(hasField("status", _.status, equalTo(Status.Unauthorized))) - && assert(errorType)(equalTo("unauthorized")) - } - ) - } - - override def tspec = suite("/login page handler")( - requestsSpec, - loginSpec + test("login with known account wrong password") { + for + fiber <- handle(Request.post(makeFormBody("password" -> "wrong", "email" -> "maximebatista18@gmail.com"), url)).fork + _ <- TestClock.adjust(1.seconds) + response <- ZIO.fromFiber(fiber) + json <- getJsonBody(response) + errorType <- parseAttribute(json, JsonCursor.field("error").isString) + yield + assert(response)(hasField("status", _.status, equalTo(Status.Unauthorized))) + && assert(errorType)(equalTo("unauthorized")) + } ) + } + + override def tspec = suite("/login page handler")( + requestsSpec, + loginSpec + ) } diff --git a/tests/src/org/tbasket/test/pages/RegisterPageHandlerTests.scala b/tests/src/org/tbasket/test/pages/RegisterPageHandlerTests.scala index b803795..8d1e0be 100644 --- a/tests/src/org/tbasket/test/pages/RegisterPageHandlerTests.scala +++ b/tests/src/org/tbasket/test/pages/RegisterPageHandlerTests.scala @@ -1,7 +1,6 @@ package org.tbasket.test.pages import org.tbasket.endpoint.Endpoint.handle -import org.tbasket.handler.HandlerUtils.{parseAttribute, parseAttributeOpt} import org.tbasket.test.TestUtils import org.tbasket.test.TestUtils.* import org.tbasket.test.pages.RegisterPageHandlerTests.test @@ -9,8 +8,8 @@ import zio.* import zio.http.* import zio.http.model.{HeaderNames, Headers, Status} import zio.json.ast.JsonCursor -import zio.test.* import zio.test.Assertion.* +import zio.test.{assert, *} object RegisterPageHandlerTests extends TBasketPageSpec("/register") { @@ -26,7 +25,7 @@ object RegisterPageHandlerTests extends TBasketPageSpec("/register") { for response <- handle(Request.post(body, url)) json <- getJsonBody(response) - errorType <- parseAttribute(json, "error", JsonCursor.field("error").isString) + errorType <- parseAttribute(json, JsonCursor.field("error").isString) yield assert(response)(hasField("status", _.status, equalTo(Status.BadRequest))) && assertTrue(errorType.startsWith("invalid")) @@ -46,15 +45,51 @@ object RegisterPageHandlerTests extends TBasketPageSpec("/register") { ) *> (for - resp <- handle(Request.post(makeFormBody("name" -> "tuaillon", "forename" -> "leo", "email" -> "leo.tuaillon@etu.uca.fr", "password" -> "bouhours"), url)) + resp <- handle(Request.post(makeFormBody("name" -> "tuaillon", "forename" -> "leo", "email" -> "leo.tuaillon@etu.uca.fr", "password" -> "bouhours"), url)) + json <- getJsonBody(resp) + errorType <- parseAttributeOpt(json, JsonCursor.field("error").isString) + + yield + assert(resp)(hasField("status", _.status, equalTo(Status.NotAcceptable))) + && assert(errorType)(isSome(equalTo("already registered"))) + && assert(resp)(hasField("headers", _.headers, contains(Headers.location("/login"))))) + }, + test("register bad email") { + for + resp <- handle(Request.post(makeFormBody("name" -> "tuaillon", "forename" -> "leo", "email" -> "leo.tuaillonbadmail", "password" -> "bouhours"), url)) + json <- getJsonBody(resp) + errorType <- parseAttributeOpt(json, JsonCursor.field("error").isString) + yield + assert(resp)(hasField("status", _.status, equalTo(Status.ExpectationFailed))) + && assert(errorType)(isSome(equalTo("invalid email"))) + }, + test("register bad password") { + for + resp <- handle(Request.post(makeFormBody("name" -> "tuaillon", "forename" -> "leo", "email" -> "leo.tuaillon@etu.uca.fr", "password" -> "1234"), url)) json <- getJsonBody(resp) - errorType <- parseAttributeOpt(json, "error", JsonCursor.field("error").isString) - + errorType <- parseAttributeOpt(json, JsonCursor.field("error").isString) yield - assert(resp)(hasField("status", _.status, equalTo(Status.NotAcceptable))) - && assert(errorType)(isSome(equalTo("already registered"))) - && assert(resp)(hasField("headers", _.headers, contains(Headers.location("/login"))))) + assert(resp)(hasField("status", _.status, equalTo(Status.ExpectationFailed))) + && assert(errorType)(isSome(equalTo("invalid password"))) }, + test("register bad name") { + for + resp <- handle(Request.post(makeFormBody("name" -> "", "forename" -> "leo", "email" -> "leo.tuaillon@etu.uca.fr", "password" -> "bouhours"), url)) + json <- getJsonBody(resp) + errorType <- parseAttributeOpt(json, JsonCursor.field("error").isString) + yield + assert(resp)(hasField("status", _.status, equalTo(Status.ExpectationFailed))) + && assert(errorType)(isSome(equalTo("invalid name"))) + }, + test("register bad forename") { + for + resp <- handle(Request.post(makeFormBody("name" -> "tuaillon", "forename" -> "", "email" -> "leo.tuaillon@etu.uca.fr", "password" -> "bouhours"), url)) + json <- getJsonBody(resp) + errorType <- parseAttributeOpt(json, JsonCursor.field("error").isString) + yield + assert(resp)(hasField("status", _.status, equalTo(Status.ExpectationFailed))) + && assert(errorType)(isSome(equalTo("invalid forename"))) + } ) override def tspec = suite("/register page handler")(