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 {
|
||||
dataSourceClassName = org.sqlite.SQLiteDataSource
|
||||
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
|
||||
|
||||
import zio.ZIO
|
||||
import org.tbasket.error.RegularException.InvalidRequest
|
||||
import zio.{Task, ZIO}
|
||||
import zio.http.Body
|
||||
import zio.json.ast.Json
|
||||
import zio.json.ast.JsonCursor
|
||||
import zio.json.*
|
||||
import zio.json.ast.{Json, JsonCursor}
|
||||
|
||||
import scala.language.reflectiveCalls
|
||||
|
||||
|
||||
object HandlerUtils {
|
||||
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