working on backend draft
continuous-integration/drone/push Build is failing
Details
continuous-integration/drone/push Build is failing
Details
parent
e5cd7b897a
commit
1650d472d1
@ -0,0 +1,79 @@
|
|||||||
|
package org.tbasket.auth
|
||||||
|
|
||||||
|
import io.circe.*
|
||||||
|
import io.circe.generic.auto.*
|
||||||
|
import io.circe.parser.*
|
||||||
|
import io.circe.syntax.*
|
||||||
|
import io.getquill.*
|
||||||
|
import io.getquill.context.qzio.ZioJdbcContext
|
||||||
|
import org.tbasket.InternalBasketServerException
|
||||||
|
import org.tbasket.db.schemas.User
|
||||||
|
import pdi.jwt.algorithms.JwtAsymmetricAlgorithm
|
||||||
|
import pdi.jwt.{JwtClaim, JwtZIOJson}
|
||||||
|
import zio.*
|
||||||
|
import zio.http.*
|
||||||
|
import zio.http.api.HttpCodec.Method
|
||||||
|
import zio.http.model.Status.{InternalServerError, Ok}
|
||||||
|
import zio.http.model.Version.Http_1_1
|
||||||
|
import zio.http.model.{Headers, Method}
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream
|
||||||
|
import java.security.PublicKey
|
||||||
|
import java.util.UUID
|
||||||
|
import javax.sql.DataSource
|
||||||
|
import scala.collection.immutable.HashMap
|
||||||
|
|
||||||
|
enum AuthentificatorError:
|
||||||
|
case ExpiredToken
|
||||||
|
case InvalidEmitterResponse
|
||||||
|
|
||||||
|
case class JwtContent(uuid: UUID)
|
||||||
|
|
||||||
|
class Authentificator(url: URL, key: PublicKey, algorithm: JwtAsymmetricAlgorithm) {
|
||||||
|
|
||||||
|
private def defineCustomClaims(user: User): String = {
|
||||||
|
JwtContent(user.id).asJson.noSpaces.toString
|
||||||
|
}
|
||||||
|
|
||||||
|
private def mkRequest(user: User): Request = {
|
||||||
|
val custom = defineCustomClaims(user)
|
||||||
|
Request(Body.fromString(custom), Headers.empty, Method.GET, url, Http_1_1, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
def requestJwt(user: User) = {
|
||||||
|
Client.request(mkRequest(user))
|
||||||
|
.flatMap {
|
||||||
|
case Response(Ok, _, body, _, _) =>
|
||||||
|
body.asString
|
||||||
|
case Response(InternalServerError, _, _, _, _) =>
|
||||||
|
ZIO.fail(new InternalBasketServerException("internal server error"))
|
||||||
|
case _ =>
|
||||||
|
ZIO.fail(new InternalBasketServerException("Received unknown response from emitter"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def validateAndGetUser(jwt: String) = {
|
||||||
|
for
|
||||||
|
//decoding token
|
||||||
|
claims <- ZIO.fromTry(JwtZIOJson.decode(jwt, key, Seq(algorithm)))
|
||||||
|
//ensure that the token is not expired (or else fail)
|
||||||
|
_ <- ZIO.attempt(claims.expiration)
|
||||||
|
.someOrFail("Received invalid jwt token (missing expiration date)")
|
||||||
|
.filterOrFail(_ <= java.lang.System.currentTimeMillis())("Expired token")
|
||||||
|
//
|
||||||
|
uuid <- ZIO.attempt(claims.content)
|
||||||
|
.mapAttempt(decode[JwtContent](_))
|
||||||
|
.flatMap(ZIO.fromEither(_))
|
||||||
|
.map(_.uuid)
|
||||||
|
|
||||||
|
user <- ZIO.serviceWithZIO[ZioJdbcContext[_,_]] { ds =>
|
||||||
|
import org.tbasket.db.Database.ctx.*
|
||||||
|
|
||||||
|
run(quote {
|
||||||
|
query[User].filter(_.id == lift(uuid))
|
||||||
|
}).map(_.headOption)
|
||||||
|
.someOrFail("uuid not found.")
|
||||||
|
}
|
||||||
|
yield user
|
||||||
|
}
|
||||||
|
}
|
@ -1,23 +0,0 @@
|
|||||||
package org.tbasket.auth
|
|
||||||
|
|
||||||
import io.netty.channel.ChannelHandlerContext
|
|
||||||
import org.tbasket.db.schemas.User
|
|
||||||
import pdi.jwt.JwtClaim
|
|
||||||
import zio.http.{Client, Request, URL}
|
|
||||||
import zio.*
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream
|
|
||||||
|
|
||||||
class JWTClient(url: URL) {
|
|
||||||
|
|
||||||
private def mkRequest(user: User): JwtClaim = {
|
|
||||||
Request.get(url)
|
|
||||||
.body
|
|
||||||
.write(new ByteArrayInputStream(Array.emptyByteArray))
|
|
||||||
}
|
|
||||||
|
|
||||||
def requestJwt(user: User): Task[JwtClaim] = {
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,60 @@
|
|||||||
|
package org.tbasket.endpoint
|
||||||
|
|
||||||
|
import io.getquill.{Literal, NamingStrategy, SqliteDialect}
|
||||||
|
import io.getquill.context.qzio.ZioContext
|
||||||
|
import org.apache.logging.log4j.LogManager
|
||||||
|
import org.tbasket.auth.Authentificator
|
||||||
|
import org.tbasket.endpoint.Endpoint.LOG
|
||||||
|
import org.tbasket.handler.LoginHandler
|
||||||
|
import zio.*
|
||||||
|
import zio.http.*
|
||||||
|
import zio.http.ServerConfig.LeakDetectionLevel
|
||||||
|
import zio.http.model.Method.{GET, POST}
|
||||||
|
import zio.http.model.Status
|
||||||
|
|
||||||
|
import scala.collection.mutable
|
||||||
|
import zio.http.netty.client.ConnectionPool
|
||||||
|
import io.getquill.idiom.Idiom
|
||||||
|
import javax.sql.DataSource
|
||||||
|
|
||||||
|
class Endpoint(port: Int):
|
||||||
|
|
||||||
|
|
||||||
|
// set generic required headers
|
||||||
|
private def applyGenerics(response: Response): Response =
|
||||||
|
response.withAccessControlAllowOrigin("*")
|
||||||
|
|
||||||
|
private val app = Http.collectZIO[Request] {
|
||||||
|
case r@POST -> _ / "login" =>
|
||||||
|
LoginHandler.post(r)
|
||||||
|
|
||||||
|
case r@method -> path =>
|
||||||
|
val ipInsights = r.remoteAddress
|
||||||
|
.map(ip => s": request received from $ip.")
|
||||||
|
.getOrElse("")
|
||||||
|
LOG.error(
|
||||||
|
s"Was unable to find a handler for request '$path' with method $method ${ipInsights}"
|
||||||
|
)
|
||||||
|
ZIO.succeed(Response(Status.NotFound))
|
||||||
|
}.map(applyGenerics)
|
||||||
|
|
||||||
|
val run =
|
||||||
|
val config = ServerConfig.default
|
||||||
|
.port(port)
|
||||||
|
.leakDetection(LeakDetectionLevel.PARANOID)
|
||||||
|
|
||||||
|
val serverConfigLayer = ServerConfig.live(config)
|
||||||
|
Server.install(app).flatMap { port =>
|
||||||
|
LOG.info(s"Listening API entries on $port")
|
||||||
|
ZIO.never
|
||||||
|
}.provideSome[ZioContext[Idiom, NamingStrategy] & Authentificator](
|
||||||
|
Scope.default,
|
||||||
|
serverConfigLayer,
|
||||||
|
ConnectionPool.fixed(4),
|
||||||
|
ClientConfig.default,
|
||||||
|
Server.live,
|
||||||
|
Client.live
|
||||||
|
)
|
||||||
|
|
||||||
|
object Endpoint:
|
||||||
|
final val LOG = LogManager.getLogger("API")
|
@ -1,21 +0,0 @@
|
|||||||
package org.tbasket.handler
|
|
||||||
|
|
||||||
import org.tbasket.Main
|
|
||||||
import org.tbasket.api.compute.APIRequestHandler
|
|
||||||
import zio.http.{Request, Response}
|
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
|
||||||
|
|
||||||
object IncrementHandler extends APIRequestHandler:
|
|
||||||
|
|
||||||
private val counter = new AtomicInteger(0)
|
|
||||||
|
|
||||||
def getCounter: Int = counter.get()
|
|
||||||
|
|
||||||
override def get(request: Request): Response =
|
|
||||||
Response.json(s"{\"value\": ${counter.get()}}")
|
|
||||||
|
|
||||||
override def post(request: Request): Response =
|
|
||||||
val i = counter.incrementAndGet()
|
|
||||||
Main.LOG.trace(s"Counter incremented : $i")
|
|
||||||
Response.ok
|
|
@ -1,19 +0,0 @@
|
|||||||
package org.tbasket.handler
|
|
||||||
|
|
||||||
import org.tbasket.api.compute.APIRequestHandler
|
|
||||||
import zio.*
|
|
||||||
import zio.http.model.Status
|
|
||||||
import zio.http.{Request, Response}
|
|
||||||
|
|
||||||
trait LoginRequiredRequestHandler extends APIRequestHandler:
|
|
||||||
|
|
||||||
override final def get: Task[Response] =
|
|
||||||
ZIO.succeed(Response(Status.MethodNotAllowed))
|
|
||||||
|
|
||||||
override final def post: Task[Response] = {
|
|
||||||
ZIO.succeed(Response(Status.MethodNotAllowed))
|
|
||||||
}
|
|
||||||
|
|
||||||
protected def authGet: Task[Response]
|
|
||||||
|
|
||||||
protected def authPost: Task[Response]
|
|
@ -1,3 +0,0 @@
|
|||||||
package org.tbasket.api
|
|
||||||
|
|
||||||
class APIException(msg: String, cause: Throwable = null) extends Exception
|
|
@ -1,54 +0,0 @@
|
|||||||
package org.tbasket.api
|
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager
|
|
||||||
import org.tbasket.api.Endpoint.LOG
|
|
||||||
import org.tbasket.api.compute.APIRequestHandler
|
|
||||||
import zio.*
|
|
||||||
import zio.http.ServerConfig.LeakDetectionLevel
|
|
||||||
import zio.http.*
|
|
||||||
import zio.http.model.Method.{GET, POST}
|
|
||||||
import zio.http.model.Status
|
|
||||||
|
|
||||||
import scala.collection.mutable
|
|
||||||
|
|
||||||
class Endpoint(port: Int):
|
|
||||||
|
|
||||||
private val handlers = mutable.HashMap.empty[String, APIRequestHandler]
|
|
||||||
|
|
||||||
def bind(path: String)(handler: APIRequestHandler): Unit =
|
|
||||||
handlers.put(path, handler)
|
|
||||||
|
|
||||||
// set generic required headers
|
|
||||||
private def transform(response: Response): Response =
|
|
||||||
response.withAccessControlAllowOrigin("*")
|
|
||||||
|
|
||||||
private val app = Http.collectZIO[Request] {
|
|
||||||
case GET -> _ / path if handlers.contains(path) =>
|
|
||||||
handlers(path).get.map(transform)
|
|
||||||
|
|
||||||
case POST -> _ / path if handlers.contains(path) =>
|
|
||||||
handlers(path).post.map(transform)
|
|
||||||
|
|
||||||
case r@method -> path =>
|
|
||||||
val ipInsights = r.remoteAddress
|
|
||||||
.map(ip => s": request received from $ip.")
|
|
||||||
.getOrElse("")
|
|
||||||
LOG.error(
|
|
||||||
s"Was unable to find a handler for request '$path' with method $method ${ipInsights}"
|
|
||||||
)
|
|
||||||
ZIO.succeed(Response(Status.NotFound))
|
|
||||||
}
|
|
||||||
|
|
||||||
val run =
|
|
||||||
val config = ServerConfig.default
|
|
||||||
.port(port)
|
|
||||||
.leakDetection(LeakDetectionLevel.PARANOID)
|
|
||||||
|
|
||||||
val configLayer = ServerConfig.live(config)
|
|
||||||
Server.install(app).flatMap { port =>
|
|
||||||
LOG.info(s"Listening API entries on $port")
|
|
||||||
ZIO.never
|
|
||||||
}.provideSome(configLayer, Server.live)
|
|
||||||
|
|
||||||
object Endpoint:
|
|
||||||
final val LOG = LogManager.getLogger("API")
|
|
@ -1,13 +0,0 @@
|
|||||||
package org.tbasket.api.compute
|
|
||||||
|
|
||||||
import zio.*
|
|
||||||
import zio.http.model.Status
|
|
||||||
import zio.http.{Request, Response}
|
|
||||||
|
|
||||||
trait APIRequestHandler:
|
|
||||||
|
|
||||||
def get: Task[Response] =
|
|
||||||
ZIO.succeed(Response(Status.MethodNotAllowed))
|
|
||||||
|
|
||||||
def post: Task[Response] =
|
|
||||||
ZIO.succeed(Response(Status.MethodNotAllowed))
|
|
Reference in new issue