fixed some aspects in login page handler and working on register page handler
continuous-integration/drone/push Build is failing
Details
continuous-integration/drone/push Build is failing
Details
parent
47cb113c8d
commit
d35a61212a
@ -1,6 +1,6 @@
|
|||||||
database {
|
database {
|
||||||
dataSourceClassName = org.sqlite.SQLiteDataSource
|
dataSourceClassName = org.sqlite.SQLiteDataSource
|
||||||
dataSource {
|
dataSource {
|
||||||
url = "jdbc:sqlite:database.sqlite;DB_CLOSE_DELAY=-1;INIT=RUNSCRIPT FROM 'classpath:table_init.sql'"
|
url = "jdbc:sqlite:database.sqlite"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,2 +0,0 @@
|
|||||||
module core {
|
|
||||||
}
|
|
@ -0,0 +1,11 @@
|
|||||||
|
package org.tbasket.data
|
||||||
|
|
||||||
|
import io.getquill.NamingStrategy
|
||||||
|
import io.getquill.context.qzio.ZioJdbcContext
|
||||||
|
import io.getquill.context.sql.idiom.SqlIdiom
|
||||||
|
|
||||||
|
case class DatabaseContext(v: ZioJdbcContext[SqlIdiom, NamingStrategy]):
|
||||||
|
export v.*
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
|||||||
|
package org.tbasket.error
|
||||||
|
|
||||||
|
import org.tbasket.error.ExceptionEnum
|
||||||
|
|
||||||
|
enum AuthException extends ExceptionEnum {
|
||||||
|
case InvalidPassword
|
||||||
|
case UserNotFound
|
||||||
|
case UserAlreadyRegistered
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
package org.tbasket.error
|
||||||
|
|
||||||
|
trait ExceptionEnum extends Exception {
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package org.tbasket.error
|
||||||
|
|
||||||
|
enum JwtException extends ExceptionEnum {
|
||||||
|
case InvalidJwt(cause: String)
|
||||||
|
case ExpiredJwt
|
||||||
|
|
||||||
|
|
||||||
|
case InvalidEmitterResponse
|
||||||
|
case EmitterInternalError
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
package org.tbasket.error
|
||||||
|
|
||||||
|
enum RegularException extends ExceptionEnum {
|
||||||
|
case InternalError(cause: Throwable)
|
||||||
|
case InvalidArgumentError(cause: String)
|
||||||
|
|
||||||
|
|
||||||
|
case InvalidRequest(msg: String, cause: String)
|
||||||
|
}
|
@ -1,11 +1,18 @@
|
|||||||
package org.tbasket.handler
|
package org.tbasket.handler
|
||||||
|
|
||||||
import zio.ZIO
|
import org.tbasket.error.RegularException.InvalidRequest
|
||||||
|
import zio.{Task, ZIO}
|
||||||
import zio.http.Body
|
import zio.http.Body
|
||||||
import zio.json.ast.Json
|
import zio.json.*
|
||||||
import zio.json.ast.JsonCursor
|
import zio.json.ast.{Json, JsonCursor}
|
||||||
|
|
||||||
|
import scala.language.reflectiveCalls
|
||||||
|
|
||||||
|
|
||||||
object HandlerUtils {
|
object HandlerUtils {
|
||||||
def errorBody(errorType: String, msg: String) = Body.fromString(s"""{"error": $errorType,"msg": "$msg"}""")
|
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,104 +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.tbasket.auth.Authentificator
|
|
||||||
import org.tbasket.handler.HandlerUtils.errorBody
|
|
||||||
import org.tbasket.handler.LoginError.*
|
|
||||||
import org.tbasket.data.User
|
|
||||||
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.{ZEnvironment, ZIO, *}
|
|
||||||
|
|
||||||
import java.sql.SQLException
|
|
||||||
import java.util.UUID
|
|
||||||
|
|
||||||
enum LoginError:
|
|
||||||
case TokenNotFound(token: UUID)
|
|
||||||
case UserNotFound(user: User)
|
|
||||||
case InvalidPassword
|
|
||||||
case InvalidRequest(msg: String, cause: String)
|
|
||||||
case InternalError(t: Throwable)
|
|
||||||
|
|
||||||
|
|
||||||
object LoginHandler:
|
|
||||||
|
|
||||||
private def getUser(json: Json) =
|
|
||||||
ZIO.serviceWithZIO[ZioJdbcContext[SqlIdiom, NamingStrategy]] { 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.hashCode))
|
|
||||||
.mapError(InvalidRequest("Missing or invalid field password", _))
|
|
||||||
|
|
||||||
result <- run(quote { // TODO use argon2id
|
|
||||||
query[User]
|
|
||||||
.filter(usr => usr.mailAddress == lift(mail))
|
|
||||||
.filter(usr => usr.passwordHash == lift(password))
|
|
||||||
}).mapError(InternalError.apply)
|
|
||||||
yield result.headOption
|
|
||||||
}.someOrFail(InvalidPassword)
|
|
||||||
|
|
||||||
def post(request: Request) =
|
|
||||||
val bindSession =
|
|
||||||
for
|
|
||||||
body <- request
|
|
||||||
.body
|
|
||||||
.asString
|
|
||||||
.tapError(Console.printError(_))
|
|
||||||
.mapError(s =>
|
|
||||||
InvalidRequest("Wrong request body", s.getMessage)
|
|
||||||
)
|
|
||||||
|
|
||||||
json <- ZIO.fromEither(body.fromJson[Json])
|
|
||||||
.mapError(InvalidRequest("Invalid JSON body", _))
|
|
||||||
|
|
||||||
user <- getUser(json)
|
|
||||||
jwt <- ZIO.serviceWithZIO[Authentificator](_.requestJwt(user))
|
|
||||||
yield (user, jwt)
|
|
||||||
|
|
||||||
bindSession.map { case (_, jwt) =>
|
|
||||||
Response(
|
|
||||||
status = Status.Found,
|
|
||||||
headers = Headers.location("/") ++ //login successful, go back to main page
|
|
||||||
Headers.setCookie(Cookie("JWT", jwt)) //and set the token cookie
|
|
||||||
)
|
|
||||||
} 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)
|
|
@ -0,0 +1,97 @@
|
|||||||
|
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.Authentificator
|
||||||
|
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.{ZEnvironment, 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[Authentificator] { auth =>
|
||||||
|
for
|
||||||
|
mail <- HandlerUtils.parseAttribute(json, "mail", JsonCursor.field("name").isString)
|
||||||
|
password <- HandlerUtils.parseAttribute(json, "mail", JsonCursor.field("name").isString)
|
||||||
|
user <- auth.loginUser(mail, password)
|
||||||
|
yield user
|
||||||
|
}
|
||||||
|
|
||||||
|
def post(request: Request) =
|
||||||
|
val bindSession =
|
||||||
|
for
|
||||||
|
body <- request
|
||||||
|
.body
|
||||||
|
.asString
|
||||||
|
.mapError(s =>
|
||||||
|
InvalidRequest("Unparseable request body", s.getMessage)
|
||||||
|
)
|
||||||
|
|
||||||
|
json <- ZIO.fromEither(body.fromJson[Json])
|
||||||
|
.mapError(InvalidRequest("Invalid JSON body", _))
|
||||||
|
|
||||||
|
user <- getUser(json)
|
||||||
|
jwt <- ZIO.serviceWithZIO[Authentificator](_.requestNewJwt(user))
|
||||||
|
yield jwt
|
||||||
|
|
||||||
|
bindSession.map { jwt =>
|
||||||
|
Response(
|
||||||
|
status = Status.Found,
|
||||||
|
headers = Headers.location("/") ++ //login successful, go back to main page
|
||||||
|
Headers.setCookie(Cookie("JWT", jwt)) //and set the token cookie
|
||||||
|
)
|
||||||
|
}.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")
|
||||||
|
))
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
package org.tbasket.handler
|
||||||
|
|
||||||
|
/**
|
||||||
|
* tag interface for page handlers
|
||||||
|
* */
|
||||||
|
trait PageHandler {
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
package org.tbasket.handler
|
||||||
|
|
||||||
|
import org.tbasket.auth.Authentificator
|
||||||
|
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, "mail", JsonCursor.field("mail").isString)
|
||||||
|
password <- parseAttribute(json, "password", JsonCursor.field("password").isString)
|
||||||
|
|
||||||
|
user <- ZIO.serviceWithZIO[Authentificator](_.registerUser(name, forename, mail, password))
|
||||||
|
jwt <- ZIO.serviceWithZIO[Authentificator](_.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 => ???
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Reference in new issue