Dynamically configure cookie and base-url

When `base-url` is the default (i.e. localhost), the cookie is now
configured with the domain doing the request and the webapp is
configured to run requests against the host in the address bar of the
browser.
This commit is contained in:
Eike Kettner 2020-09-13 12:29:39 +02:00
parent 7104323e7e
commit d8bb6dcba3
4 changed files with 26 additions and 13 deletions

View File

@ -83,6 +83,9 @@ case class LenientUri(
} }
) )
def isLocal: Boolean =
host.exists(_.equalsIgnoreCase("localhost"))
def asString: String = { def asString: String = {
val schemePart = scheme.toList.mkString(":") val schemePart = scheme.toList.mkString(":")
val authPart = authority.map(a => s"//$a").getOrElse("") val authPart = authority.map(a => s"//$a").getOrElse("")

View File

@ -11,8 +11,8 @@ case class CookieData(auth: AuthToken) {
def accountId: AccountId = auth.account def accountId: AccountId = auth.account
def asString: String = auth.asString def asString: String = auth.asString
def asCookie(cfg: Config): ResponseCookie = { def asCookie(cfg: Config, host: Option[String]): ResponseCookie = {
val domain = cfg.baseUrl.host val domain = CookieData.getDomain(cfg, host)
val sec = cfg.baseUrl.scheme.exists(_.endsWith("s")) val sec = cfg.baseUrl.scheme.exists(_.endsWith("s"))
val path = cfg.baseUrl.path / "api" / "v1" / "sec" val path = cfg.baseUrl.path / "api" / "v1" / "sec"
ResponseCookie( ResponseCookie(
@ -29,6 +29,10 @@ object CookieData {
val cookieName = "docspell_auth" val cookieName = "docspell_auth"
val headerName = "X-Docspell-Auth" val headerName = "X-Docspell-Auth"
private def getDomain(cfg: Config, remote: Option[String]): Option[String] =
if (cfg.baseUrl.isLocal) remote.orElse(cfg.baseUrl.host)
else cfg.baseUrl.host
def authenticator[F[_]](r: Request[F]): Either[String, String] = def authenticator[F[_]](r: Request[F]): Either[String, String] =
fromCookie(r).orElse(fromHeader(r)) fromCookie(r).orElse(fromHeader(r))
@ -47,11 +51,11 @@ object CookieData {
.map(_.value) .map(_.value)
.toRight("Couldn't find an authenticator") .toRight("Couldn't find an authenticator")
def deleteCookie(cfg: Config): ResponseCookie = def deleteCookie(cfg: Config, remoteHost: Option[String]): ResponseCookie =
ResponseCookie( ResponseCookie(
cookieName, cookieName,
"", "",
domain = cfg.baseUrl.host, domain = getDomain(cfg, remoteHost),
path = Some(cfg.baseUrl.path / "api" / "v1" / "sec").map(_.asString), path = Some(cfg.baseUrl.path / "api" / "v1" / "sec").map(_.asString),
httpOnly = true, httpOnly = true,
secure = cfg.baseUrl.scheme.exists(_.endsWith("s")), secure = cfg.baseUrl.scheme.exists(_.endsWith("s")),

View File

@ -21,9 +21,10 @@ object LoginRoutes {
HttpRoutes.of[F] { case req @ POST -> Root / "login" => HttpRoutes.of[F] { case req @ POST -> Root / "login" =>
for { for {
up <- req.as[UserPass] up <- req.as[UserPass]
res <- S.loginUserPass(cfg.auth)(Login.UserPass(up.account, up.password)) res <- S.loginUserPass(cfg.auth)(Login.UserPass(up.account, up.password))
resp <- makeResponse(dsl, cfg, res, up.account) remote = req.from.map(_.getHostName())
resp <- makeResponse(dsl, cfg, remote, res, up.account)
} yield resp } yield resp
} }
} }
@ -36,16 +37,17 @@ object LoginRoutes {
case req @ POST -> Root / "session" => case req @ POST -> Root / "session" =>
Authenticate Authenticate
.authenticateRequest(S.loginSession(cfg.auth))(req) .authenticateRequest(S.loginSession(cfg.auth))(req)
.flatMap(res => makeResponse(dsl, cfg, res, "")) .flatMap(res => makeResponse(dsl, cfg, req.from.map(_.getHostName), res, ""))
case POST -> Root / "logout" => case req @ POST -> Root / "logout" =>
Ok().map(_.addCookie(CookieData.deleteCookie(cfg))) Ok().map(_.addCookie(CookieData.deleteCookie(cfg, req.from.map(_.getHostName))))
} }
} }
def makeResponse[F[_]: Effect]( def makeResponse[F[_]: Effect](
dsl: Http4sDsl[F], dsl: Http4sDsl[F],
cfg: Config, cfg: Config,
remoteHost: Option[String],
res: Login.Result, res: Login.Result,
account: String account: String
): F[Response[F]] = { ): F[Response[F]] = {
@ -63,7 +65,7 @@ object LoginRoutes {
Some(cd.asString), Some(cd.asString),
cfg.auth.sessionValid.millis cfg.auth.sessionValid.millis
) )
).map(_.addCookie(cd.asCookie(cfg))) ).map(_.addCookie(cd.asCookie(cfg, remoteHost)))
} yield resp } yield resp
case _ => case _ =>
Ok(AuthResult("", account, false, "Login failed.", None, 0L)) Ok(AuthResult("", account, false, "Login failed.", None, 0L))

View File

@ -11,7 +11,7 @@ import yamusca.imports._
case class Flags( case class Flags(
appName: String, appName: String,
baseUrl: LenientUri, baseUrl: String,
signupMode: SignupConfig.Mode, signupMode: SignupConfig.Mode,
docspellAssetPath: String, docspellAssetPath: String,
integrationEnabled: Boolean, integrationEnabled: Boolean,
@ -25,7 +25,7 @@ object Flags {
def apply(cfg: Config): Flags = def apply(cfg: Config): Flags =
Flags( Flags(
cfg.appName, cfg.appName,
cfg.baseUrl, getBaseUrl(cfg),
cfg.backend.signup.mode, cfg.backend.signup.mode,
s"/app/assets/docspell-webapp/${BuildInfo.version}", s"/app/assets/docspell-webapp/${BuildInfo.version}",
cfg.integrationEndpoint.enabled, cfg.integrationEndpoint.enabled,
@ -35,6 +35,10 @@ object Flags {
cfg.showClassificationSettings cfg.showClassificationSettings
) )
private def getBaseUrl(cfg: Config): String =
if (cfg.baseUrl.isLocal) cfg.baseUrl.path.asString
else cfg.baseUrl.asString
implicit val jsonEncoder: Encoder[Flags] = implicit val jsonEncoder: Encoder[Flags] =
deriveEncoder[Flags] deriveEncoder[Flags]