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
|
||||
.idea
|
||||
out
|
||||
target
|
||||
*/.gradle/
|
||||
.bsp
|
||||
keys
|
||||
www
|
||||
log
|
||||
keys
|
||||
|
||||
server.properties
|
||||
|
||||
log
|
||||
*.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
|
||||
|
||||
sealed class AuthException(msg: String) extends Exception(msg) with UserException
|
||||
|
||||
case class InvalidPassword(msg: String) extends AuthException(msg)
|
||||
|
||||
case class InvalidEmail(msg: String) extends AuthException(msg)
|
||||
|
||||
case class UserNotFound(msg: String) extends AuthException(msg)
|
||||
|
||||
case class UserAlreadyRegistered(msg: String) extends AuthException(msg)
|
||||
|
||||
|
||||
case class InvalidName(msg: String) extends AuthException(msg)
|
||||
|
||||
case class InvalidForename(msg: String) extends AuthException(msg)
|
||||
|
||||
import org.tbasket.error.ExceptionEnum
|
||||
|
||||
enum AuthException extends ExceptionEnum {
|
||||
case InvalidPassword
|
||||
case InvalidEmail
|
||||
case UserNotFound
|
||||
case UserAlreadyRegistered
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package org.tbasket.error
|
||||
|
||||
trait ExceptionEnum extends Exception {
|
||||
|
||||
}
|
@ -1,12 +1,10 @@
|
||||
package org.tbasket.error
|
||||
|
||||
sealed class JwtException(msg: String) extends Exception(msg)
|
||||
|
||||
case class InvalidJwt(msg: String) extends JwtException(msg) with UserException
|
||||
|
||||
case class ExpiredJwt(msg: String) extends JwtException(msg) with UserException
|
||||
|
||||
|
||||
case class UnrecognizedEmitterResponse(msg: String) extends JwtException(msg)
|
||||
|
||||
case class EmitterInternalError(msg: String) extends JwtException(msg)
|
||||
enum JwtException extends ExceptionEnum {
|
||||
case InvalidJwt(cause: String)
|
||||
case ExpiredJwt
|
||||
|
||||
|
||||
case InvalidEmitterResponse
|
||||
case EmitterInternalError
|
||||
}
|
@ -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
|
@ -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,36 +1,8 @@
|
||||
package org.tbasket.test
|
||||
|
||||
import com.sun.nio.file.ExtendedOpenOption
|
||||
import io.netty.buffer.ByteBuf
|
||||
|
||||
import java.nio.channels.Pipe
|
||||
import java.nio.file.{Files, Path, StandardOpenOption}
|
||||
|
||||
|
||||
object TestEmitter:
|
||||
val PORT = 5457
|
||||
val PORT = 5455
|
||||
|
||||
def start(): Unit = {
|
||||
println("SETTING UP LOCAL JWT EMITTER FOR TESTS")
|
||||
val emitterPresence = Path.of("/tmp/emitter.presence")
|
||||
Files.deleteIfExists(emitterPresence)
|
||||
|
||||
val process = new ProcessBuilder(
|
||||
"bash",
|
||||
"./mill", "--disable-ticker", "JWTEmitter.run",
|
||||
"-k", "/tmp/keys/key.pcqks",
|
||||
"-p", TestServerConfig.emitterURL.port.get.toString
|
||||
)
|
||||
.inheritIO()
|
||||
.start()
|
||||
Runtime.getRuntime.addShutdownHook(new Thread((() => process.destroy()): Runnable))
|
||||
|
||||
//the emitter will create the /tmp/emitter.presence file once it started, this is to inform us that the server is OP
|
||||
while (Files.notExists(emitterPresence))
|
||||
Thread.sleep(500)
|
||||
if (Files.readString(emitterPresence) == "A") {
|
||||
System.err.println("Emitter did not start successfully")
|
||||
System.exit(1)
|
||||
}
|
||||
Files.delete(emitterPresence)
|
||||
println("EMITTER PRESENCE DETECTED AND INITIALIZED, CONTINUING...")
|
||||
}
|
||||
|
@ -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