From e5cd7b897a5a4ed14f6669fe6fb12cca957ccc01 Mon Sep 17 00:00:00 2001 From: Override-6 Date: Wed, 25 Jan 2023 15:13:02 +0100 Subject: [PATCH] working on login --- .scalafmt.conf | 27 ---- Core/resources/server.properties | 3 + Core/src/org/tbasket/EndpointSetup.scala | 5 +- Core/src/org/tbasket/Main.scala | 4 +- Core/src/org/tbasket/auth/JWTClient.scala | 23 +++ Core/src/org/tbasket/auth/UserSession.scala | 7 - .../org/tbasket/handler/LoginHandler.scala | 137 ++++++++++-------- .../handler/LoginRequiredRequestHandler.scala | 19 +++ DB/resources/table_init.sql | 2 +- DB/src/org/tbasket/db/Database.scala | 17 +-- DB/src/org/tbasket/db/schemas/User.scala | 19 ++- 11 files changed, 137 insertions(+), 126 deletions(-) delete mode 100644 .scalafmt.conf create mode 100644 Core/resources/server.properties create mode 100644 Core/src/org/tbasket/auth/JWTClient.scala delete mode 100644 Core/src/org/tbasket/auth/UserSession.scala create mode 100644 Core/src/org/tbasket/handler/LoginRequiredRequestHandler.scala diff --git a/.scalafmt.conf b/.scalafmt.conf deleted file mode 100644 index d3e11f9..0000000 --- a/.scalafmt.conf +++ /dev/null @@ -1,27 +0,0 @@ -version = "3.6.1" -runner.dialect = "scala3" - -assumeStandardLibraryStripMargin = true -newlines.source = keep - -rewrite.scala3 { - convertToNewSyntax = yes - removeOptionalBraces = yes -} - -align { - preset = some - openParenCallSite = false - stripMargin = true -} - -continuationIndent { - callSite = 2 - defnSite = 4 -} - -docstrings { - style = Asterisk - oneline = keep - wrap = no -} \ No newline at end of file diff --git a/Core/resources/server.properties b/Core/resources/server.properties new file mode 100644 index 0000000..14d538d --- /dev/null +++ b/Core/resources/server.properties @@ -0,0 +1,3 @@ +#may not be a public url +emitter.url=localhost:4454 +eme=dzq=d=qzd=qz=d \ No newline at end of file diff --git a/Core/src/org/tbasket/EndpointSetup.scala b/Core/src/org/tbasket/EndpointSetup.scala index 0b05c7f..c01dea7 100644 --- a/Core/src/org/tbasket/EndpointSetup.scala +++ b/Core/src/org/tbasket/EndpointSetup.scala @@ -13,13 +13,12 @@ object EndpointSetup: private final val EndpointPort = "endpoint.port" private final val EndpointPortDefault = "48485" - def setupEndpoint(config: Properties, db: ZioContext[SqliteDialect, Literal]): Endpoint = + def setupEndpoint(config: Properties): Endpoint = Main.LOG.debug("Initializing API endpoint...") - val endpoint = createEndpoint(config) endpoint.bind("counter")(IncrementHandler) - endpoint.bind("login")(new LoginHandler(sessions, db)) + endpoint.bind("login")(new LoginHandler()) endpoint private def createEndpoint(config: Properties): Endpoint = diff --git a/Core/src/org/tbasket/Main.scala b/Core/src/org/tbasket/Main.scala index b4e554e..fc76da7 100644 --- a/Core/src/org/tbasket/Main.scala +++ b/Core/src/org/tbasket/Main.scala @@ -14,10 +14,10 @@ import scala.util.control.NonFatal object Main extends ZIOAppDefault: final val LOG = LogManager.getLogger("Core") - override def run: ZIO[Main.Environment with ZIOAppArgs with Scope, Any, Any] = + override def run = val config = retrieveConfig val db = new Database(config) - EndpointSetup.setupEndpoint(config, db.layer).run + EndpointSetup.setupEndpoint(config).run.provideLayer(db.layer) private def retrieveConfig: Properties = val configFile = Path.of("server.properties") diff --git a/Core/src/org/tbasket/auth/JWTClient.scala b/Core/src/org/tbasket/auth/JWTClient.scala new file mode 100644 index 0000000..34ecbca --- /dev/null +++ b/Core/src/org/tbasket/auth/JWTClient.scala @@ -0,0 +1,23 @@ +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] = { + } + + +} diff --git a/Core/src/org/tbasket/auth/UserSession.scala b/Core/src/org/tbasket/auth/UserSession.scala deleted file mode 100644 index ebc1efc..0000000 --- a/Core/src/org/tbasket/auth/UserSession.scala +++ /dev/null @@ -1,7 +0,0 @@ -package org.tbasket.auth - -import org.tbasket.db.schemas.User - -import java.util.UUID - -case class UserSession(user: User, token: UUID, expirationDate: Long) diff --git a/Core/src/org/tbasket/handler/LoginHandler.scala b/Core/src/org/tbasket/handler/LoginHandler.scala index 51ee3fb..85206ad 100644 --- a/Core/src/org/tbasket/handler/LoginHandler.scala +++ b/Core/src/org/tbasket/handler/LoginHandler.scala @@ -1,25 +1,23 @@ package org.tbasket.handler import io.getquill.* +import io.getquill.context.qzio.ZioContext import org.tbasket.api.compute.APIRequestHandler +import org.tbasket.auth.JWTClient import org.tbasket.db.Database.ctx.* import org.tbasket.db.schemas.User -import org.tbasket.auth.{UserSession, UserSessionHandler} +import org.tbasket.handler.HandlerUtils.errorBody +import org.tbasket.handler.LoginError.{InvalidRequest, *} import zio.http.model.{Cookie, Header, Headers, Status} -import zio.http.{Body, Request, Response} +import zio.http.{Body, Client, Request, Response, URL} import zio.json.* import zio.json.ast.Json.Str import zio.json.ast.{Json, JsonCursor} -import zio.{ZEnvironment, ZIO} -import zio.* -import io.getquill.context.qzio.ZioContext -import org.tbasket.handler.HandlerUtils.errorBody -import org.tbasket.handler.LoginError.InvalidRequest +import zio.{ZEnvironment, ZIO, *} -import javax.sql.DataSource import java.sql.SQLException import java.util.UUID - +import javax.sql.DataSource enum LoginError: case TokenNotFound(token: UUID) @@ -29,72 +27,83 @@ enum LoginError: case InternalError(t: Throwable) -import LoginError.* - -class LoginHandler(sessions: UserSessionHandler, ctx: ZioContext[SqliteDialect, Literal]) extends APIRequestHandler: +class LoginHandler extends APIRequestHandler: - import ctx.* + private def getUser(json: Json) = + val r = + ZIO.serviceWithZIO[ZioContext[SqliteDialect, Literal]] { ctx => + import ctx.* + for + mail <- + ZIO.fromEither(json.get[Str](JsonCursor.field("mail").isString).map(_.value)) + .mapError(InvalidRequest("Missing or invalid field mail", _)) + password <- + ZIO.fromEither(json.get[Str](JsonCursor.field("password").isString).map(_.value)) + .mapError(InvalidRequest("Missing or invalid field password", _)) + + result <- run(quote { // TODO use argon2id + User.schema.filter(usr => + usr.mailAddress == mail && usr.passwordHash == lift(password.hashCode) + ) + }).mapError(InternalError.apply) + yield result.headOption + } + r.someOrFail(InvalidPassword) - private def getUser(json: Json): IO[LoginError, User] = - val r: ZIO[DataSource, LoginError, Option[User]] = for - mail <- - ZIO.fromEither(json.get[Str](JsonCursor.field("mail").isString).map(_.value)) - .mapError(InvalidRequest("Missing or invalid field mail", _)) - password <- - ZIO.fromEither(json.get[Str](JsonCursor.field("password").isString).map(_.value)) - .mapError(InvalidRequest("Missing or invalid field password", _)) - - result <- run(quote { // TODO use argon2id - User.schema.filter(usr => - usr.mailAddress == mail && usr.passwordHash == lift(password.hashCode) - ) - }).mapError(InternalError.apply) - yield result.headOption - r.someOrFail(InvalidPassword).provideSome(ZLayer.succeed(ctx)) - - override def post(request: Request): URIO[Console, Response] = - val bindSession: ZIO[Console, LoginError, UserSession] = + override def post(request: Request): Task[Response] = + val bindSession = for body <- request .body .asString .tapError(Console.printError(_)) - .mapError(s => InvalidRequest("Wrong request body", s.getMessage)): IO[LoginError, String] + .mapError(s => + InvalidRequest("Wrong request body", s.getMessage) + ) json <- ZIO.fromEither(body.fromJson[Json]) - .mapError(InvalidRequest("Invalid JSON body", _)): IO[LoginError, Json] + .mapError(InvalidRequest("Invalid JSON body", _)) - user <- getUser(json): IO[LoginError, User] - session <- sessions.bind(user): IO[LoginError, UserSession] - yield session + user <- getUser(json) + jwt <- ZIO.serviceWithZIO[JWTClient](_.requestJwt(user)) + yield (user, jwt) bindSession.map { sess => Response( - status = Status.Ok, - body = Body.fromString(s"""{"token": "${sess.token}"}"""), - ) - }.mapError { - case TokenNotFound(_) => Response( - status = Status.Unauthorized, - body = errorBody("unauthorized", "unknown token"), - headers = Headers(Headers.location("/login")) //send back caller to login panel + status = Status.Found, + headers = Headers.location("/") ++ //login successful, go back to main page + Headers.setCookie(Cookie( + "JWT", "Jw" + )) ) - case UserNotFound(_) => Response( - status = Status.Unauthorized, - body = errorBody("unauthorized", "unknown user email"), - headers = Headers(Headers.location("/register")) //send back caller to register panel - ) - case InvalidPassword => Response( - status = Status.Unauthorized, - body = errorBody("unauthorized", "invalid password") - ) - case InvalidRequest(msg, cause) => Response( - status = Status.Unauthorized, - body = errorBody("wrong request", s"$cause: $msg") - ) - case InternalError(_) => Response( - status = Status.InternalServerError, - body = errorBody("internal", "internal error, please contact support") - ) - } - + } fold( { + _ match + case TokenNotFound(_) => Response( + status = Status.Unauthorized, + body = errorBody("unauthorized", "unknown token"), + headers = + Headers( + Headers.location("/login") + ) // send back caller to login panel + ) + case UserNotFound(_) => Response( + status = Status.Unauthorized, + body = errorBody("unauthorized", "unknown user email"), + headers = + Headers( + Headers.location("/register") + ) // send back caller to register panel + ) + case InvalidPassword => Response( + status = Status.Unauthorized, + body = errorBody("unauthorized", "invalid password") + ) + case InvalidRequest(msg, cause) => Response( + status = Status.Unauthorized, + body = errorBody("wrong request", s"$cause: $msg") + ) + case InternalError(_) => Response( + status = Status.InternalServerError, + body = errorBody("internal", "internal error, please contact support") + ) + }, x => x) diff --git a/Core/src/org/tbasket/handler/LoginRequiredRequestHandler.scala b/Core/src/org/tbasket/handler/LoginRequiredRequestHandler.scala new file mode 100644 index 0000000..f3d5b37 --- /dev/null +++ b/Core/src/org/tbasket/handler/LoginRequiredRequestHandler.scala @@ -0,0 +1,19 @@ +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] diff --git a/DB/resources/table_init.sql b/DB/resources/table_init.sql index 249c982..7b12d58 100644 --- a/DB/resources/table_init.sql +++ b/DB/resources/table_init.sql @@ -1,6 +1,6 @@ CREATE TABLE user ( - id int PRIMARY KEY, + id varchar(32) PRIMARY KEY, name varchar(30) NOT NULL, mail_address varchar NOT NULL UNIQUE, forename varchar(30) NOT NULL, diff --git a/DB/src/org/tbasket/db/Database.scala b/DB/src/org/tbasket/db/Database.scala index 655201a..e64b7b7 100644 --- a/DB/src/org/tbasket/db/Database.scala +++ b/DB/src/org/tbasket/db/Database.scala @@ -11,25 +11,14 @@ import javax.sql class Database(config: Properties): - private val source = new SQLiteDataSource() with Closeable: - override def close(): Unit = () + private val source = new SQLiteDataSource() + source.setUrl(config.getProperty("database.url")) - val layer = DataSourceLayer.fromDataSource(source) + val layer = ZLayer.succeed(source) object Database: val ctx = new SqliteZioJdbcContext(Literal) import ctx.* - /* override def run = { - DataService.getPeople - .provide( - DataService.live, - Quill.Postgres.fromNamingStrategy(SnakeCase), - Quill.DataSource.fromPrefix("myDatabaseConfig") - ) - .debug("Results") - .exitCode - } - */ diff --git a/DB/src/org/tbasket/db/schemas/User.scala b/DB/src/org/tbasket/db/schemas/User.scala index 341555a..fa016ba 100644 --- a/DB/src/org/tbasket/db/schemas/User.scala +++ b/DB/src/org/tbasket/db/schemas/User.scala @@ -3,17 +3,20 @@ package org.tbasket.db.schemas import io.getquill.* import org.tbasket.db.Database +import java.util.UUID + case class User( - id: Int, - name: String, - forename: String, - passwordHash: Int, - mailAddress: String -) -object User: + id : UUID, + name : String, + forename : String, + passwordHash: Int, + mailAddress : String + ) +object User: + import Database.ctx.* - + val schema = quote { querySchema[User]( "user",