Compare commits
24 Commits
drone-setu
...
dev
Author | SHA1 | Date |
---|---|---|
![]() |
e15ed2a7f8 | 2 years ago |
![]() |
8e99789947 | 2 years ago |
![]() |
8d78d1f0a7 | 2 years ago |
![]() |
cf3e03035f | 2 years ago |
![]() |
1b44d8734d | 2 years ago |
![]() |
33287f8d33 | 2 years ago |
![]() |
89a684f692 | 2 years ago |
![]() |
45810fc368 | 2 years ago |
![]() |
26a090952c | 2 years ago |
![]() |
bb14aef019 | 2 years ago |
![]() |
f3782f492d | 2 years ago |
![]() |
ceb53e5abb | 2 years ago |
![]() |
7b3be31271 | 2 years ago |
![]() |
b22a1230c7 | 2 years ago |
![]() |
c9f45d1bf4 | 2 years ago |
![]() |
8aa3c0a39a | 2 years ago |
![]() |
c8247bd998 | 2 years ago |
|
f7e35a21cb | 2 years ago |
![]() |
debb61e14c | 2 years ago |
![]() |
a3065f64af | 2 years ago |
![]() |
9c9917ff9f | 2 years ago |
![]() |
4b80964de5 | 2 years ago |
![]() |
fdfc3cbac1 | 2 years ago |
![]() |
a20ba55248 | 2 years ago |
@ -1,13 +1,15 @@
|
|||||||
build
|
build
|
||||||
.idea
|
.idea
|
||||||
out
|
out
|
||||||
|
target
|
||||||
*/.gradle/
|
*/.gradle/
|
||||||
.bsp
|
.bsp
|
||||||
keys
|
keys
|
||||||
|
www
|
||||||
|
log
|
||||||
|
|
||||||
server.properties
|
server.properties
|
||||||
|
|
||||||
log
|
|
||||||
*.sqlite
|
*.sqlite
|
||||||
|
|
||||||
*.$*
|
*.$*
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
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) {}
|
|
@ -1,7 +0,0 @@
|
|||||||
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) {}
|
|
@ -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
|
* tag interface for page handlers
|
@ -0,0 +1,57 @@
|
|||||||
|
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)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,57 @@
|
|||||||
|
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
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
package org.tbasket.endpoint.auth
|
||||||
|
|
||||||
|
import zio.http.Request
|
||||||
|
|
||||||
|
object 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,10 +1,17 @@
|
|||||||
package org.tbasket.error
|
package org.tbasket.error
|
||||||
|
|
||||||
import org.tbasket.error.ExceptionEnum
|
sealed class AuthException(msg: String) extends Exception(msg) with UserException
|
||||||
|
|
||||||
enum AuthException extends ExceptionEnum {
|
case class InvalidPassword(msg: String) extends AuthException(msg)
|
||||||
case InvalidPassword
|
|
||||||
case InvalidEmail
|
case class InvalidEmail(msg: String) extends AuthException(msg)
|
||||||
case UserNotFound
|
|
||||||
case UserAlreadyRegistered
|
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)
|
||||||
|
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
package org.tbasket.error
|
|
||||||
|
|
||||||
trait ExceptionEnum extends Exception {
|
|
||||||
|
|
||||||
}
|
|
@ -1,10 +1,12 @@
|
|||||||
package org.tbasket.error
|
package org.tbasket.error
|
||||||
|
|
||||||
enum JwtException extends ExceptionEnum {
|
sealed class JwtException(msg: String) extends Exception(msg)
|
||||||
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)
|
||||||
|
@ -0,0 +1,17 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
|||||||
package org.tbasket.error
|
|
||||||
|
|
||||||
enum RegularException extends ExceptionEnum {
|
|
||||||
case InternalError(cause: Throwable)
|
|
||||||
case InvalidArgumentError(cause: String)
|
|
||||||
|
|
||||||
|
|
||||||
case InvalidRequest(msg: String, cause: String)
|
|
||||||
}
|
|
@ -0,0 +1,10 @@
|
|||||||
|
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
|
||||||
|
}
|
@ -1,18 +0,0 @@
|
|||||||
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"}""")
|
|
||||||
}
|
|
@ -1,95 +0,0 @@
|
|||||||
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,40 +0,0 @@
|
|||||||
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 => ???
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -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
|
|
@ -1,3 +1,9 @@
|
|||||||
DIR=$(readlink -e "$(dirname "$0")")
|
DIR=$(readlink -e "$(dirname "$0")")
|
||||||
|
|
||||||
|
echo "starting emitter"
|
||||||
|
java -jar "$DIR/JWTEmitter-prod.jar"&
|
||||||
|
|
||||||
|
|
||||||
echo "starting server"
|
echo "starting server"
|
||||||
java -jar "$DIR/server-prod.jar"
|
java -jar "$DIR/server-prod.jar"
|
||||||
|
|
||||||
|
@ -1,53 +0,0 @@
|
|||||||
{
|
|
||||||
"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,8 +1,36 @@
|
|||||||
package org.tbasket.test
|
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:
|
object TestEmitter:
|
||||||
val PORT = 5455
|
val PORT = 5457
|
||||||
|
|
||||||
|
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...")
|
||||||
|
}
|
||||||
|
@ -0,0 +1,28 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
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)
|
||||||
|
}
|
@ -0,0 +1,87 @@
|
|||||||
|
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
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,100 @@
|
|||||||
|
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
|
||||||
|
)
|
||||||
|
}
|
@ -1,87 +0,0 @@
|
|||||||
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