working on login
continuous-integration/drone/push Build is failing Details

drone-setup
Override-6 2 years ago
parent f9ba0b9af3
commit e5cd7b897a

@ -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
}

@ -0,0 +1,3 @@
#may not be a public url
emitter.url=localhost:4454
eme=dzq=d=qzd=qz=d

@ -13,13 +13,12 @@ object EndpointSetup:
private final val EndpointPort = "endpoint.port" private final val EndpointPort = "endpoint.port"
private final val EndpointPortDefault = "48485" 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...") Main.LOG.debug("Initializing API endpoint...")
val endpoint = createEndpoint(config) val endpoint = createEndpoint(config)
endpoint.bind("counter")(IncrementHandler) endpoint.bind("counter")(IncrementHandler)
endpoint.bind("login")(new LoginHandler(sessions, db)) endpoint.bind("login")(new LoginHandler())
endpoint endpoint
private def createEndpoint(config: Properties): Endpoint = private def createEndpoint(config: Properties): Endpoint =

@ -14,10 +14,10 @@ import scala.util.control.NonFatal
object Main extends ZIOAppDefault: object Main extends ZIOAppDefault:
final val LOG = LogManager.getLogger("Core") 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 config = retrieveConfig
val db = new Database(config) val db = new Database(config)
EndpointSetup.setupEndpoint(config, db.layer).run EndpointSetup.setupEndpoint(config).run.provideLayer(db.layer)
private def retrieveConfig: Properties = private def retrieveConfig: Properties =
val configFile = Path.of("server.properties") val configFile = Path.of("server.properties")

@ -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] = {
}
}

@ -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)

@ -1,25 +1,23 @@
package org.tbasket.handler package org.tbasket.handler
import io.getquill.* import io.getquill.*
import io.getquill.context.qzio.ZioContext
import org.tbasket.api.compute.APIRequestHandler import org.tbasket.api.compute.APIRequestHandler
import org.tbasket.auth.JWTClient
import org.tbasket.db.Database.ctx.* import org.tbasket.db.Database.ctx.*
import org.tbasket.db.schemas.User 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.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.*
import zio.json.ast.Json.Str import zio.json.ast.Json.Str
import zio.json.ast.{Json, JsonCursor} import zio.json.ast.{Json, JsonCursor}
import zio.{ZEnvironment, ZIO} 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 javax.sql.DataSource
import java.sql.SQLException import java.sql.SQLException
import java.util.UUID import java.util.UUID
import javax.sql.DataSource
enum LoginError: enum LoginError:
case TokenNotFound(token: UUID) case TokenNotFound(token: UUID)
@ -29,72 +27,83 @@ enum LoginError:
case InternalError(t: Throwable) case InternalError(t: Throwable)
import LoginError.* class LoginHandler extends APIRequestHandler:
class LoginHandler(sessions: UserSessionHandler, ctx: ZioContext[SqliteDialect, Literal]) 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] = override def post(request: Request): Task[Response] =
val r: ZIO[DataSource, LoginError, Option[User]] = for val bindSession =
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] =
for for
body <- request body <- request
.body .body
.asString .asString
.tapError(Console.printError(_)) .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]) 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] user <- getUser(json)
session <- sessions.bind(user): IO[LoginError, UserSession] jwt <- ZIO.serviceWithZIO[JWTClient](_.requestJwt(user))
yield session yield (user, jwt)
bindSession.map { sess => bindSession.map { sess =>
Response( Response(
status = Status.Ok, status = Status.Found,
body = Body.fromString(s"""{"token": "${sess.token}"}"""), headers = Headers.location("/") ++ //login successful, go back to main page
) Headers.setCookie(Cookie(
}.mapError { "JWT", "Jw"
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( } fold( {
status = Status.Unauthorized, _ match
body = errorBody("unauthorized", "unknown user email"), case TokenNotFound(_) => Response(
headers = Headers(Headers.location("/register")) //send back caller to register panel status = Status.Unauthorized,
) body = errorBody("unauthorized", "unknown token"),
case InvalidPassword => Response( headers =
status = Status.Unauthorized, Headers(
body = errorBody("unauthorized", "invalid password") Headers.location("/login")
) ) // send back caller to login panel
case InvalidRequest(msg, cause) => Response( )
status = Status.Unauthorized, case UserNotFound(_) => Response(
body = errorBody("wrong request", s"$cause: $msg") status = Status.Unauthorized,
) body = errorBody("unauthorized", "unknown user email"),
case InternalError(_) => Response( headers =
status = Status.InternalServerError, Headers(
body = errorBody("internal", "internal error, please contact support") 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)

@ -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]

@ -1,6 +1,6 @@
CREATE TABLE user CREATE TABLE user
( (
id int PRIMARY KEY, id varchar(32) PRIMARY KEY,
name varchar(30) NOT NULL, name varchar(30) NOT NULL,
mail_address varchar NOT NULL UNIQUE, mail_address varchar NOT NULL UNIQUE,
forename varchar(30) NOT NULL, forename varchar(30) NOT NULL,

@ -11,25 +11,14 @@ import javax.sql
class Database(config: Properties): class Database(config: Properties):
private val source = new SQLiteDataSource() with Closeable: private val source = new SQLiteDataSource()
override def close(): Unit = ()
source.setUrl(config.getProperty("database.url")) source.setUrl(config.getProperty("database.url"))
val layer = DataSourceLayer.fromDataSource(source) val layer = ZLayer.succeed(source)
object Database: object Database:
val ctx = new SqliteZioJdbcContext(Literal) val ctx = new SqliteZioJdbcContext(Literal)
import ctx.* import ctx.*
/* override def run = {
DataService.getPeople
.provide(
DataService.live,
Quill.Postgres.fromNamingStrategy(SnakeCase),
Quill.DataSource.fromPrefix("myDatabaseConfig")
)
.debug("Results")
.exitCode
}
*/

@ -3,17 +3,20 @@ package org.tbasket.db.schemas
import io.getquill.* import io.getquill.*
import org.tbasket.db.Database import org.tbasket.db.Database
import java.util.UUID
case class User( case class User(
id: Int, id : UUID,
name: String, name : String,
forename: String, forename : String,
passwordHash: Int, passwordHash: Int,
mailAddress: String mailAddress : String
) )
object User:
object User:
import Database.ctx.* import Database.ctx.*
val schema = quote { val schema = quote {
querySchema[User]( querySchema[User](
"user", "user",