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