Compare commits
16 Commits
dev
...
drone-setu
Author | SHA1 | Date |
---|---|---|
![]() |
8692ed55c3 | 2 years ago |
![]() |
b94b4493e2 | 2 years ago |
![]() |
d7c57f06fa | 2 years ago |
![]() |
023f847b06 | 2 years ago |
![]() |
708c3308d5 | 2 years ago |
![]() |
01eebf9bc1 | 2 years ago |
![]() |
b1380d2d5a | 2 years ago |
![]() |
7b7eed885d | 2 years ago |
![]() |
c4fae28f43 | 2 years ago |
![]() |
87accaf7a2 | 2 years ago |
![]() |
21249c1b82 | 2 years ago |
![]() |
05288f089f | 2 years ago |
![]() |
a0652bb3d0 | 2 years ago |
![]() |
2c5b54d5ff | 2 years ago |
![]() |
fb83280c79 | 2 years ago |
![]() |
a4196fae1b | 2 years ago |
@ -1,15 +1,13 @@
|
|||||||
build
|
build
|
||||||
.idea
|
.idea
|
||||||
out
|
out
|
||||||
target
|
|
||||||
*/.gradle/
|
*/.gradle/
|
||||||
.bsp
|
.bsp
|
||||||
keys
|
keys
|
||||||
www
|
|
||||||
log
|
|
||||||
|
|
||||||
server.properties
|
server.properties
|
||||||
|
|
||||||
|
log
|
||||||
*.sqlite
|
*.sqlite
|
||||||
|
|
||||||
*.$*
|
*.$*
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
package org.tbasket
|
||||||
|
|
||||||
|
/**
|
||||||
|
* exception thrown when an error occurs due to an external fault from the server.
|
||||||
|
*/
|
||||||
|
class ExternalBasketServerException(msg: String, cause: Throwable = null)
|
||||||
|
extends Exception(msg, cause) {}
|
@ -0,0 +1,7 @@
|
|||||||
|
package org.tbasket
|
||||||
|
|
||||||
|
/**
|
||||||
|
* exception thrown when an error occurs due to an internal fault of the server or its maintainers.
|
||||||
|
*/
|
||||||
|
class InternalBasketServerException(msg: String, cause: Throwable = null)
|
||||||
|
extends Exception(msg, cause) {}
|
@ -1,29 +0,0 @@
|
|||||||
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,57 +0,0 @@
|
|||||||
package org.tbasket.endpoint.auth
|
|
||||||
|
|
||||||
import io.getquill.*
|
|
||||||
import io.getquill.context.ZioJdbc.*
|
|
||||||
import io.getquill.context.qzio.{ZioContext, ZioJdbcContext}
|
|
||||||
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 zio.*
|
|
||||||
import zio.http.*
|
|
||||||
import zio.http.model.{Cookie, Header, Headers, Status}
|
|
||||||
import zio.json.*
|
|
||||||
import zio.json.ast.Json.Str
|
|
||||||
import zio.json.ast.{Json, JsonCursor}
|
|
||||||
|
|
||||||
import java.sql.SQLException
|
|
||||||
import java.util.UUID
|
|
||||||
|
|
||||||
|
|
||||||
object LoginHandler extends PageHandler:
|
|
||||||
|
|
||||||
implicit private final val Log: Logger = LogManager.getLogger("/login")
|
|
||||||
|
|
||||||
private def tryLogin(request: Request) =
|
|
||||||
for
|
|
||||||
request <- decode[LoginRequest](request.body)
|
|
||||||
user <- ZIO.serviceWithZIO[Authenticator](_.loginUser(request.email, request.password))
|
|
||||||
|
|
||||||
jwt <- ZIO.serviceWithZIO[Authenticator](_.requestNewJwt(user))
|
|
||||||
yield Response(
|
|
||||||
status = Status.Found,
|
|
||||||
headers = Headers.location("/") ++ //login successful, go back to main page
|
|
||||||
Headers.setCookie(Cookie("JWT", jwt)) //and set the token cookie
|
|
||||||
)
|
|
||||||
|
|
||||||
def login(request: Request) =
|
|
||||||
tryLogin(request).catchSome {
|
|
||||||
case UserNotFound(msg) => ZIO.attempt(Response(
|
|
||||||
status = Status.Found,
|
|
||||||
body = errorBody("unauthorized", msg),
|
|
||||||
headers =
|
|
||||||
Headers(
|
|
||||||
Headers.location("/register")
|
|
||||||
) // send back caller to register panel
|
|
||||||
))
|
|
||||||
|
|
||||||
case InvalidPassword(msg) => ZIO.attempt(Response(
|
|
||||||
status = Status.Unauthorized,
|
|
||||||
body = errorBody("unauthorized", msg)
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
|||||||
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 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 RegisterHandler extends PageHandler {
|
|
||||||
|
|
||||||
|
|
||||||
private def tryRegister(request: Request) =
|
|
||||||
for
|
|
||||||
request <- decode[RegisterRequest](request.body)
|
|
||||||
|
|
||||||
user <- ZIO.serviceWithZIO[Authenticator](_.registerUser(request.name, request.forename, request.email, request.password))
|
|
||||||
jwt <- ZIO.serviceWithZIO[Authenticator](_.requestNewJwt(user))
|
|
||||||
yield Response(
|
|
||||||
status = Status.Found,
|
|
||||||
headers = Headers.location("/") ++ //register successful, go back to main page
|
|
||||||
Headers.setCookie(Cookie("JWT", jwt))
|
|
||||||
)
|
|
||||||
|
|
||||||
def register(request: Request) = tryRegister(request)
|
|
||||||
.catchSome {
|
|
||||||
case InvalidEmail(msg) => ZIO.attempt(Response(
|
|
||||||
status = Status.ExpectationFailed,
|
|
||||||
body = errorBody("invalid email", msg)
|
|
||||||
))
|
|
||||||
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,
|
|
||||||
body = errorBody("already registered", msg),
|
|
||||||
headers = Headers.location("/login") //the account already exists so we move the user to login page
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
package org.tbasket.endpoint.auth
|
|
||||||
|
|
||||||
import zio.http.Request
|
|
||||||
|
|
||||||
object TokenLoginHandler {
|
|
||||||
|
|
||||||
def login(request: Request) = {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
package org.tbasket.endpoint.request
|
|
||||||
|
|
||||||
|
|
||||||
case class RegisterRequest(name: String, forename: String, email: String, password: String)
|
|
||||||
|
|
||||||
case class LoginRequest(email: String, password: String)
|
|
@ -1,17 +1,10 @@
|
|||||||
package org.tbasket.error
|
package org.tbasket.error
|
||||||
|
|
||||||
sealed class AuthException(msg: String) extends Exception(msg) with UserException
|
import org.tbasket.error.ExceptionEnum
|
||||||
|
|
||||||
case class InvalidPassword(msg: String) extends AuthException(msg)
|
enum AuthException extends ExceptionEnum {
|
||||||
|
case InvalidPassword
|
||||||
case class InvalidEmail(msg: String) extends AuthException(msg)
|
case InvalidEmail
|
||||||
|
case UserNotFound
|
||||||
case class UserNotFound(msg: String) extends AuthException(msg)
|
case UserAlreadyRegistered
|
||||||
|
}
|
||||||
case class UserAlreadyRegistered(msg: String) extends AuthException(msg)
|
|
||||||
|
|
||||||
|
|
||||||
case class InvalidName(msg: String) extends AuthException(msg)
|
|
||||||
|
|
||||||
case class InvalidForename(msg: String) extends AuthException(msg)
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
|||||||
|
package org.tbasket.error
|
||||||
|
|
||||||
|
trait ExceptionEnum extends Exception {
|
||||||
|
|
||||||
|
}
|
@ -1,12 +1,10 @@
|
|||||||
package org.tbasket.error
|
package org.tbasket.error
|
||||||
|
|
||||||
sealed class JwtException(msg: String) extends Exception(msg)
|
enum JwtException extends ExceptionEnum {
|
||||||
|
case InvalidJwt(cause: String)
|
||||||
|
case ExpiredJwt
|
||||||
|
|
||||||
case class InvalidJwt(msg: String) extends JwtException(msg) with UserException
|
|
||||||
|
|
||||||
case class ExpiredJwt(msg: String) extends JwtException(msg) with UserException
|
case InvalidEmitterResponse
|
||||||
|
case EmitterInternalError
|
||||||
|
}
|
||||||
case class UnrecognizedEmitterResponse(msg: String) extends JwtException(msg)
|
|
||||||
|
|
||||||
case class EmitterInternalError(msg: String) extends JwtException(msg)
|
|
@ -1,17 +0,0 @@
|
|||||||
package org.tbasket.error
|
|
||||||
|
|
||||||
import org.apache.logging.log4j.{LogManager, Logger}
|
|
||||||
import zio.http.Response
|
|
||||||
import zio.http.model.Status
|
|
||||||
import zio.{Task, ZIO}
|
|
||||||
|
|
||||||
sealed class RegularException(msg: String, cause: Throwable = null) extends Exception(msg, cause)
|
|
||||||
|
|
||||||
case class InternalError(cause: Throwable) extends Exception(cause)
|
|
||||||
|
|
||||||
case class InvalidArgumentError(cause: String) extends Exception(cause)
|
|
||||||
|
|
||||||
|
|
||||||
case class InvalidRequest(msg: String, cause: String = "") extends Exception(msg + (if cause.nonEmpty then ":" + cause else "")) with UserException
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
|||||||
|
package org.tbasket.error
|
||||||
|
|
||||||
|
enum RegularException extends ExceptionEnum {
|
||||||
|
case InternalError(cause: Throwable)
|
||||||
|
case InvalidArgumentError(cause: String)
|
||||||
|
|
||||||
|
|
||||||
|
case InvalidRequest(msg: String, cause: String)
|
||||||
|
}
|
@ -1,10 +0,0 @@
|
|||||||
package org.tbasket.error
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User exceptions are a special type of exception where stack trace is not filled as those exceptions are not meant to be printed in the console
|
|
||||||
* because they are predictable error.
|
|
||||||
*
|
|
||||||
* */
|
|
||||||
trait UserException extends Exception {
|
|
||||||
override def fillInStackTrace(): Throwable = this //do not fill stack trace
|
|
||||||
}
|
|
@ -0,0 +1,18 @@
|
|||||||
|
package org.tbasket.handler
|
||||||
|
|
||||||
|
import org.tbasket.error.RegularException.InvalidRequest
|
||||||
|
import zio.{Task, ZIO}
|
||||||
|
import zio.http.Body
|
||||||
|
import zio.json.*
|
||||||
|
import zio.json.ast.{Json, JsonCursor}
|
||||||
|
|
||||||
|
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 errorBody(errorType: String, msg: String) = Body.fromString(s"""{"error": "$errorType","msg": "$msg"}""")
|
||||||
|
}
|
@ -0,0 +1,95 @@
|
|||||||
|
package org.tbasket.handler
|
||||||
|
|
||||||
|
import io.getquill.*
|
||||||
|
import io.getquill.context.ZioJdbc.*
|
||||||
|
import io.getquill.context.qzio.{ZioContext, ZioJdbcContext}
|
||||||
|
import io.getquill.context.sql.idiom.SqlIdiom
|
||||||
|
import org.apache.logging.log4j.LogManager
|
||||||
|
import org.tbasket.auth.Authenticator
|
||||||
|
import org.tbasket.data.{DatabaseContext, User}
|
||||||
|
import org.tbasket.error.AuthException.*
|
||||||
|
import org.tbasket.error.ExceptionEnum
|
||||||
|
import org.tbasket.error.JwtException.*
|
||||||
|
import org.tbasket.error.RegularException.*
|
||||||
|
import org.tbasket.handler.HandlerUtils.errorBody
|
||||||
|
import zio.http.*
|
||||||
|
import zio.http.model.{Cookie, Header, Headers, Status}
|
||||||
|
import zio.json.*
|
||||||
|
import zio.json.ast.Json.Str
|
||||||
|
import zio.json.ast.{Json, JsonCursor}
|
||||||
|
import zio.*
|
||||||
|
|
||||||
|
import java.sql.SQLException
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
|
||||||
|
object LoginPageHandler extends PageHandler:
|
||||||
|
|
||||||
|
private val LOG = LogManager.getLogger("Login")
|
||||||
|
|
||||||
|
private def getUser(json: Json) =
|
||||||
|
ZIO.serviceWithZIO[Authenticator] { auth =>
|
||||||
|
for
|
||||||
|
mail <- HandlerUtils.parseAttribute(json, "email", JsonCursor.field("email").isString)
|
||||||
|
password <- HandlerUtils.parseAttribute(json, "password", JsonCursor.field("password").isString)
|
||||||
|
user <- auth.loginUser(mail, password)
|
||||||
|
yield user
|
||||||
|
}
|
||||||
|
|
||||||
|
private def tryPost(request: Request) =
|
||||||
|
for
|
||||||
|
body <- request
|
||||||
|
.body
|
||||||
|
.asString
|
||||||
|
.mapError(s =>
|
||||||
|
InvalidRequest("Invalid request body", s.getMessage)
|
||||||
|
)
|
||||||
|
|
||||||
|
json <- ZIO.fromEither(body.fromJson[Json])
|
||||||
|
.mapError(InvalidRequest("Invalid JSON body", _))
|
||||||
|
|
||||||
|
user <- getUser(json)
|
||||||
|
jwt <- ZIO.serviceWithZIO[Authenticator](_.requestNewJwt(user))
|
||||||
|
yield Response(
|
||||||
|
status = Status.Found,
|
||||||
|
headers = Headers.location("/") ++ //login successful, go back to main page
|
||||||
|
Headers.setCookie(Cookie("JWT", jwt)) //and set the token cookie
|
||||||
|
)
|
||||||
|
|
||||||
|
def post(request: Request) =
|
||||||
|
tryPost(request).catchAll {
|
||||||
|
case UserNotFound => ZIO.attempt(Response(
|
||||||
|
status = Status.Unauthorized,
|
||||||
|
body = errorBody("unauthorized", "unknown user email"),
|
||||||
|
headers =
|
||||||
|
Headers(
|
||||||
|
Headers.location("/register")
|
||||||
|
) // send back caller to register panel
|
||||||
|
))
|
||||||
|
|
||||||
|
case InvalidPassword => ZIO.attempt(Response(
|
||||||
|
status = Status.Unauthorized,
|
||||||
|
body = errorBody("unauthorized", "invalid password")
|
||||||
|
))
|
||||||
|
|
||||||
|
case InvalidRequest(msg, cause) => ZIO.attempt(Response(
|
||||||
|
status = Status.Unauthorized,
|
||||||
|
body = errorBody("invalid request", s"$cause: $msg")
|
||||||
|
))
|
||||||
|
|
||||||
|
case InternalError(e) =>
|
||||||
|
LOG.error("Internal error : ")
|
||||||
|
LOG.throwing(e)
|
||||||
|
ZIO.attempt(Response(
|
||||||
|
status = Status.InternalServerError,
|
||||||
|
body = errorBody("internal", "internal error, please contact support")
|
||||||
|
))
|
||||||
|
|
||||||
|
case other =>
|
||||||
|
LOG.error("Unhandle exception : ")
|
||||||
|
LOG.throwing(other)
|
||||||
|
ZIO.attempt(Response(
|
||||||
|
status = Status.InternalServerError,
|
||||||
|
body = errorBody("internal", "internal error, please contact support")
|
||||||
|
))
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package org.tbasket.endpoint
|
package org.tbasket.handler
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* tag interface for page handlers
|
* tag interface for page handlers
|
@ -0,0 +1,40 @@
|
|||||||
|
package org.tbasket.handler
|
||||||
|
|
||||||
|
import org.tbasket.auth.Authenticator
|
||||||
|
import org.tbasket.data.User
|
||||||
|
import org.tbasket.error.RegularException.InvalidRequest
|
||||||
|
import org.tbasket.handler.HandlerUtils.parseAttribute
|
||||||
|
import zio.ZIO
|
||||||
|
import zio.http.model.{Cookie, Headers, Status}
|
||||||
|
import zio.http.{Request, Response, model}
|
||||||
|
import zio.json.ast.{Json, JsonCursor}
|
||||||
|
import zio.json.*
|
||||||
|
|
||||||
|
object RegisterPageHandler extends PageHandler {
|
||||||
|
|
||||||
|
private def tryPost(request: Request) =
|
||||||
|
for
|
||||||
|
body <- request.body.asString
|
||||||
|
.mapError(e => InvalidRequest("Invalid request body", e.getMessage))
|
||||||
|
|
||||||
|
json <- ZIO.fromEither(body.fromJson[Json])
|
||||||
|
.mapError(InvalidRequest("Invalid JSON body", _))
|
||||||
|
|
||||||
|
name <- parseAttribute(json, "name", JsonCursor.field("name").isString)
|
||||||
|
forename <- parseAttribute(json, "forename", JsonCursor.field("forename").isString)
|
||||||
|
mail <- parseAttribute(json, "email", JsonCursor.field("email").isString)
|
||||||
|
password <- parseAttribute(json, "password", JsonCursor.field("password").isString)
|
||||||
|
|
||||||
|
user <- ZIO.serviceWithZIO[Authenticator](_.registerUser(name, forename, mail, password))
|
||||||
|
jwt <- ZIO.serviceWithZIO[Authenticator](_.requestNewJwt(user))
|
||||||
|
yield Response(
|
||||||
|
status = Status.Found,
|
||||||
|
headers = Headers.location("/") ++ //register successful, go back to main page
|
||||||
|
Headers.setCookie(Cookie("JWT", jwt))
|
||||||
|
)
|
||||||
|
|
||||||
|
def post(request: Request) = tryPost(request).catchSome {
|
||||||
|
case x => ???
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
|
||||||
|
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
|
@ -0,0 +1,53 @@
|
|||||||
|
{
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"name" : "Test Task name not available here/\/login page handler/login situation tests/login with unknown account",
|
||||||
|
"status" : "Success",
|
||||||
|
"durationMillis" : "4103",
|
||||||
|
"annotations" : "",
|
||||||
|
"fullyQualifiedClassName" : "Test Task name not available here",
|
||||||
|
"labels" : ["\/login page handler", "login situation tests", "login with unknown account"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name" : "Test Task name not available here/\/login page handler/login situation tests/login with known account",
|
||||||
|
"status" : "Failure",
|
||||||
|
"durationMillis" : "1",
|
||||||
|
"annotations" : "",
|
||||||
|
"fullyQualifiedClassName" : "Test Task name not available here",
|
||||||
|
"labels" : ["\/login page handler", "login situation tests", "login with known account"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name" : "Test Task name not available here/\/login page handler/erroned request body tests/with no password attribute",
|
||||||
|
"status" : "Success",
|
||||||
|
"durationMillis" : "2681",
|
||||||
|
"annotations" : "",
|
||||||
|
"fullyQualifiedClassName" : "Test Task name not available here",
|
||||||
|
"labels" : ["\/login page handler", "erroned request body tests", "with no password attribute"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name" : "Test Task name not available here/\/login page handler/erroned request body tests/empty packet",
|
||||||
|
"status" : "Success",
|
||||||
|
"durationMillis" : "2697",
|
||||||
|
"annotations" : "",
|
||||||
|
"fullyQualifiedClassName" : "Test Task name not available here",
|
||||||
|
"labels" : ["\/login page handler", "erroned request body tests", "empty packet"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name" : "Test Task name not available here/\/login page handler/erroned request body tests/with no mail attribute",
|
||||||
|
"status" : "Success",
|
||||||
|
"durationMillis" : "2702",
|
||||||
|
"annotations" : "",
|
||||||
|
"fullyQualifiedClassName" : "Test Task name not available here",
|
||||||
|
"labels" : ["\/login page handler", "erroned request body tests", "with no mail attribute"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name" : "Test Task name not available here/\/login page handler/erroned request body tests/with invalid json",
|
||||||
|
"status" : "Success",
|
||||||
|
"durationMillis" : "2717",
|
||||||
|
"annotations" : "",
|
||||||
|
"fullyQualifiedClassName" : "Test Task name not available here",
|
||||||
|
"labels" : ["\/login page handler", "erroned request body tests", "with invalid json"]
|
||||||
|
}
|
||||||
|
|
||||||
|
]
|
||||||
|
}
|
@ -1,28 +0,0 @@
|
|||||||
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, JsonCursor}
|
|
||||||
|
|
||||||
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
|
|
||||||
json <- ZIO.fromEither(body.fromJson[Json]).orElseSucceed(Json.Obj())
|
|
||||||
yield json
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
package org.tbasket.test.endpoint
|
|
||||||
|
|
||||||
import io.getquill.NamingStrategy
|
|
||||||
import io.getquill.context.qzio.ZioJdbcContext
|
|
||||||
import io.getquill.context.sql.idiom.SqlIdiom
|
|
||||||
import org.tbasket.auth.Authenticator
|
|
||||||
import org.tbasket.data.DatabaseContext
|
|
||||||
import org.tbasket.endpoint.auth.LoginHandler.login
|
|
||||||
import org.tbasket.test.TestLayers.*
|
|
||||||
import zio.*
|
|
||||||
import zio.http.netty.client.ConnectionPool
|
|
||||||
import zio.http.{Client, ClientConfig, URL}
|
|
||||||
import zio.test.{Spec, TestEnvironment, ZIOSpecDefault}
|
|
||||||
|
|
||||||
import javax.sql.DataSource
|
|
||||||
|
|
||||||
abstract class TBasketPageSpec(location: String) extends ZIOSpecDefault {
|
|
||||||
|
|
||||||
protected val url = URL.fromString(s"http://localhost$location") match
|
|
||||||
case Left(exception) => throw exception
|
|
||||||
case Right(url) => url
|
|
||||||
|
|
||||||
protected def tspec: Spec[
|
|
||||||
DataSource & ClientConfig & Authenticator & ConnectionPool &
|
|
||||||
Scope & DatabaseContext & Client, Any
|
|
||||||
]
|
|
||||||
|
|
||||||
final override def spec = tspec.provide(
|
|
||||||
db.datasourceLayer,
|
|
||||||
db.contextLayer,
|
|
||||||
auth,
|
|
||||||
ConnectionPool.fixed(1),
|
|
||||||
Scope.default,
|
|
||||||
ClientConfig.default,
|
|
||||||
Client.live)
|
|
||||||
}
|
|
@ -1,87 +0,0 @@
|
|||||||
package org.tbasket.test.endpoint.auth
|
|
||||||
|
|
||||||
import io.getquill.jdbczio.Quill
|
|
||||||
import io.getquill.{SnakeCase, SqliteZioJdbcContext}
|
|
||||||
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.test.TestUtils.*
|
|
||||||
import org.tbasket.test.endpoint.TBasketPageSpec
|
|
||||||
import org.tbasket.test.{TestLayers, TestUtils}
|
|
||||||
import zio.*
|
|
||||||
import zio.http.*
|
|
||||||
import zio.http.model.Headers.{Header, empty}
|
|
||||||
import zio.http.model.{HeaderNames, Headers, Status}
|
|
||||||
import zio.http.netty.client.ConnectionPool
|
|
||||||
import zio.json.*
|
|
||||||
import zio.json.ast.{Json, JsonCursor}
|
|
||||||
import zio.test.*
|
|
||||||
import zio.test.Assertion.*
|
|
||||||
|
|
||||||
object LoginHandlerTests extends TBasketPageSpec("/auth/login") {
|
|
||||||
|
|
||||||
|
|
||||||
private def requestsSpec = suite("bad request tests")(
|
|
||||||
ZIO.attempt(Map(
|
|
||||||
"empty packet" -> Body.empty,
|
|
||||||
"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
|
|
||||||
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(Body.fromString("""{"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(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)))
|
|
||||||
&& assert(response)(hasField("headers", _.headers, exists(hasField("key", _.key, equalTo(HeaderNames.setCookie)))))
|
|
||||||
},
|
|
||||||
|
|
||||||
test("login with known account wrong password") {
|
|
||||||
for
|
|
||||||
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)
|
|
||||||
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
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,100 +0,0 @@
|
|||||||
package org.tbasket.test.endpoint.auth
|
|
||||||
|
|
||||||
import org.tbasket.endpoint.Endpoint.handle
|
|
||||||
import org.tbasket.test.TestUtils
|
|
||||||
import org.tbasket.test.TestUtils.*
|
|
||||||
import org.tbasket.test.endpoint.TBasketPageSpec
|
|
||||||
import org.tbasket.test.endpoint.auth.RegisterHandlerTests.test
|
|
||||||
import zio.*
|
|
||||||
import zio.http.*
|
|
||||||
import zio.http.model.{HeaderNames, Headers, Status}
|
|
||||||
import zio.json.ast.JsonCursor
|
|
||||||
import zio.test.*
|
|
||||||
import zio.test.Assertion.*
|
|
||||||
|
|
||||||
object RegisterHandlerTests extends TBasketPageSpec("/auth/register") {
|
|
||||||
|
|
||||||
|
|
||||||
private def requestsSpec = suite("bad request tests")(
|
|
||||||
ZIO.attempt(Map( //TODO test all wrong combinations
|
|
||||||
"empty packet" -> Body.empty,
|
|
||||||
"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
|
|
||||||
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.startsWith("invalid"))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
private def registerSpec = suite("register tests")(
|
|
||||||
test("register then try register again") {
|
|
||||||
(for
|
|
||||||
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
|
|
||||||
&& assert(resp)(hasField("headers", _.headers, exists(hasField("key", _.key, equalTo(HeaderNames.setCookie)))))
|
|
||||||
&& assert(resp)(hasField("headers", _.headers, contains(Headers.location("/"))))
|
|
||||||
)
|
|
||||||
*>
|
|
||||||
(for
|
|
||||||
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)
|
|
||||||
|
|
||||||
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(Body.fromString("""{"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(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
|
|
||||||
assert(resp)(hasField("status", _.status, equalTo(Status.ExpectationFailed)))
|
|
||||||
&& assert(errorType)(isSome(equalTo("invalid password")))
|
|
||||||
},
|
|
||||||
test("register bad name") {
|
|
||||||
for
|
|
||||||
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
|
|
||||||
assert(resp)(hasField("status", _.status, equalTo(Status.ExpectationFailed)))
|
|
||||||
&& assert(errorType)(isSome(equalTo("invalid name")))
|
|
||||||
},
|
|
||||||
test("register bad forename") {
|
|
||||||
for
|
|
||||||
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
|
|
||||||
assert(resp)(hasField("status", _.status, equalTo(Status.ExpectationFailed)))
|
|
||||||
&& assert(errorType)(isSome(equalTo("invalid forename")))
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
override def tspec = suite("/register page handler")(
|
|
||||||
requestsSpec,
|
|
||||||
registerSpec
|
|
||||||
)
|
|
||||||
}
|
|
@ -0,0 +1,87 @@
|
|||||||
|
package org.tbasket.test.pages
|
||||||
|
|
||||||
|
import io.getquill.jdbczio.Quill
|
||||||
|
import io.getquill.{SnakeCase, SqliteZioJdbcContext}
|
||||||
|
import org.tbasket.auth.Authenticator
|
||||||
|
import org.tbasket.data.{Database, DatabaseContext}
|
||||||
|
import org.tbasket.error.RegularException.InvalidRequest
|
||||||
|
import org.tbasket.handler.HandlerUtils.parseAttribute
|
||||||
|
import org.tbasket.handler.LoginPageHandler
|
||||||
|
import org.tbasket.handler.LoginPageHandler.post
|
||||||
|
import org.tbasket.test.TestLayers
|
||||||
|
import org.tbasket.test.pages.LoginPageHandlerTests.test
|
||||||
|
import zio.*
|
||||||
|
import zio.http.netty.client.ConnectionPool
|
||||||
|
import zio.http.*
|
||||||
|
import zio.http.model.{HeaderNames, Headers}
|
||||||
|
import zio.http.model.Headers.Header
|
||||||
|
import zio.json.*
|
||||||
|
import zio.json.ast.{Json, JsonCursor}
|
||||||
|
import zio.test.*
|
||||||
|
import zio.test.Assertion.*
|
||||||
|
|
||||||
|
object LoginPageHandlerTests extends ZIOSpecDefault {
|
||||||
|
|
||||||
|
import LoginPageHandler.post
|
||||||
|
import TestLayers.*
|
||||||
|
|
||||||
|
private def getJsonBody(r: Response): Task[Json] = {
|
||||||
|
for
|
||||||
|
body <- r.body.asString
|
||||||
|
json <- ZIO.fromEither(body.fromJson[Json]).mapError(new Exception(_))
|
||||||
|
yield json
|
||||||
|
}
|
||||||
|
|
||||||
|
private def requestsSpec = suite("erroned request body tests")(
|
||||||
|
ZIO.attempt(Map(
|
||||||
|
"empty packet" -> Body.empty,
|
||||||
|
"with no mail attribute" -> Body.fromString("""{"password":"1234"}"""),
|
||||||
|
"with no password attribute" -> Body.fromString("""{"email":"valid.mail@x.y"}"""),
|
||||||
|
"with invalid json" -> Body.fromString("""this is a corrupted json""")
|
||||||
|
)).map(_.map((name, body) =>
|
||||||
|
test(name) {
|
||||||
|
for
|
||||||
|
response <- post(Request.post(body, URL.empty))
|
||||||
|
json <- getJsonBody(response)
|
||||||
|
errorType <- parseAttribute(json, "error", JsonCursor.field("error").isString)
|
||||||
|
yield assertTrue(errorType == "invalid request")
|
||||||
|
}
|
||||||
|
))
|
||||||
|
)
|
||||||
|
|
||||||
|
private def loginSpec = {
|
||||||
|
suite("login situation tests")(
|
||||||
|
test("login with unknown account") {
|
||||||
|
for
|
||||||
|
response <- post(Request.post(Body.fromString("""{"password":"123456","email":"unknownaccount@gmail.com"}"""), URL.empty))
|
||||||
|
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(errorType)(equalTo("unauthorized"))
|
||||||
|
&& assert(response)(hasField("headers", _.headers, hasSameElements(Headers.location("/register"))))
|
||||||
|
},
|
||||||
|
|
||||||
|
test("login with known account") {
|
||||||
|
for
|
||||||
|
response <- post(Request.post(Body.fromString("""{"password":"123456","email":"maximebatista18@gmail.com"}"""), URL.empty))
|
||||||
|
json <- getJsonBody(response)
|
||||||
|
//errorType <- parseAttribute(json, "error", JsonCursor.field("error").isString)
|
||||||
|
yield
|
||||||
|
assert(response)(hasField("headers", _.headers, exists(hasField("key", _.key, equalTo(HeaderNames.setCookie)))))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override def spec = suite("/login page handler")(
|
||||||
|
requestsSpec,
|
||||||
|
loginSpec
|
||||||
|
).provide(
|
||||||
|
db.datasourceLayer,
|
||||||
|
db.contextLayer,
|
||||||
|
auth,
|
||||||
|
ConnectionPool.fixed(1),
|
||||||
|
Scope.default,
|
||||||
|
ClientConfig.default,
|
||||||
|
Client.live)
|
||||||
|
}
|
Reference in new issue