From d8bb6dcba30f6bcb8d07c5a014fe4f9aaa2e4c1b Mon Sep 17 00:00:00 2001 From: Eike Kettner Date: Sun, 13 Sep 2020 12:29:39 +0200 Subject: [PATCH 1/2] 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] From f65f5eff356da037541e81fed37e303a43cdc4f2 Mon Sep 17 00:00:00 2001 From: Eike Kettner Date: Sun, 13 Sep 2020 13:01:49 +0200 Subject: [PATCH 2/2] Set client base-url from browser when not given This is necessary when generating absolute URLs in the webapp (as done in "Sources" page). --- modules/webapp/src/main/elm/App/Data.elm | 29 +++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/modules/webapp/src/main/elm/App/Data.elm b/modules/webapp/src/main/elm/App/Data.elm index ba9fe730..28fbd8d4 100644 --- a/modules/webapp/src/main/elm/App/Data.elm +++ b/modules/webapp/src/main/elm/App/Data.elm @@ -49,8 +49,11 @@ type alias Model = init : Key -> Url -> Flags -> UiSettings -> ( Model, Cmd Msg ) -init key url flags settings = +init key url flags_ settings = let + flags = + initBaseUrl url flags_ + page = Page.fromUrl url |> Maybe.withDefault (defaultPage flags) @@ -90,6 +93,30 @@ init key url flags settings = ) +initBaseUrl : Url -> Flags -> Flags +initBaseUrl url flags_ = + let + cfg = + flags_.config + + baseUrl = + if cfg.baseUrl == "" then + Url.toString + { url + | path = "" + , query = Nothing + , fragment = Nothing + } + + else + cfg.baseUrl + + cfgNew = + { cfg | baseUrl = baseUrl } + in + { flags_ | config = cfgNew } + + type Msg = NavRequest UrlRequest | NavChange Url