mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-06-22 02:18:26 +00:00
Use remember-me cookie if present
This commit is contained in:
@ -57,7 +57,7 @@ docspell.server {
|
||||
remember-me {
|
||||
enabled = true
|
||||
# How long the remember me cookie/token is valid.
|
||||
valid = "3 month"
|
||||
valid = "21 days"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -64,7 +64,7 @@ object RestServer {
|
||||
token: AuthToken
|
||||
): HttpRoutes[F] =
|
||||
Router(
|
||||
"auth" -> LoginRoutes.session(restApp.backend.login, cfg),
|
||||
"auth" -> LoginRoutes.session(restApp.backend.login, cfg, token),
|
||||
"tag" -> TagRoutes(restApp.backend, token),
|
||||
"equipment" -> EquipmentRoutes(restApp.backend, token),
|
||||
"organization" -> OrganizationRoutes(restApp.backend, token),
|
||||
|
@ -23,6 +23,10 @@ case class CookieData(auth: AuthToken) {
|
||||
secure = sec
|
||||
)
|
||||
}
|
||||
|
||||
def addCookie[F[_]](baseUrl: LenientUri)(resp: Response[F]): Response[F] =
|
||||
resp.addCookie(asCookie(baseUrl))
|
||||
|
||||
}
|
||||
object CookieData {
|
||||
val cookieName = "docspell_auth"
|
||||
|
@ -0,0 +1,51 @@
|
||||
package docspell.restserver.auth
|
||||
|
||||
import docspell.backend.auth._
|
||||
import docspell.common._
|
||||
|
||||
import org.http4s._
|
||||
|
||||
case class RememberCookieData(token: RememberToken) {
|
||||
def asString: String = token.asString
|
||||
|
||||
def asCookie(config: Login.RememberMe, baseUrl: LenientUri): ResponseCookie = {
|
||||
val sec = baseUrl.scheme.exists(_.endsWith("s"))
|
||||
val path = baseUrl.path / "api" / "v1"
|
||||
ResponseCookie(
|
||||
name = RememberCookieData.cookieName,
|
||||
content = asString,
|
||||
domain = None,
|
||||
path = Some(path.asString),
|
||||
httpOnly = true,
|
||||
secure = sec,
|
||||
maxAge = Some(config.valid.seconds)
|
||||
)
|
||||
}
|
||||
|
||||
def addCookie[F[_]](cfg: Login.RememberMe, baseUrl: LenientUri)(
|
||||
resp: Response[F]
|
||||
): Response[F] =
|
||||
resp.addCookie(asCookie(cfg, baseUrl))
|
||||
|
||||
}
|
||||
object RememberCookieData {
|
||||
val cookieName = "docspell_remember"
|
||||
|
||||
def fromCookie[F[_]](req: Request[F]): Option[String] =
|
||||
for {
|
||||
header <- headers.Cookie.from(req.headers)
|
||||
cookie <- header.values.toList.find(_.name == cookieName)
|
||||
} yield cookie.content
|
||||
|
||||
def delete(baseUrl: LenientUri): ResponseCookie =
|
||||
ResponseCookie(
|
||||
cookieName,
|
||||
"",
|
||||
domain = None,
|
||||
path = Some(baseUrl.path / "api" / "v1").map(_.asString),
|
||||
httpOnly = true,
|
||||
secure = baseUrl.scheme.exists(_.endsWith("s")),
|
||||
maxAge = Some(-1)
|
||||
)
|
||||
|
||||
}
|
@ -15,11 +15,19 @@ import org.http4s.server._
|
||||
object Authenticate {
|
||||
|
||||
def authenticateRequest[F[_]: Effect](
|
||||
auth: String => F[Login.Result]
|
||||
auth: (String, Option[String]) => F[Login.Result]
|
||||
)(req: Request[F]): F[Login.Result] =
|
||||
CookieData.authenticator(req) match {
|
||||
case Right(str) => auth(str)
|
||||
case Left(_) => Login.Result.invalidAuth.pure[F]
|
||||
case Right(str) =>
|
||||
val rememberMe = RememberCookieData.fromCookie(req)
|
||||
auth(str, rememberMe)
|
||||
case Left(_) =>
|
||||
RememberCookieData.fromCookie(req) match {
|
||||
case Some(rc) =>
|
||||
auth("", rc.some)
|
||||
case None =>
|
||||
Login.Result.invalidAuth.pure[F]
|
||||
}
|
||||
}
|
||||
|
||||
def of[F[_]: Effect](S: Login[F], cfg: Login.Config)(
|
||||
@ -28,7 +36,7 @@ object Authenticate {
|
||||
val dsl: Http4sDsl[F] = new Http4sDsl[F] {}
|
||||
import dsl._
|
||||
|
||||
val authUser = getUser[F](S.loginSession(cfg))
|
||||
val authUser = getUser[F](S.loginSessionOrRememberMe(cfg))
|
||||
|
||||
val onFailure: AuthedRoutes[String, F] =
|
||||
Kleisli(req => OptionT.liftF(Forbidden(req.context)))
|
||||
@ -45,7 +53,7 @@ object Authenticate {
|
||||
val dsl: Http4sDsl[F] = new Http4sDsl[F] {}
|
||||
import dsl._
|
||||
|
||||
val authUser = getUser[F](S.loginSession(cfg))
|
||||
val authUser = getUser[F](S.loginSessionOrRememberMe(cfg))
|
||||
|
||||
val onFailure: AuthedRoutes[String, F] =
|
||||
Kleisli(req => OptionT.liftF(Forbidden(req.context)))
|
||||
@ -57,7 +65,7 @@ object Authenticate {
|
||||
}
|
||||
|
||||
private def getUser[F[_]: Effect](
|
||||
auth: String => F[Login.Result]
|
||||
auth: (String, Option[String]) => F[Login.Result]
|
||||
): Kleisli[F, Request[F], Either[String, AuthToken]] =
|
||||
Kleisli(r => authenticateRequest(auth)(r).map(_.toEither))
|
||||
}
|
||||
|
@ -32,18 +32,24 @@ object LoginRoutes {
|
||||
}
|
||||
}
|
||||
|
||||
def session[F[_]: Effect](S: Login[F], cfg: Config): HttpRoutes[F] = {
|
||||
def session[F[_]: Effect](S: Login[F], cfg: Config, token: AuthToken): HttpRoutes[F] = {
|
||||
val dsl: Http4sDsl[F] = new Http4sDsl[F] {}
|
||||
import dsl._
|
||||
|
||||
HttpRoutes.of[F] {
|
||||
case req @ POST -> Root / "session" =>
|
||||
Authenticate
|
||||
.authenticateRequest(S.loginSession(cfg.auth))(req)
|
||||
AuthToken
|
||||
.update(token, cfg.auth.serverSecret)
|
||||
.map(newToken => Login.Result.ok(newToken, None))
|
||||
.flatMap(res => makeResponse(dsl, cfg, req, res, ""))
|
||||
|
||||
case req @ POST -> Root / "logout" =>
|
||||
Ok().map(_.addCookie(CookieData.deleteCookie(getBaseUrl(cfg, req))))
|
||||
for {
|
||||
_ <- RememberCookieData.fromCookie(req).traverse(S.removeRememberToken)
|
||||
res <- Ok()
|
||||
} yield res
|
||||
.removeCookie(CookieData.deleteCookie(getBaseUrl(cfg, req)))
|
||||
.removeCookie(RememberCookieData.delete(getBaseUrl(cfg, req)))
|
||||
}
|
||||
}
|
||||
|
||||
@ -59,9 +65,10 @@ object LoginRoutes {
|
||||
): F[Response[F]] = {
|
||||
import dsl._
|
||||
res match {
|
||||
case Login.Result.Ok(token) =>
|
||||
case Login.Result.Ok(token, remember) =>
|
||||
val cd = CookieData(token)
|
||||
val rem = remember.map(RememberCookieData.apply)
|
||||
for {
|
||||
cd <- AuthToken.user(token.account, cfg.auth.serverSecret).map(CookieData.apply)
|
||||
resp <- Ok(
|
||||
AuthResult(
|
||||
token.account.collective.id,
|
||||
@ -71,7 +78,13 @@ object LoginRoutes {
|
||||
Some(cd.asString),
|
||||
cfg.auth.sessionValid.millis
|
||||
)
|
||||
).map(_.addCookie(cd.asCookie(getBaseUrl(cfg, req))))
|
||||
).map(cd.addCookie(getBaseUrl(cfg, req)))
|
||||
.map(resp =>
|
||||
rem
|
||||
.map(_.addCookie(cfg.auth.rememberMe, getBaseUrl(cfg, req))(resp))
|
||||
.getOrElse(resp)
|
||||
)
|
||||
|
||||
} yield resp
|
||||
case _ =>
|
||||
Ok(AuthResult("", account, false, "Login failed.", None, 0L))
|
||||
|
Reference in New Issue
Block a user