From d8bb6dcba30f6bcb8d07c5a014fe4f9aaa2e4c1b Mon Sep 17 00:00:00 2001 From: Eike Kettner <eike.kettner@posteo.de> Date: Sun, 13 Sep 2020 12:29:39 +0200 Subject: [PATCH] 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. --- .../main/scala/docspell/common/LenientUri.scala | 3 +++ .../docspell/restserver/auth/CookieData.scala | 12 ++++++++---- .../docspell/restserver/routes/LoginRoutes.scala | 16 +++++++++------- .../scala/docspell/restserver/webapp/Flags.scala | 8 ++++++-- 4 files changed, 26 insertions(+), 13 deletions(-) diff --git a/modules/common/src/main/scala/docspell/common/LenientUri.scala b/modules/common/src/main/scala/docspell/common/LenientUri.scala index 01649e3a..bb66d95d 100644 --- a/modules/common/src/main/scala/docspell/common/LenientUri.scala +++ b/modules/common/src/main/scala/docspell/common/LenientUri.scala @@ -83,6 +83,9 @@ case class LenientUri( } ) + def isLocal: Boolean = + host.exists(_.equalsIgnoreCase("localhost")) + def asString: String = { val schemePart = scheme.toList.mkString(":") val authPart = authority.map(a => s"//$a").getOrElse("") diff --git a/modules/restserver/src/main/scala/docspell/restserver/auth/CookieData.scala b/modules/restserver/src/main/scala/docspell/restserver/auth/CookieData.scala index 91bf0938..8a43843d 100644 --- a/modules/restserver/src/main/scala/docspell/restserver/auth/CookieData.scala +++ b/modules/restserver/src/main/scala/docspell/restserver/auth/CookieData.scala @@ -11,8 +11,8 @@ case class CookieData(auth: AuthToken) { def accountId: AccountId = auth.account def asString: String = auth.asString - def asCookie(cfg: Config): ResponseCookie = { - val domain = cfg.baseUrl.host + def asCookie(cfg: Config, host: Option[String]): ResponseCookie = { + val domain = CookieData.getDomain(cfg, host) val sec = cfg.baseUrl.scheme.exists(_.endsWith("s")) val path = cfg.baseUrl.path / "api" / "v1" / "sec" ResponseCookie( @@ -29,6 +29,10 @@ object CookieData { val cookieName = "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] = fromCookie(r).orElse(fromHeader(r)) @@ -47,11 +51,11 @@ object CookieData { .map(_.value) .toRight("Couldn't find an authenticator") - def deleteCookie(cfg: Config): ResponseCookie = + def deleteCookie(cfg: Config, remoteHost: Option[String]): ResponseCookie = ResponseCookie( cookieName, "", - domain = cfg.baseUrl.host, + domain = getDomain(cfg, remoteHost), path = Some(cfg.baseUrl.path / "api" / "v1" / "sec").map(_.asString), httpOnly = true, secure = cfg.baseUrl.scheme.exists(_.endsWith("s")), diff --git a/modules/restserver/src/main/scala/docspell/restserver/routes/LoginRoutes.scala b/modules/restserver/src/main/scala/docspell/restserver/routes/LoginRoutes.scala index d7205d68..d707d8b8 100644 --- a/modules/restserver/src/main/scala/docspell/restserver/routes/LoginRoutes.scala +++ b/modules/restserver/src/main/scala/docspell/restserver/routes/LoginRoutes.scala @@ -21,9 +21,10 @@ object LoginRoutes { HttpRoutes.of[F] { case req @ POST -> Root / "login" => for { - up <- req.as[UserPass] - res <- S.loginUserPass(cfg.auth)(Login.UserPass(up.account, up.password)) - resp <- makeResponse(dsl, cfg, res, up.account) + up <- req.as[UserPass] + res <- S.loginUserPass(cfg.auth)(Login.UserPass(up.account, up.password)) + remote = req.from.map(_.getHostName()) + resp <- makeResponse(dsl, cfg, remote, res, up.account) } yield resp } } @@ -36,16 +37,17 @@ object LoginRoutes { case req @ POST -> Root / "session" => Authenticate .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" => - Ok().map(_.addCookie(CookieData.deleteCookie(cfg))) + case req @ POST -> Root / "logout" => + Ok().map(_.addCookie(CookieData.deleteCookie(cfg, req.from.map(_.getHostName)))) } } def makeResponse[F[_]: Effect]( dsl: Http4sDsl[F], cfg: Config, + remoteHost: Option[String], res: Login.Result, account: String ): F[Response[F]] = { @@ -63,7 +65,7 @@ object LoginRoutes { Some(cd.asString), cfg.auth.sessionValid.millis ) - ).map(_.addCookie(cd.asCookie(cfg))) + ).map(_.addCookie(cd.asCookie(cfg, remoteHost))) } yield resp case _ => Ok(AuthResult("", account, false, "Login failed.", None, 0L)) diff --git a/modules/restserver/src/main/scala/docspell/restserver/webapp/Flags.scala b/modules/restserver/src/main/scala/docspell/restserver/webapp/Flags.scala index 98134f2a..3e36359b 100644 --- a/modules/restserver/src/main/scala/docspell/restserver/webapp/Flags.scala +++ b/modules/restserver/src/main/scala/docspell/restserver/webapp/Flags.scala @@ -11,7 +11,7 @@ import yamusca.imports._ case class Flags( appName: String, - baseUrl: LenientUri, + baseUrl: String, signupMode: SignupConfig.Mode, docspellAssetPath: String, integrationEnabled: Boolean, @@ -25,7 +25,7 @@ object Flags { def apply(cfg: Config): Flags = Flags( cfg.appName, - cfg.baseUrl, + getBaseUrl(cfg), cfg.backend.signup.mode, s"/app/assets/docspell-webapp/${BuildInfo.version}", cfg.integrationEndpoint.enabled, @@ -35,6 +35,10 @@ object Flags { 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] = deriveEncoder[Flags]