modification in tests, anticipating further development
continuous-integration/drone/push Build is passing Details

dev
Override-6 2 years ago
parent 1b44d8734d
commit cf3e03035f

@ -11,8 +11,8 @@ import org.tbasket.data.DatabaseContext
import org.tbasket.dispatch.StaticWebService
import org.tbasket.endpoint.Endpoint.{Log, app}
import org.tbasket.error.*
import org.tbasket.handler.HandlerUtils.errorBody
import org.tbasket.handler.{HandlerUtils, LoginPageHandler, RegisterPageHandler}
import EndpointUtils.errorBody
import org.tbasket.endpoint.auth.{LoginHandler, RegisterHandler}
import zio.*
import zio.http.*
import zio.http.ServerConfig.LeakDetectionLevel
@ -51,10 +51,10 @@ object Endpoint:
private def tryHandle(r: Request) = r match
case r@POST -> _ / "login" =>
LoginPageHandler.post(r)
LoginHandler.login(r)
case r@POST -> _ / "register" =>
RegisterPageHandler.post(r)
RegisterHandler.register(r)
case r@GET -> _ =>

@ -0,0 +1,29 @@
package org.tbasket.endpoint
import io.netty.handler.codec.http.QueryStringDecoder
import org.tbasket.error.*
import zio.http.Body
import zio.http.api.openapi.OpenAPI.Parameter.QueryStyle.Form
import zio.json.*
import zio.json.ast.{Json, JsonCursor}
import zio.{Task, ZIO, http}
import java.net.URLDecoder
import java.nio.charset.StandardCharsets
import scala.deriving.Mirror
import scala.jdk.CollectionConverters.*
import scala.language.{implicitConversions, reflectiveCalls}
object EndpointUtils {
def errorBody(errorType: String, msg: String) = Body.fromString(s"""{"error": "$errorType","msg": "$msg"}""")
inline def decode[A <: Product](body: Body)(using mirror: Mirror.Of[A]) =
for
str <- body.asString
decoded <- ZIO.fromEither(str.fromJson(DeriveJsonDecoder.gen[A]))
.mapError(InvalidRequest("Invalid request body", _))
yield decoded
}

@ -1,4 +1,4 @@
package org.tbasket.handler
package org.tbasket.endpoint
/**
* tag interface for page handlers

@ -1,4 +1,4 @@
package org.tbasket.handler
package org.tbasket.endpoint.auth
import io.getquill.*
import io.getquill.context.ZioJdbc.*
@ -7,8 +7,10 @@ import io.getquill.context.sql.idiom.SqlIdiom
import org.apache.logging.log4j.{LogManager, Logger}
import org.tbasket.auth.Authenticator
import org.tbasket.data.{DatabaseContext, User}
import org.tbasket.endpoint.EndpointUtils.{decode, errorBody}
import org.tbasket.endpoint.PageHandler
import org.tbasket.endpoint.request.LoginRequest
import org.tbasket.error.*
import org.tbasket.handler.HandlerUtils.*
import zio.*
import zio.http.*
import zio.http.model.{Cookie, Header, Headers, Status}
@ -20,24 +22,14 @@ import java.sql.SQLException
import java.util.UUID
object LoginPageHandler extends PageHandler:
object LoginHandler extends PageHandler:
implicit private final val Log: Logger = LogManager.getLogger("/login")
private def getUser(params: Map[String, String]) =
ZIO.serviceWithZIO[Authenticator] { auth =>
val zio = for
mail <- search(params)("email")
password <- search(params)("password")
user <- auth.loginUser(mail, password)
yield user
zio
}
private def tryPost(request: Request) =
private def tryLogin(request: Request) =
for
params <- parseRequestForm(request.body)
user <- getUser(params)
request <- decode[LoginRequest](request.body)
user <- ZIO.serviceWithZIO[Authenticator](_.loginUser(request.email, request.password))
jwt <- ZIO.serviceWithZIO[Authenticator](_.requestNewJwt(user))
yield Response(
@ -46,8 +38,8 @@ object LoginPageHandler extends PageHandler:
Headers.setCookie(Cookie("JWT", jwt)) //and set the token cookie
)
def post(request: Request) =
tryPost(request).catchSome {
def login(request: Request) =
tryLogin(request).catchSome {
case UserNotFound(msg) => ZIO.attempt(Response(
status = Status.Found,
body = errorBody("unauthorized", msg),
@ -62,3 +54,4 @@ object LoginPageHandler extends PageHandler:
body = errorBody("unauthorized", msg)
))
}

@ -1,30 +1,26 @@
package org.tbasket.handler
package org.tbasket.endpoint.auth
import org.apache.logging.log4j.{LogManager, Logger}
import org.tbasket.auth.Authenticator
import org.tbasket.data.User
import org.tbasket.endpoint.EndpointUtils.*
import org.tbasket.endpoint.PageHandler
import org.tbasket.endpoint.request.RegisterRequest
import org.tbasket.error.*
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}
import zio.json.*
import zio.json.ast.{Json, JsonCursor}
object RegisterPageHandler extends PageHandler {
object RegisterHandler extends PageHandler {
implicit private final val Log: Logger = LogManager.getLogger("/register")
private def tryPost(request: Request) =
private def tryRegister(request: Request) =
for
params <- parseRequestForm(request.body)
request <- decode[RegisterRequest](request.body)
name <- search(params)("name")
forename <- search(params)("forename")
mail <- search(params)("email")
password <- search(params)("password")
user <- ZIO.serviceWithZIO[Authenticator](_.registerUser(name, forename, mail, password))
user <- ZIO.serviceWithZIO[Authenticator](_.registerUser(request.name, request.forename, request.email, request.password))
jwt <- ZIO.serviceWithZIO[Authenticator](_.requestNewJwt(user))
yield Response(
status = Status.Found,
@ -32,7 +28,7 @@ object RegisterPageHandler extends PageHandler {
Headers.setCookie(Cookie("JWT", jwt))
)
def post(request: Request) = tryPost(request)
def register(request: Request) = tryRegister(request)
.catchSome {
case InvalidEmail(msg) => ZIO.attempt(Response(
status = Status.ExpectationFailed,

@ -0,0 +1,11 @@
package org.tbasket.endpoint.auth
import zio.http.Request
class TokenLoginHandler {
def login(request: Request) = {
}
}

@ -0,0 +1,6 @@
package org.tbasket.endpoint.request
case class RegisterRequest(name: String, forename: String, email: String, password: String)
case class LoginRequest(email: String, password: String)

@ -1,31 +0,0 @@
package org.tbasket.handler
import io.netty.handler.codec.http.QueryStringDecoder
import org.tbasket.error.*
import zio.http.Body
import zio.http.api.openapi.OpenAPI.Parameter.QueryStyle.Form
import zio.json.*
import zio.json.ast.{Json, JsonCursor}
import zio.{Task, ZIO}
import java.net.URLDecoder
import java.nio.charset.StandardCharsets
import scala.jdk.CollectionConverters.*
import scala.language.reflectiveCalls
object HandlerUtils {
def errorBody(errorType: String, msg: String) = Body.fromString(s"""{"error": "$errorType","msg": "$msg"}""")
def parseRequestForm(body: Body) =
(for
decoded <- body.asString.map(str => URLDecoder.decode(str, StandardCharsets.UTF_8.name()))
params = decoded.split('&').collect { case s"$k=$v" => (k.stripPrefix("?"), v)}.toMap
yield params)
.mapError(s => InvalidRequest("Invalid request body", s.getMessage))
def search(map: Map[String, String])(name: String) =
ZIO.attempt(map.get(name)).someOrFail(InvalidRequest(s"Missing or invalid field $name."))
}

@ -1,27 +0,0 @@
FILES_COMMITTED=$(git diff --name-only HEAD^)
IMAGES_COMMITTED=$(echo "$FILES_COMMITTED" | grep -E "^.*\.dockerfile$")
REPOSITORY="hub.codefirst.iut.uca.fr/maxime.batista/codefirst-docdeployer"
dockerd
push_image() ({
local IMAGE_NAME=$(basename "$1" | rev | cut -d . -f2- | rev)
cd "$(dirname "$1")"
docker login -u "$DOCKER_USERNAME" -p "$DOCKER_PASSWORD" "$REPOSITORY"
echo "Building $IMAGE_NAME..."
docker build -f "$(basename "$1")" .
docker tag "$IMAGE_NAME" "$REPOSITORY/$IMAGE_NAME:latest"
echo "Pushing $IMAGE_NAME..."
docker push "$REPOSITORY/$IMAGE_NAME"
})
echo $IMAGES_COMMITTED
for IMAGE in $IMAGES_COMMITTED; do
push_image "$IMAGE"
done

@ -25,10 +25,4 @@ object TestUtils {
yield json
}
implicit def makeFormBody(params: (String, String)*): Body = {
val qse = new QueryStringEncoder("")
params.foreach(qse.addParam(_, _))
Body.fromString(qse.toString)
}
}

@ -6,8 +6,8 @@ import org.tbasket.auth.Authenticator
import org.tbasket.data.{Database, DatabaseContext}
import org.tbasket.endpoint.Endpoint
import org.tbasket.endpoint.Endpoint.handle
import org.tbasket.endpoint.auth.LoginHandler
import org.tbasket.error.*
import org.tbasket.handler.LoginPageHandler
import org.tbasket.test.TestUtils.*
import org.tbasket.test.{TestLayers, TestUtils}
import zio.*
@ -26,9 +26,9 @@ 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""")
"with no mail attribute" -> Body.fromString("""{"password":"bouhours"}"""),
"with no password attribute" -> Body.fromString("""{"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
@ -46,7 +46,7 @@ object LoginPageHandlerTests extends TBasketPageSpec("/login") {
suite("login situation tests")(
test("login with unknown account") {
for
response <- handle(Request.post(makeFormBody("password" -> "bouhours", "email" -> "unknownaccount@gmail.com"), url))
response <- handle(Request.post(Body.fromString("""{"password":"bouhours","email":"unknownaccount@gmail.com"}"""), url))
json <- getJsonBody(response)
errorType <- parseAttribute(json, JsonCursor.field("error").isString)
yield
@ -58,16 +58,16 @@ object LoginPageHandlerTests extends TBasketPageSpec("/login") {
test("login with known account") {
for
response <- handle(Request.post(makeFormBody("password" -> "123456", "email" -> "maximebatista18@gmail.com"), url))
response <- handle(Request.post(Body.fromString("""{"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("body", _.body, equalTo(Body.empty)))
&& 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
fiber <- handle(Request.post(Body.fromString("""{"password":"wrong","email":"maximebatista18@gmail.com"}"""), url)).fork
_ <- TestClock.adjust(1.seconds)
response <- ZIO.fromFiber(fiber)
json <- getJsonBody(response)

@ -17,9 +17,9 @@ object RegisterPageHandlerTests extends TBasketPageSpec("/register") {
private def requestsSpec = suite("bad request tests")(
ZIO.attempt(Map( //TODO test all wrong combinations
"empty packet" -> Body.empty,
"with no mail attribute" -> makeFormBody("password" -> "123445678"),
"with no password attribute" -> makeFormBody("email" -> "valid.mail@x.y"),
"with invalid form data" -> Body.fromString("""this is a corrupted form data""")
"with no mail attribute" -> Body.fromString("""{"password": "123445678"}"""),
"with no password attribute" -> Body.fromString("""{"email":"valid.mail@x.y"}"""),
"with invalid form data" -> Body.fromString("""{this is a corrupted form data}""")
)).map(_.map { case (name, body) =>
test(name) {
for
@ -36,7 +36,7 @@ object RegisterPageHandlerTests extends TBasketPageSpec("/register") {
private def registerSpec = suite("register tests")(
test("register then try register again") {
(for
resp <- handle(Request.post(makeFormBody("name" -> "tuaillon", "forename" -> "leo", "email" -> "leo.tuaillon@etu.uca.fr", "password" -> "bouhours"), url))
resp <- handle(Request.post(Body.fromString("""{"name":"tuaillon","forename":"leo","email":"leo.tuaillon@etu.uca.fr","password":"bouhours"}"""), url))
yield
assert(resp)(hasField("status", _.status, equalTo(Status.Found)))
&& assert(resp)(hasField("body", _.body, equalTo(Body.empty))) //TODO assert that the cookie name is JWT
@ -45,7 +45,7 @@ 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(Body.fromString("""{"name":"tuaillon","forename":"leo","email":"leo.tuaillon@etu.uca.fr","password":"bouhours"}"""), url))
json <- getJsonBody(resp)
errorType <- parseAttributeOpt(json, JsonCursor.field("error").isString)
@ -56,7 +56,7 @@ object RegisterPageHandlerTests extends TBasketPageSpec("/register") {
},
test("register bad email") {
for
resp <- handle(Request.post(makeFormBody("name" -> "tuaillon", "forename" -> "leo", "email" -> "leo.tuaillonbadmail", "password" -> "bouhours"), url))
resp <- handle(Request.post(Body.fromString("""{"name":"tuaillon","forename":"leo","email":"leo.tuaillonbadmail","password":"bouhours"}"""), url))
json <- getJsonBody(resp)
errorType <- parseAttributeOpt(json, JsonCursor.field("error").isString)
yield
@ -65,7 +65,7 @@ object RegisterPageHandlerTests extends TBasketPageSpec("/register") {
},
test("register bad password") {
for
resp <- handle(Request.post(makeFormBody("name" -> "tuaillon", "forename" -> "leo", "email" -> "leo.tuaillon@etu.uca.fr", "password" -> "1234"), url))
resp <- handle(Request.post(Body.fromString("""{"name":"tuaillon","forename":"leo","email":"leo.tuaillon@etu.uca.fr","password":"1234"}"""), url))
json <- getJsonBody(resp)
errorType <- parseAttributeOpt(json, JsonCursor.field("error").isString)
yield
@ -74,7 +74,7 @@ object RegisterPageHandlerTests extends TBasketPageSpec("/register") {
},
test("register bad name") {
for
resp <- handle(Request.post(makeFormBody("name" -> "", "forename" -> "leo", "email" -> "leo.tuaillon@etu.uca.fr", "password" -> "bouhours"), url))
resp <- handle(Request.post(Body.fromString("""{"name":"","forename":"leo","email":"leo.tuaillon@etu.uca.fr","password":"123456"}"""), url))
json <- getJsonBody(resp)
errorType <- parseAttributeOpt(json, JsonCursor.field("error").isString)
yield
@ -83,7 +83,7 @@ object RegisterPageHandlerTests extends TBasketPageSpec("/register") {
},
test("register bad forename") {
for
resp <- handle(Request.post(makeFormBody("name" -> "tuaillon", "forename" -> "", "email" -> "leo.tuaillon@etu.uca.fr", "password" -> "bouhours"), url))
resp <- handle(Request.post(Body.fromString("""{"name":"tuaillon","forename":"","email":"leo.tuaillon@etu.uca.fr","password":"bouhours"}"""), url))
json <- getJsonBody(resp)
errorType <- parseAttributeOpt(json, JsonCursor.field("error").isString)
yield

@ -6,7 +6,7 @@ import io.getquill.context.sql.idiom.SqlIdiom
import org.tbasket.auth.Authenticator
import org.tbasket.data.DatabaseContext
import org.tbasket.dispatch.StaticWebService
import org.tbasket.handler.LoginPageHandler.post
import org.tbasket.endpoint.auth.LoginHandler.login
import org.tbasket.test.TestLayers.*
import zio.*
import zio.http.netty.client.ConnectionPool
@ -18,7 +18,7 @@ import javax.sql.DataSource
abstract class TBasketPageSpec(location: String) extends ZIOSpecDefault {
protected val url = URL.fromString(s"http://localhost$location") match
case Left(value) => throw value
case Left(exception) => throw exception
case Right(url) => url
protected def tspec: Spec[DataSource & ClientConfig & Authenticator & ConnectionPool & Scope & DatabaseContext & Client & StaticWebService, Any]