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] 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