diff --git a/.envrc b/.envrc new file mode 100644 index 00000000..3add3fcc --- /dev/null +++ b/.envrc @@ -0,0 +1,3 @@ +use flake +layout node +eval "$shellHook" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9be3d2bb..6075e446 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,7 +29,7 @@ jobs: os: [ubuntu-latest] scala: [3] java: [temurin@17] - project: [rootJS, rootJVM] + project: [rootJVM] runs-on: ${{ matrix.os }} timeout-minutes: 60 steps: @@ -62,10 +62,6 @@ jobs: if: matrix.java == 'temurin@17' && matrix.os == 'ubuntu-latest' run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' 'scalafixAll --check' - - name: scalaJSLink - if: matrix.project == 'rootJS' - run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' Test/scalaJSLinkerResult - - name: Test run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' test diff --git a/.gitignore b/.gitignore index e1d79a93..691358fa 100644 --- a/.gitignore +++ b/.gitignore @@ -70,6 +70,7 @@ ensime.sbt archive smartgcal .jvmopts +.direnv/ /jre # Node modules diff --git a/.mergify.yml b/.mergify.yml index 2827e4b0..ea436308 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -13,7 +13,6 @@ pull_request_rules: - body~=labels:.*early-semver-patch - body~=labels:.*early-semver-minor - 'title=flake.lock: Update' - - status-success=Build and Test (ubuntu-latest, 3, temurin@17, rootJS) - status-success=Build and Test (ubuntu-latest, 3, temurin@17, rootJVM) actions: merge: {} diff --git a/build.sbt b/build.sbt index 08d286ec..5365f834 100644 --- a/build.sbt +++ b/build.sbt @@ -1,5 +1,4 @@ import Settings.Libraries -import Settings.Plugins import Common.* import Settings.Libraries.* import Settings.LibraryVersions @@ -91,20 +90,13 @@ lazy val navigate_web_server = project .settings( name := "navigate_web_server", libraryDependencies ++= Seq( - UnboundId, - JwtCore, - JwtCirce, - CommonsHttp, Log4CatsNoop.value, CatsEffect.value, Log4Cats.value, Http4sCirce, - BooPickle.value, - Http4sBoopickle, GrackleRoutes, Natchez, - LucumaSchemas, - JavaLocales.value + LucumaSchemas ) ++ Http4sClient ++ Http4s ++ PureConfig ++ Logging.value ++ MUnit.value ++ Grackle.value, // Supports launching the server in the background @@ -123,29 +115,18 @@ lazy val navigate_web_server = project buildInfoPackage := "navigate.web.server" ) .dependsOn(navigate_server) - .dependsOn(navigate_model.jvm % "compile->compile;test->test") + .dependsOn(navigate_model % "compile->compile;test->test") .dependsOn(schema_util) -lazy val navigate_model = crossProject(JVMPlatform, JSPlatform) - .crossType(CrossType.Full) +lazy val navigate_model = project .in(file("modules/model")) .enablePlugins(GitBranchPrompt) .settings( libraryDependencies ++= Seq( - Squants.value, Mouse.value, - BooPickle.value, + Http4sCore, CatsTime.value - ) ++ MUnit.value ++ Monocle.value ++ LucumaCore.value ++ Sttp.value ++ Circe.value - ) - .jvmSettings( - commonSettings, - libraryDependencies += Http4sCore - ) - .jsSettings( - // And add a custom one - libraryDependencies += JavaTimeJS.value, - scalaJSLinkerConfig ~= (_.withModuleKind(ModuleKind.CommonJSModule)) + ) ++ MUnit.value ++ Monocle.value ++ LucumaCore.value ++ Circe.value ) lazy val schema_util = project @@ -169,7 +150,7 @@ lazy val navigate_server = project Log4Cats.value ) ++ MUnit.value ++ LucumaCore.value ++ Http4sClient ) - .dependsOn(navigate_model.jvm % "compile->compile;test->test") + .dependsOn(navigate_model % "compile->compile;test->test") .dependsOn(epics) .dependsOn(stateengine) diff --git a/flake.lock b/flake.lock new file mode 100644 index 00000000..e19ed5fd --- /dev/null +++ b/flake.lock @@ -0,0 +1,138 @@ +{ + "nodes": { + "devshell": { + "inputs": { + "nixpkgs": "nixpkgs", + "systems": "systems" + }, + "locked": { + "lastModified": 1701787589, + "narHash": "sha256-ce+oQR4Zq9VOsLoh9bZT8Ip9PaMLcjjBUHVPzW5d7Cw=", + "owner": "numtide", + "repo": "devshell", + "rev": "44ddedcbcfc2d52a76b64fb6122f209881bd3e1e", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "devshell", + "type": "github" + } + }, + "flake-utils": { + "inputs": { + "systems": "systems_2" + }, + "locked": { + "lastModified": 1701680307, + "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1677383253, + "narHash": "sha256-UfpzWfSxkfXHnb4boXZNaKsAcUrZT9Hw+tao1oZxd08=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "9952d6bc395f5841262b006fbace8dd7e143b634", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1704008649, + "narHash": "sha256-rGPSWjXTXTurQN9beuHdyJhB8O761w1Zc5BqSSmHvoM=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "d44d59d2b5bd694cd9d996fd8c51d03e3e9ba7f7", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": [ + "typelevel-nix", + "flake-utils" + ], + "nixpkgs": [ + "typelevel-nix", + "nixpkgs" + ], + "typelevel-nix": "typelevel-nix" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_2": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "typelevel-nix": { + "inputs": { + "devshell": "devshell", + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs_2" + }, + "locked": { + "lastModified": 1704087752, + "narHash": "sha256-PTM++FBin2Xd4kc2HyQ74Wm0PdjSheCcnX45xhH1fOw=", + "owner": "typelevel", + "repo": "typelevel-nix", + "rev": "8eb51654dc21054929b0ae6883c0382c9f51b1a2", + "type": "github" + }, + "original": { + "owner": "typelevel", + "repo": "typelevel-nix", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 00000000..397d9cf7 --- /dev/null +++ b/flake.nix @@ -0,0 +1,37 @@ +{ + inputs = { + typelevel-nix.url = "github:typelevel/typelevel-nix"; + nixpkgs.follows = "typelevel-nix/nixpkgs"; + flake-utils.follows = "typelevel-nix/flake-utils"; + }; + + outputs = { self, nixpkgs, flake-utils, typelevel-nix }: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs-x86_64 = import nixpkgs { + system = "x86_64-darwin"; + }; + scala-cli-overlay = final: prev: { + scala-cli = pkgs-x86_64.scala-cli; + }; + pkgs = import nixpkgs { + inherit system; + overlays = [ typelevel-nix.overlay scala-cli-overlay]; + }; + in + { + devShell = pkgs.devshell.mkShell { + imports = [ typelevel-nix.typelevelShell ]; + packages = [ + pkgs.nodePackages.vscode-langservers-extracted + pkgs.nodePackages.prettier + pkgs.websocat + ]; + typelevelShell = { + nodejs.enable = true; + jdk.package = pkgs.jdk17; + }; + }; + } + ); +} diff --git a/modules/model/jvm/src/main/scala/navigate/model/config/AuthenticationConfig.scala b/modules/model/jvm/src/main/scala/navigate/model/config/AuthenticationConfig.scala deleted file mode 100644 index 803fb402..00000000 --- a/modules/model/jvm/src/main/scala/navigate/model/config/AuthenticationConfig.scala +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA) -// For license information see LICENSE or https://opensource.org/licenses/BSD-3-Clause - -package navigate.model.config - -import cats.Eq -import org.http4s.Uri - -import scala.concurrent.duration.FiniteDuration - -/** - * Configuration for the general authentication service - * @param devMode - * Indicates if we are in development mode, In this mode there is an internal list of users - * @param sessionLifeHrs - * How long will the session live in hours - * @param cookieName - * Name of the cookie to store the token - * @param secretKey - * Secret key to encrypt jwt tokens - * @param useSSL - * Whether we use SSL setting the cookie to be https only - * @param ldap - * URL of the ldap servers - */ -case class AuthenticationConfig( - sessionLifeHrs: FiniteDuration, - cookieName: String, - secretKey: String, - useSSL: Boolean = false, - ldapUrls: List[Uri] -) - -object AuthenticationConfig { - given Eq[AuthenticationConfig] = - Eq.by(x => (x.sessionLifeHrs.toNanos, x.cookieName, x.secretKey, x.useSSL, x.ldapUrls)) - -} diff --git a/modules/model/shared/src/main/scala/navigate/model/security/package.scala b/modules/model/shared/src/main/scala/navigate/model/security/package.scala deleted file mode 100644 index 9145209e..00000000 --- a/modules/model/shared/src/main/scala/navigate/model/security/package.scala +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA) -// For license information see LICENSE or https://opensource.org/licenses/BSD-3-Clause - -package navigate.model - -import cats.Eq -import io.circe.Decoder -import io.circe.Encoder - -package security { - // Shared classes used for authentication - case class UserLoginRequest(username: String, password: String) derives Decoder - - object UserLoginRequest { - given Eq[UserLoginRequest] = Eq.by(x => (x.username, x.password)) - } - - case class UserDetails(username: String, displayName: String) derives Encoder.AsObject - - object UserDetails { - // Some useful type aliases for user elements - type UID = String - type DisplayName = String - type Groups = List[String] - type Thumbnail = Array[Byte] - - given Eq[UserDetails] = Eq.by(x => (x.username, x.displayName)) - } - -} diff --git a/modules/model/shared/src/main/scala/navigate/model/Distance.scala b/modules/model/src/main/scala/navigate/model/Distance.scala similarity index 100% rename from modules/model/shared/src/main/scala/navigate/model/Distance.scala rename to modules/model/src/main/scala/navigate/model/Distance.scala diff --git a/modules/model/shared/src/main/scala/navigate/model/EngineInputEvent.scala b/modules/model/src/main/scala/navigate/model/EngineInputEvent.scala similarity index 100% rename from modules/model/shared/src/main/scala/navigate/model/EngineInputEvent.scala rename to modules/model/src/main/scala/navigate/model/EngineInputEvent.scala diff --git a/modules/model/shared/src/main/scala/navigate/model/NavigateCommand.scala b/modules/model/src/main/scala/navigate/model/NavigateCommand.scala similarity index 100% rename from modules/model/shared/src/main/scala/navigate/model/NavigateCommand.scala rename to modules/model/src/main/scala/navigate/model/NavigateCommand.scala diff --git a/modules/model/shared/src/main/scala/navigate/model/NavigateEvent.scala b/modules/model/src/main/scala/navigate/model/NavigateEvent.scala similarity index 96% rename from modules/model/shared/src/main/scala/navigate/model/NavigateEvent.scala rename to modules/model/src/main/scala/navigate/model/NavigateEvent.scala index 2a2b666e..f183db24 100644 --- a/modules/model/shared/src/main/scala/navigate/model/NavigateEvent.scala +++ b/modules/model/src/main/scala/navigate/model/NavigateEvent.scala @@ -6,8 +6,8 @@ package navigate.model import cats.Eq import cats.Order import cats.syntax.all.* +import lucuma.core.model.User import navigate.model.enums.ServerLogLevel -import navigate.model.security.UserDetails import java.time.Instant @@ -26,7 +26,7 @@ object NavigateEvent { case object NullEvent extends NavigateEvent case class ConnectionOpenEvent( - userDetails: Option[UserDetails], + userDetails: Option[User], clientId: ClientId, serverVersion: String ) extends NavigateEvent diff --git a/modules/model/jvm/src/main/scala/navigate/model/config/ControlStrategy.scala b/modules/model/src/main/scala/navigate/model/config/ControlStrategy.scala similarity index 100% rename from modules/model/jvm/src/main/scala/navigate/model/config/ControlStrategy.scala rename to modules/model/src/main/scala/navigate/model/config/ControlStrategy.scala diff --git a/modules/model/jvm/src/main/scala/navigate/model/config/Mode.scala b/modules/model/src/main/scala/navigate/model/config/Mode.scala similarity index 100% rename from modules/model/jvm/src/main/scala/navigate/model/config/Mode.scala rename to modules/model/src/main/scala/navigate/model/config/Mode.scala diff --git a/modules/model/jvm/src/main/scala/navigate/model/config/NavigateConfiguration.scala b/modules/model/src/main/scala/navigate/model/config/NavigateConfiguration.scala similarity index 82% rename from modules/model/jvm/src/main/scala/navigate/model/config/NavigateConfiguration.scala rename to modules/model/src/main/scala/navigate/model/config/NavigateConfiguration.scala index cb70ef50..691d7029 100644 --- a/modules/model/jvm/src/main/scala/navigate/model/config/NavigateConfiguration.scala +++ b/modules/model/src/main/scala/navigate/model/config/NavigateConfiguration.scala @@ -23,11 +23,10 @@ case class NavigateConfiguration( site: Site, mode: Mode, navigateEngine: NavigateEngineConfiguration, - webServer: WebServerConfiguration, - authentication: AuthenticationConfig + webServer: WebServerConfiguration ) object NavigateConfiguration { given Eq[NavigateConfiguration] = - Eq.by(x => (x.site, x.mode, x.navigateEngine, x.webServer, x.authentication)) + Eq.by(x => (x.site, x.mode, x.navigateEngine, x.webServer)) } diff --git a/modules/model/jvm/src/main/scala/navigate/model/config/NavigateEngineConfiguration.scala b/modules/model/src/main/scala/navigate/model/config/NavigateEngineConfiguration.scala similarity index 100% rename from modules/model/jvm/src/main/scala/navigate/model/config/NavigateEngineConfiguration.scala rename to modules/model/src/main/scala/navigate/model/config/NavigateEngineConfiguration.scala diff --git a/modules/model/jvm/src/main/scala/navigate/model/config/SystemsControlConfiguration.scala b/modules/model/src/main/scala/navigate/model/config/SystemsControlConfiguration.scala similarity index 100% rename from modules/model/jvm/src/main/scala/navigate/model/config/SystemsControlConfiguration.scala rename to modules/model/src/main/scala/navigate/model/config/SystemsControlConfiguration.scala diff --git a/modules/model/jvm/src/main/scala/navigate/model/config/WebServerConfiguration.scala b/modules/model/src/main/scala/navigate/model/config/WebServerConfiguration.scala similarity index 100% rename from modules/model/jvm/src/main/scala/navigate/model/config/WebServerConfiguration.scala rename to modules/model/src/main/scala/navigate/model/config/WebServerConfiguration.scala diff --git a/modules/model/shared/src/main/scala/navigate/model/enums/DomeMode.scala b/modules/model/src/main/scala/navigate/model/enums/DomeMode.scala similarity index 100% rename from modules/model/shared/src/main/scala/navigate/model/enums/DomeMode.scala rename to modules/model/src/main/scala/navigate/model/enums/DomeMode.scala diff --git a/modules/model/shared/src/main/scala/navigate/model/enums/M1Source.scala b/modules/model/src/main/scala/navigate/model/enums/M1Source.scala similarity index 100% rename from modules/model/shared/src/main/scala/navigate/model/enums/M1Source.scala rename to modules/model/src/main/scala/navigate/model/enums/M1Source.scala diff --git a/modules/model/shared/src/main/scala/navigate/model/enums/ServerLogLevel.scala b/modules/model/src/main/scala/navigate/model/enums/ServerLogLevel.scala similarity index 100% rename from modules/model/shared/src/main/scala/navigate/model/enums/ServerLogLevel.scala rename to modules/model/src/main/scala/navigate/model/enums/ServerLogLevel.scala diff --git a/modules/model/shared/src/main/scala/navigate/model/enums/ShutterMode.scala b/modules/model/src/main/scala/navigate/model/enums/ShutterMode.scala similarity index 100% rename from modules/model/shared/src/main/scala/navigate/model/enums/ShutterMode.scala rename to modules/model/src/main/scala/navigate/model/enums/ShutterMode.scala diff --git a/modules/model/shared/src/main/scala/navigate/model/enums/TipTiltSource.scala b/modules/model/src/main/scala/navigate/model/enums/TipTiltSource.scala similarity index 100% rename from modules/model/shared/src/main/scala/navigate/model/enums/TipTiltSource.scala rename to modules/model/src/main/scala/navigate/model/enums/TipTiltSource.scala diff --git a/modules/model/shared/src/main/scala/navigate/model/package.scala b/modules/model/src/main/scala/navigate/model/package.scala similarity index 100% rename from modules/model/shared/src/main/scala/navigate/model/package.scala rename to modules/model/src/main/scala/navigate/model/package.scala diff --git a/modules/web/server/src/main/scala/navigate/web/server/http4s/PingRoutes.scala b/modules/web/server/src/main/scala/navigate/web/server/http4s/PingRoutes.scala deleted file mode 100644 index 94f1b500..00000000 --- a/modules/web/server/src/main/scala/navigate/web/server/http4s/PingRoutes.scala +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA) -// For license information see LICENSE or https://opensource.org/licenses/BSD-3-Clause - -package navigate.web.server.http4s - -import cats.effect.Sync -import cats.syntax.all.* -import navigate.web.server.security.AuthenticationService -import navigate.web.server.security.Http4sAuthentication -import org.http4s.* -import org.http4s.dsl.* - -import AuthenticationService.AuthResult - -/** - * Rest Endpoints to ping the backend and detect when you're logged out - */ -class PingRoutes[F[_]: Sync](auth: AuthenticationService[F]) extends Http4sDsl[F] { - - private val httpAuthentication = new Http4sAuthentication(auth) - val pingService: AuthedRoutes[AuthResult, F] = - AuthedRoutes.of { case GET -> Root as user => - user.fold(_ => Response[F](Status.Unauthorized).pure[F], _ => Ok("")) - } - - def service: HttpRoutes[F] = httpAuthentication.optAuth(pingService) - -} diff --git a/modules/web/server/src/main/scala/navigate/web/server/http4s/WebServerLauncher.scala b/modules/web/server/src/main/scala/navigate/web/server/http4s/WebServerLauncher.scala index bf548430..5f26c56d 100644 --- a/modules/web/server/src/main/scala/navigate/web/server/http4s/WebServerLauncher.scala +++ b/modules/web/server/src/main/scala/navigate/web/server/http4s/WebServerLauncher.scala @@ -31,7 +31,6 @@ import navigate.web.server.common.StaticRoutes import navigate.web.server.common.baseDir import navigate.web.server.config.* import navigate.web.server.logging.SubscriptionAppender -import navigate.web.server.security.AuthenticationService import org.http4s.HttpRoutes import org.http4s.Uri import org.http4s.blaze.server.BlazeServerBuilder @@ -75,13 +74,6 @@ object WebServerLauncher extends IOApp with LogInitialization { (fileConfig, defaultConfig).mapN: (file, default) => file.optional.withFallback(default.optional) - /** Configures the Authentication service */ - def authService[F[_]: Sync: Logger]( - mode: Mode, - conf: AuthenticationConfig - ): F[AuthenticationService[F]] = - Sync[F].delay(AuthenticationService[F](mode, conf)) - def makeContext[F[_]: Sync](tls: TLSConfig): F[SSLContext] = Sync[F].delay { val ksStream = new FileInputStream(tls.keyStore.toFile.getAbsolutePath) val ks = KeyStore.getInstance("JKS") @@ -117,7 +109,6 @@ object WebServerLauncher extends IOApp with LogInitialization { /** Resource that yields the running web server */ def webServer[F[_]: Logger: Async: Dns: Files: Compression: Network]( conf: NavigateConfiguration, - as: AuthenticationService[F], outputs: Topic[F, NavigateEvent], logTopic: Topic[F, ILoggingEvent], guideTopic: Topic[F, GuideState], @@ -132,15 +123,10 @@ object WebServerLauncher extends IOApp with LogInitialization { ProxyRoute.toString -> proxyService ) - val pingRouter = Router[F]( - "/ping" -> new PingRoutes(as).service - ) - def loggedRoutes(wsBuilder: WebSocketBuilder2[F], proxyService: HttpRoutes[F]) = - pingRouter <+> - Http4sLogger.httpRoutes(logHeaders = false, logBody = false)( - router(wsBuilder, proxyService) - ) + Http4sLogger.httpRoutes(logHeaders = false, logBody = false)( + router(wsBuilder, proxyService) + ) def builder(proxyService: HttpRoutes[F]) = BlazeServerBuilder[F] @@ -247,19 +233,6 @@ object WebServerLauncher extends IOApp with LogInitialization { ) } yield seqE - def webServerIO( - conf: NavigateConfiguration, - out: Topic[IO, NavigateEvent], - log: Topic[IO, ILoggingEvent], - guide: Topic[IO, GuideState], - en: NavigateEngine[IO], - cs: ClientsSetDb[IO] - ): Resource[IO, Unit] = - for { - as <- Resource.eval(authService[IO](conf.mode, conf.authentication)) - _ <- webServer[IO](conf, as, out, log, guide, en, cs) - } yield () - def publishStats[F[_]: Temporal](cs: ClientsSetDb[F]): Stream[F, Unit] = Stream.fixedRate[F](10.minute).flatMap(_ => Stream.eval(cs.report)) @@ -280,7 +253,7 @@ object WebServerLauncher extends IOApp with LogInitialization { ) _ <- Resource.eval(publishStats(cs).compile.drain.start) engine <- engineIO(conf, cli) - _ <- webServerIO(conf, out, log, gd, engine, cs) + _ <- webServer[IO](conf, out, log, gd, engine, cs) _ <- Resource.eval( out.subscribers .evalMap(l => Logger[IO].debug(s"Subscribers amount: $l").whenA(l > 1)) diff --git a/modules/web/server/src/main/scala/navigate/web/server/security/FreeLDAPAuthenticationService.scala b/modules/web/server/src/main/scala/navigate/web/server/security/FreeLDAPAuthenticationService.scala deleted file mode 100644 index f0998331..00000000 --- a/modules/web/server/src/main/scala/navigate/web/server/security/FreeLDAPAuthenticationService.scala +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA) -// For license information see LICENSE or https://opensource.org/licenses/BSD-3-Clause - -package navigate.web.server.security - -import cats.* -import cats.effect.* -import cats.free.Free -import cats.syntax.all.* -import com.unboundid.ldap.sdk.* -import navigate.model.security.UserDetails -import org.typelevel.log4cats.Logger - -import AuthenticationService.AuthResult - -/** - * Definition of LDAP as a free monad algebra/interpreters - */ -object FreeLDAPAuthenticationService { - import LdapConnectionOps._ - import UserDetails._ - - sealed trait LdapOp[A] - // Operations on ldap - object LdapOp { - // Operation to authenticate a user, It returns true if it works - case class AuthenticateOp(username: String, password: String) extends LdapOp[UID] - // Read the user display name - case class UserDisplayNameOp(uid: UID) extends LdapOp[DisplayName] - // Reads the name, groups and thumbnail - case class DisplayNameGrpThumbOp(uid: UID) - extends LdapOp[(DisplayName, Groups, Option[Thumbnail])] - } - - // Free monad over the free functor of LdapOp. - type LdapM[A] = Free[LdapOp, A] - - // Smart constructors for LdapOp[A] - def bind(u: String, p: String): LdapM[UID] = Free.liftF(LdapOp.AuthenticateOp(u, p)) - def displayName(u: UID): LdapM[DisplayName] = Free.liftF(LdapOp.UserDisplayNameOp(u)) - def nameGroupsThumb(u: UID): LdapM[(DisplayName, Groups, Option[Thumbnail])] = - Free.liftF(LdapOp.DisplayNameGrpThumbOp(u)) - - // Natural transformation to IO - def toF[F[_]: Sync](c: LDAPConnection): LdapOp ~> F = - new (LdapOp ~> F) { - def apply[A](fa: LdapOp[A]): F[A] = - fa match { - case LdapOp.AuthenticateOp(u, p) => Sync[F].delay(c.authenticate(u, p)) - case LdapOp.UserDisplayNameOp(uid) => Sync[F].delay(c.displayName(uid)) - case LdapOp.DisplayNameGrpThumbOp(uid) => Sync[F].delay(c.nameGroupsThumb(uid)) - } - } - - // Run on IO - def runF[F[_]: Sync, A](a: LdapM[A], c: LDAPConnection): F[A] = - a.foldMap(toF(c)) - - // Programs - // Does simple user authentication - def authenticate(u: String, p: String): LdapM[UID] = bind(u, p) - - // Authenticate and reads the display name - def authenticationAndName(u: String, p: String): LdapM[UserDetails] = for { - u <- bind(u, p) - d <- displayName(u) - } yield UserDetails(u, d) - - // Authenticate and reads the name, groups and photo - def authNameGroupThumb(u: String, p: String): LdapM[(UserDetails, Groups, Option[Thumbnail])] = - for { - u <- bind(u, p) - d <- nameGroupsThumb(u) - } yield (UserDetails(u, d._1), d._2, d._3) -} - -/** - * Handles authentication against the AD/LDAP server - */ -class FreeLDAPAuthenticationService[F[_]: Sync: Logger](hosts: List[(String, Int)]) - extends AuthService[F] { - import FreeLDAPAuthenticationService._ - private given Eq[ResultCode] = Eq.fromUniversalEquals - - // Shorten the default timeout - private val Timeout = 1000 - private val Domain = "@gemini.edu" - - lazy val ldapOptions: LDAPConnectionOptions = { - val opts = new LDAPConnectionOptions() - opts.setConnectTimeoutMillis(Timeout) - opts - } - - // Will attempt several servers in case they fail - lazy val failoverServerSet = - new FailoverServerSet(hosts.map(_._1).toArray, hosts.map(_._2).toArray, ldapOptions) - - override def authenticateUser(username: String, password: String): F[AuthResult] = { - // We should always return the domain - val usernameWithDomain = if (username.endsWith(Domain)) username else s"$username$Domain" - - val rsrc = - for { - c <- Resource.make(Sync[F].delay(failoverServerSet.getConnection))(c => - Sync[F].delay(c.close()) - ) - x <- Resource.eval(runF(authenticationAndName(usernameWithDomain, password), c).attempt) - } yield x - - rsrc.use { - case Left(e: LDAPException) if e.getResultCode === ResultCode.NO_SUCH_OBJECT => - Logger[F].error(e)(s"Exception connection to LDAP server: ${e.getExceptionMessage}") *> - BadCredentials(username).asLeft[UserDetails].pure[F].widen[AuthResult] - case Left(e: LDAPException) if e.getResultCode === ResultCode.INVALID_CREDENTIALS => - Logger[F].error(e)(s"Exception connection to LDAP server: ${e.getExceptionMessage}") *> - UserNotFound(username).asLeft[UserDetails].pure[F].widen[AuthResult] - case Left(e: LDAPException) => - Logger[F].error(e)(s"Exception connection to LDAP server: ${e.getExceptionMessage}") *> - GenericFailure("LDAP Authentication error").asLeft[UserDetails].pure[F].widen[AuthResult] - case Left(e: Throwable) => - GenericFailure(e.getMessage).asLeft[UserDetails].pure[F].widen[AuthResult] - case Right(u) => - u.asRight.pure[F] - } - } - -} diff --git a/modules/web/server/src/main/scala/navigate/web/server/security/Http4sAuthentication.scala b/modules/web/server/src/main/scala/navigate/web/server/security/Http4sAuthentication.scala deleted file mode 100644 index 1b4fae22..00000000 --- a/modules/web/server/src/main/scala/navigate/web/server/security/Http4sAuthentication.scala +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA) -// For license information see LICENSE or https://opensource.org/licenses/BSD-3-Clause - -package navigate.web.server.security - -import cats.* -import cats.data.Kleisli -import cats.data.OptionT -import cats.effect.Sync -import cats.syntax.all.* -import navigate.model.security.UserDetails -import org.http4s.* -import org.http4s.dsl.* -import org.http4s.server.AuthMiddleware - -import java.time.Instant - -import AuthenticationService.AuthResult - -/** - * Bridge between http4s authentication and the actual internal authenticator - */ -class Http4sAuthentication[F[_]: Sync](auth: AuthenticationService[F]) extends Http4sDsl[F] { - private val cookieService = - CookiesService(auth.config.cookieName, auth.config.useSSL, auth.sessionTimeout) - - def loginCookie(user: UserDetails): F[ResponseCookie] = - cookieService.loginCookie(auth, user) - - private def authRequest(request: Request[F]) = { - val authResult = for { - header <- request.headers.get[headers.Cookie].toRight(MissingCookie) - cookie <- header.values.toList.find(_.name === auth.config.cookieName).toRight(MissingCookie) - user <- auth.decodeToken(cookie.content) - } yield user - authResult - } - - val authUser: Kleisli[F, Request[F], AuthResult] = - Kleisli(request => Sync[F].delay(authRequest(request))) - - val optAuthUser: Kleisli[OptionT[F, *], Request[F], AuthResult] = - Kleisli(request => OptionT.liftF(Sync[F].delay(authRequest(request)))) - - val optAuth: AuthMiddleware[F, AuthResult] = AuthMiddleware(optAuthUser) - - private val onFailure: AuthedRoutes[AuthenticationFailure, F] = - Kleisli(_ => OptionT.liftF(Forbidden())) - val reqAuth: AuthMiddleware[F, UserDetails] = AuthMiddleware(authUser, onFailure) - -} - -/** - * Middleware used to keep the session alive as long as authenticated request are coming It has some - * cost as it needs to decode/encode the cookie with JWT - */ -object TokenRefresher { - private def replaceCookie[F[_]: Monad](service: HttpRoutes[F], auth: Http4sAuthentication[F])( - result: AuthResult - ): Kleisli[OptionT[F, *], Request[F], Response[F]] = Kleisli { request => - result.fold(_ => service(request), - u => - OptionT.liftF(auth.loginCookie(u)) >>= { c => - service.map(_.addCookie(c)).apply(request) - } - ) - } - - def apply[F[_]: Monad](service: HttpRoutes[F], auth: Http4sAuthentication[F]): HttpRoutes[F] = - auth.optAuthUser.flatMap(replaceCookie[F](service, auth)) -} - -trait CookiesService { - def name: String - def ssl: Boolean - def ttl: Long - - def buildCookie[F[_]: Sync](token: String): F[ResponseCookie] = - Sync[F] - .delay { - HttpDate.fromInstant(Instant.now().plusSeconds(ttl)) - } - .map { exp => - ResponseCookie(name, - token, - path = "/".some, - expires = exp.toOption, - secure = ssl, - httpOnly = true - ) - } - - def loginCookie[F[_]: Sync]( - auth: AuthenticationService[F], - user: UserDetails - ): F[ResponseCookie] = - auth.buildToken(user) >>= buildCookie[F] -} - -object CookiesService { - def apply(cookieName: String, useSSL: Boolean, timeToLive: Long): CookiesService = - new CookiesService { - override val name: String = cookieName - override val ssl: Boolean = useSSL - override val ttl: Long = timeToLive - } -} diff --git a/modules/web/server/src/main/scala/navigate/web/server/security/LdapConnectionOps.scala b/modules/web/server/src/main/scala/navigate/web/server/security/LdapConnectionOps.scala deleted file mode 100644 index f91876c0..00000000 --- a/modules/web/server/src/main/scala/navigate/web/server/security/LdapConnectionOps.scala +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA) -// For license information see LICENSE or https://opensource.org/licenses/BSD-3-Clause - -package navigate.web.server.security - -import com.unboundid.ldap.sdk.SearchRequest -import com.unboundid.ldap.sdk.SearchScope -import com.unboundid.ldap.sdk.* -import navigate.model.security.UserDetails.* - -import scala.jdk.CollectionConverters.* - -object LdapConnectionOps { - // Extension methods for ldap connection - extension (c: LDAPConnection) { - def authenticate(u: String, p: String): UID = { - val UidExtractor = s"([\\w\\.]*)(\\@.*)?".r - - val bindRequest = new SimpleBindRequest(u, p) - // Authenticate, it throws an exception if it fails - c.bind(bindRequest) - // Uid shouldn't have domain - u match { - case UidExtractor(uid, _) => uid - case uid => uid - } - } - - def displayName(uid: UID): DisplayName = { - val dn = for { - a <- attributes(uid, List("displayName")).get("displayName") - d <- a.headOption - } yield d - dn.getOrElse("-") - } - - def nameGroupsThumb(uid: UID): (DisplayName, Groups, Option[Thumbnail]) = { - val attrs = attributes(uid, List("displayName", "memberOf", "thumbnailPhoto")) - - val dn = for { - a <- attrs.get("displayName") - d <- a.headOption.filter(_ => false) - } yield d - - // Read the groups - val gr = attrs.getOrElse("memberOf", Nil) - val groups = gr.map { g => - val grDN = new DN(g) - for { - rdn <- grDN.getRDNs.toList - if rdn.hasAttribute("CN") && !rdn.hasAttributeValue("CN", "Users") - } yield rdn.getAttributeValues.toList - } - - // Read the thumbnail if possible - val thBytes = for { - ph <- attrs.get("thumbnailPhoto") - th <- ph.headOption - } yield th.getBytes - - (dn.getOrElse("-"), groups.flatten.flatten, thBytes) - } - - // Search for a user and find attributes. All attributes are String in LDAP - private def attributes(uid: UID, attributes: List[String]): Map[String, List[String]] = { - def readAttr(e: SearchResultEntry)(attr: String): Option[(String, List[String])] = - for { - a <- Option(e.getAttribute(attr)) - d <- Option(a.getValues.toList) - } yield attr -> d - - val baseDN = c.getRootDSE.getAttributeValue("namingContexts") - val filter = Filter.createANDFilter( - Filter.createEqualityFilter("sAMAccountName", uid), - Filter.createEqualityFilter("objectClass", "user") - ) - - // val attributes = List("displayName", "memberOf", "thumbnailPhoto") - val search = new SearchRequest(s"cn=users,$baseDN", SearchScope.SUB, filter, attributes: _*) - // Search to read user data, it may throw an exception - val searchResult = c.search(search) - - val r = for { - s <- searchResult.getSearchEntries.asScala.headOption - } yield attributes.map(readAttr(s)) - r.map(_.collect { case Some(i) => i }.toMap).getOrElse(Map.empty) - } - } -} diff --git a/modules/web/server/src/main/scala/navigate/web/server/security/TestAuthenticationService.scala b/modules/web/server/src/main/scala/navigate/web/server/security/TestAuthenticationService.scala deleted file mode 100644 index f50c47c4..00000000 --- a/modules/web/server/src/main/scala/navigate/web/server/security/TestAuthenticationService.scala +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA) -// For license information see LICENSE or https://opensource.org/licenses/BSD-3-Clause - -package navigate.web.server.security - -import cats.Applicative -import cats.syntax.all.* -import navigate.model.security.UserDetails - -import AuthenticationService.AuthResult - -/** - * Authentication service for testing with a hardcoded list of users It lets you avoid the LDAP - * dependency but should not be used in production - */ -class TestAuthenticationService[F[_]: Applicative] extends AuthService[F] { - private val cannedUsers = List(UserDetails("telops", "Telops") -> "pwd") - - override def authenticateUser(username: String, password: String): F[AuthResult] = - cannedUsers - .collectFirst { - case (ud @ UserDetails(u, _), p) if u === username && p === password => ud - } - .fold(BadCredentials(username).asLeft[UserDetails])(_.asRight) - .pure[F] - .widen[AuthResult] -} diff --git a/modules/web/server/src/main/scala/navigate/web/server/security/package.scala b/modules/web/server/src/main/scala/navigate/web/server/security/package.scala deleted file mode 100644 index e38f8bce..00000000 --- a/modules/web/server/src/main/scala/navigate/web/server/security/package.scala +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA) -// For license information see LICENSE or https://opensource.org/licenses/BSD-3-Clause - -package navigate.web.server - -import cats.* -import cats.effect.* -import cats.syntax.all.* -import com.unboundid.ldap.sdk.LDAPURL -import io.circe.* -import io.circe.generic.semiauto.deriveCodec -import io.circe.jawn.decode -import io.circe.syntax.* -import navigate.model.config.* -import navigate.model.security.UserDetails -import org.typelevel.log4cats.Logger -import pdi.jwt.Jwt -import pdi.jwt.JwtAlgorithm -import pdi.jwt.JwtCirce -import pdi.jwt.JwtClaim - -import java.time - -import security.AuthenticationService.AuthResult - -package security { - sealed trait AuthenticationFailure extends Product with Serializable - case class UserNotFound(user: String) extends AuthenticationFailure - case class BadCredentials(user: String) extends AuthenticationFailure - case object NoAuthenticator extends AuthenticationFailure - case class GenericFailure(msg: String) extends AuthenticationFailure - case class DecodingFailure(msg: String) extends AuthenticationFailure - case object MissingCookie extends AuthenticationFailure - - /** - * Interface for implementations that can authenticate users from a username/pwd pair - */ - trait AuthService[F[_]] { - def authenticateUser(username: String, password: String): F[AuthResult] - } - - // Intermediate class to decode the claim stored in the JWT token - case class JwtUserClaim(exp: Int, iat: Int, username: String, displayName: String) { - def toUserDetails: UserDetails = UserDetails(username, displayName) - } - - case class AuthenticationService[F[_]: Sync: Logger]( - mode: Mode, - config: AuthenticationConfig - ) extends AuthService[F] { - import AuthenticationService._ - given time.Clock = java.time.Clock.systemUTC() - - private val hosts = - config.ldapUrls.map(u => new LDAPURL(u.renderString)).map(u => (u.getHost, u.getPort)) - - val ldapService: AuthService[F] = new FreeLDAPAuthenticationService(hosts) - - given Codec[UserDetails] = deriveCodec - - private val authServices = - if (mode === Mode.Development) List(new TestAuthenticationService[F], ldapService) - else List(ldapService) - - /** - * From the user details it creates a JSON Web Token - */ - def buildToken(u: UserDetails): F[String] = Sync[F].delay { - // Given that only this server will need the key we can just use HMAC. 512-bit is the max key size allowed - Jwt.encode( - JwtClaim(u.asJson.noSpaces).issuedNow.expiresIn(config.sessionLifeHrs.toSeconds), - config.secretKey, - JwtAlgorithm.HS512 - ) - } - - /** - * Decodes a token out of JSON Web Token - */ - def decodeToken(t: String): AuthResult = - for { - claim <- JwtCirce - .decode(t, config.secretKey, Seq(JwtAlgorithm.HS512)) - .toEither - .leftMap(t => DecodingFailure(t.getMessage)) - userDetails <- - decode[UserDetails](claim.content).leftMap(e => DecodingFailure(e.getMessage)) - } yield userDetails - - val sessionTimeout: Long = config.sessionLifeHrs.toSeconds - - override def authenticateUser(username: String, password: String): F[AuthResult] = - authServices.authenticateUser(username, password) - } - - object AuthenticationService { - type AuthResult = Either[AuthenticationFailure, UserDetails] - type AuthenticationServices[F[_]] = List[AuthService[F]] - - // Allows calling authenticate on a list of authenticator, stopping at the first - // that succeeds - extension [F[_]: MonadThrow: Logger]( - s: AuthenticationServices[F] - ) { - - def authenticateUser(username: String, password: String): F[AuthResult] = { - def go(l: List[AuthService[F]]): F[AuthResult] = l match { - case Nil => NoAuthenticator.asLeft[UserDetails].pure[F].widen[AuthResult] - case x :: xs => - x.authenticateUser(username, password).attempt.flatMap { - case Right(Right(u)) => u.asRight.pure[F] - case Right(Left(e)) => Logger[F].warn(s"Auth method error $x with $e") *> go(xs) - case _ => go(xs) - } - } - // Discard empty values right away - if (username.isEmpty || password.isEmpty) { - BadCredentials(username).asLeft[UserDetails].pure[F].widen[AuthResult] - } else { - go(s).flatTap(a => Logger[F].info(a.toString)) - } - } - } - } - -} diff --git a/project/Settings.scala b/project/Settings.scala index 580a2a07..2868cff1 100644 --- a/project/Settings.scala +++ b/project/Settings.scala @@ -9,61 +9,33 @@ object Settings { /** Library versions */ object LibraryVersions { - // ScalaJS libraries - val scalaDom = "2.3.0" - val scalajsReact = "2.1.1" - val booPickle = "1.4.0" - val javaTimeJS = "2.5.0" - val javaLocales = "1.5.1" - val lucumaReact = "0.25.0" - val scalaJSSemanticUI = "0.13.1" - val scalaJSReactVirtualized = "0.13.1" - val scalaJSReactClipboard = "1.5.1" - val scalaJSReactDraggable = "0.16.0" - val scalaJSReactSortable = "0.5.2" // Scala libraries - val catsEffect = "3.5.2" - val cats = "2.10.0" - val mouse = "1.2.2" - val fs2 = "3.9.3" - val shapeless = "2.3.7" - val scalaParsers = "1.1.2" - val scalaXml = "1.2.0" - val catsTime = "0.5.1" + val catsEffect = "3.5.2" + val cats = "2.10.0" + val mouse = "1.2.2" + val fs2 = "3.9.3" + val catsTime = "0.5.1" // Logging val log4Cats = "2.6.0" val log4CatsLogLevel = "0.3.1" - val http4s = "0.23.25" - val http4sBlaze = "0.23.16" - val http4sBoopickle = "0.23.11" - val http4sXml = "0.23.12" - val http4sPrometheus = "0.23.12" - val squants = "1.8.3" - val commonsHttp = "3.1" - val unboundId = "3.2.1" - val jwt = "9.4.5" - val slf4j = "2.0.11" - val log4s = "1.10.0" - val logback = "1.4.14" - val janino = "3.1.11" - val logstash = "7.0" - val pureConfig = "0.17.4" - val monocle = "3.2.0" - val circe = "0.14.6" - val doobie = "0.6.0" - val flyway = "6.0.4" + val http4s = "0.23.25" + val http4sBlaze = "0.23.16" + val slf4j = "2.0.11" + val log4s = "1.10.0" + val logback = "1.4.14" + val janino = "3.1.11" + val logstash = "7.0" + val pureConfig = "0.17.4" + val monocle = "3.2.0" + val circe = "0.14.6" // test libraries - val xmlUnit = "1.6" - val scalaMock = "5.2.0" - lazy val munitCatsEffectVersion = "1.0.7" + val scalaMock = "5.2.0" + val munitCatsEffectVersion = "1.0.7" - val apacheXMLRPC = "3.1.3" - val opencsv = "2.3" - val epicsService = "1.0.7" val gmpCommandRecords = "0.7.7" val giapi = "1.1.7" val giapiJmsUtil = "0.5.7" @@ -74,23 +46,15 @@ object Settings { val gmpStatusDatabase = "0.3.7" val gmpCmdClientBridge = "0.6.7" val guava = "31.0.1-jre" - val prometheusClient = "0.16.0" val geminiLocales = "0.7.0" val pprint = "0.8.1" - val jaxb = "3.0.1" // EPICS Libraries val ca = "1.3.2" val jca = "2.4.9" - // Gemini Libraries - val gspMath = "0.1.17" - val gspCore = "0.1.8" - val gppUI = "0.0.3" - // Lucuma val lucumaCore = "0.91.0" - val lucumaUI = "0.66.0" val lucumaSchemas = "0.68.0" val grackle = "0.18.0" @@ -99,14 +63,8 @@ object Settings { val clue = "0.35.0" - val sttp = "3.9.1" - - // Pure JS libraries - val fomanticUI = "2.8.7" - // Natchez val natchez = "0.3.5" - } /** @@ -114,33 +72,26 @@ object Settings { */ object Libraries { // Test Libraries - val TestLibs = Def.setting( + val TestLibs = Def.setting( "org.typelevel" %%% "cats-testkit-scalatest" % "2.1.5" % "test" ) - val MUnit = Def.setting( + val MUnit = Def.setting( Seq( "org.typelevel" %% "munit-cats-effect-3" % LibraryVersions.munitCatsEffectVersion % Test ) ) - val XmlUnit = "xmlunit" % "xmlunit" % LibraryVersions.xmlUnit % "test" - val ScalaMock = "org.scalamock" %% "scalamock" % LibraryVersions.scalaMock % "test" + val ScalaMock = "org.scalamock" %% "scalamock" % LibraryVersions.scalaMock % "test" // Server side libraries - val Cats = Def.setting("org.typelevel" %%% "cats-core" % LibraryVersions.cats) - val CatsLaws = Def.setting("org.typelevel" %%% "cats-laws" % LibraryVersions.cats % "test") - val CatsEffect = + val Cats = Def.setting("org.typelevel" %%% "cats-core" % LibraryVersions.cats) + val CatsLaws = Def.setting("org.typelevel" %%% "cats-laws" % LibraryVersions.cats % "test") + val CatsEffect = Def.setting("org.typelevel" %%% "cats-effect" % LibraryVersions.catsEffect) - val Fs2 = "co.fs2" %% "fs2-core" % LibraryVersions.fs2 - val Fs2IO = "co.fs2" %% "fs2-io" % LibraryVersions.fs2 % "test" - val Mouse = Def.setting("org.typelevel" %%% "mouse" % LibraryVersions.mouse) - val Shapeless = Def.setting("com.chuusai" %%% "shapeless" % LibraryVersions.shapeless) - val CommonsHttp = "commons-httpclient" % "commons-httpclient" % LibraryVersions.commonsHttp - val UnboundId = - "com.unboundid" % "unboundid-ldapsdk-minimal-edition" % LibraryVersions.unboundId - val JwtCore = "com.github.jwt-scala" %% "jwt-core" % LibraryVersions.jwt - val JwtCirce = "com.github.jwt-scala" %% "jwt-circe" % LibraryVersions.jwt - val Slf4j = "org.slf4j" % "slf4j-api" % LibraryVersions.slf4j - val JuliSlf4j = "org.slf4j" % "jul-to-slf4j" % LibraryVersions.slf4j - val NopSlf4j = "org.slf4j" % "slf4j-nop" % LibraryVersions.slf4j + val Fs2 = "co.fs2" %% "fs2-core" % LibraryVersions.fs2 + val Fs2IO = "co.fs2" %% "fs2-io" % LibraryVersions.fs2 % "test" + val Mouse = Def.setting("org.typelevel" %%% "mouse" % LibraryVersions.mouse) + val Slf4j = "org.slf4j" % "slf4j-api" % LibraryVersions.slf4j + val JuliSlf4j = "org.slf4j" % "jul-to-slf4j" % LibraryVersions.slf4j + val NopSlf4j = "org.slf4j" % "slf4j-nop" % LibraryVersions.slf4j val CatsTime = Def.setting( "org.typelevel" %%% "cats-time" % LibraryVersions.catsTime % "compile->compile;test->test" ) @@ -159,8 +110,6 @@ object Settings { "ch.qos.logback" % "logback-classic" % LibraryVersions.logback, "org.codehaus.janino" % "janino" % LibraryVersions.janino ) - val PrometheusClient = - "io.prometheus" % "simpleclient_common" % LibraryVersions.prometheusClient val Logging = Def.setting(Seq(JuliSlf4j, Log4s.value) ++ Logback) val PureConfig = Seq( "com.github.pureconfig" %% "pureconfig-core" % LibraryVersions.pureConfig, @@ -168,10 +117,6 @@ object Settings { "com.github.pureconfig" %% "pureconfig-cats-effect" % LibraryVersions.pureConfig, "com.github.pureconfig" %% "pureconfig-http4s" % LibraryVersions.pureConfig ) - val OpenCSV = "net.sf.opencsv" % "opencsv" % LibraryVersions.opencsv - val Squants = Def.setting("org.typelevel" %%% "squants" % LibraryVersions.squants) - val ScalaXml = - Def.setting("org.scala-lang.modules" %%% "scala-xml" % LibraryVersions.scalaXml) val Http4s = Seq("org.http4s" %% "http4s-dsl" % LibraryVersions.http4s, "org.http4s" %% "http4s-blaze-server" % LibraryVersions.http4sBlaze ) @@ -179,13 +124,9 @@ object Settings { "org.http4s" %% "http4s-dsl" % LibraryVersions.http4s, "org.http4s" %% "http4s-ember-client" % LibraryVersions.http4s ) - val Http4sBoopickle = "org.http4s" %% "http4s-boopickle" % LibraryVersions.http4sBoopickle - val Http4sCore = "org.http4s" %% "http4s-core" % LibraryVersions.http4s - val Http4sCirce = "org.http4s" %% "http4s-circe" % LibraryVersions.http4s - val Http4sXml = "org.http4s" %% "http4s-scala-xml" % LibraryVersions.http4sXml - val Http4sPrometheus = - "org.http4s" %% "http4s-prometheus-metrics" % LibraryVersions.http4sPrometheus - val Monocle = Def.setting( + val Http4sCore = "org.http4s" %% "http4s-core" % LibraryVersions.http4s + val Http4sCirce = "org.http4s" %% "http4s-circe" % LibraryVersions.http4s + val Monocle = Def.setting( Seq( "dev.optics" %%% "monocle-core" % LibraryVersions.monocle, "dev.optics" %%% "monocle-macro" % LibraryVersions.monocle, @@ -193,7 +134,7 @@ object Settings { "dev.optics" %%% "monocle-law" % LibraryVersions.monocle ) ) - val Circe = Def.setting( + val Circe = Def.setting( Seq( "io.circe" %%% "circe-core" % LibraryVersions.circe, "io.circe" %%% "circe-generic" % LibraryVersions.circe, @@ -202,48 +143,7 @@ object Settings { ) ) - // Client Side JS libraries - val ReactScalaJS = Def.setting( - Seq( - "com.github.japgolly.scalajs-react" %%% "core" % LibraryVersions.scalajsReact, - "com.github.japgolly.scalajs-react" %%% "extra" % LibraryVersions.scalajsReact, - "com.github.japgolly.scalajs-react" %%% "extra-ext-monocle3" % LibraryVersions.scalajsReact, - "com.github.japgolly.scalajs-react" %%% "core-ext-cats" % LibraryVersions.scalajsReact - ) - ) - val ScalaJSDom = Def.setting("org.scala-js" %%% "scalajs-dom" % LibraryVersions.scalaDom) - val ScalaJSReactCommon = - Def.setting("io.github.cquiroz.react" %%% "lucuma-react-common" % LibraryVersions.lucumaReact) - val ScalaJSReactSemanticUI = Def.setting( - "edu.gemini" %%% "lucuma-react-semantic-ui" % LibraryVersions.lucumaReact - ) - val ScalaJSReactVirtualized = Def.setting( - "io.github.cquiroz.react" %%% "react-virtualized" % LibraryVersions.scalaJSReactVirtualized - ) - val ScalaJSReactDraggable = Def.setting( - "edu.gemini" %%% "lucuma-react-draggable" % LibraryVersions.lucumaReact - ) - val ScalaJSReactSortable = Def.setting( - "io.github.cquiroz.react" %%% "react-sortable-hoc" % LibraryVersions.scalaJSReactSortable - ) - val ScalaJSReactClipboard = Def.setting( - "edu.gemini" %%% "lucuma-react-clipboard" % LibraryVersions.lucumaReact - ) - val BooPickle = Def.setting("io.suzaku" %%% "boopickle" % LibraryVersions.booPickle) - val JavaTimeJS = - Def.setting("io.github.cquiroz" %%% "scala-java-time" % LibraryVersions.javaTimeJS) - val JavaLocales = - Def.setting("io.github.cquiroz" %%% "scala-java-locales" % LibraryVersions.javaLocales) - val GeminiLocales = - Def.setting("edu.gemini" %%% "gemini-locales" % LibraryVersions.geminiLocales) - val PPrint = Def.setting("com.lihaoyi" %%% "pprint" % LibraryVersions.pprint) - - val JAXB = Seq("javax.xml.bind" % "jaxb-api" % LibraryVersions.jaxb, - "org.glassfish.jaxb" % "jaxb-runtime" % LibraryVersions.jaxb - ) - // GIAPI Libraries - val EpicsService = "edu.gemini.epics" % "epics-service" % LibraryVersions.epicsService val GmpCommandsRecords = "edu.gemini.gmp" % "gmp-commands-records" % LibraryVersions.gmpCommandRecords val GiapiJmsUtil = "edu.gemini.aspen" % "giapi-jms-util" % LibraryVersions.giapiJmsUtil @@ -267,16 +167,6 @@ object Settings { val EpicsJCA = "org.epics" % "jca" % LibraryVersions.jca val EpicsCA = "org.epics" % "ca" % LibraryVersions.ca - // Gemini Libraries -// val GspMath = Def.setting("edu.gemini" %%% "gsp-math" % LibraryVersions.gspMath) -// val GspMathTestkit = -// Def.setting("edu.gemini" %%% "gsp-math-testkit" % LibraryVersions.gspMath % "test") -// val GspCoreModel = Def.setting("edu.gemini" %%% "gsp-core-model" % LibraryVersions.gspCore) -// val GspCoreTestkit = -// Def.setting("edu.gemini" %%% "gsp-core-testkit" % LibraryVersions.gspCore % "test") -// val GspCoreOcs2Api = Def.setting("edu.gemini" %%% "gsp-core-ocs2-api" % LibraryVersions.gspCore) -// val GppUI = Def.setting("edu.gemini" %%% "gpp-ui" % LibraryVersions.gppUI) - // Lucuma libraries val LucumaCore = Def.setting( Seq( @@ -284,7 +174,6 @@ object Settings { "edu.gemini" %%% "lucuma-core-testkit" % LibraryVersions.lucumaCore ) ) - val LucumaUI = Def.setting("edu.gemini" %%% "lucuma-ui" % LibraryVersions.lucumaUI) val LucumaSchemas = "edu.gemini" %% "lucuma-schemas" % LibraryVersions.lucumaSchemas val Grackle = Def.setting( @@ -302,14 +191,6 @@ object Settings { val ClueHttp4s = "edu.gemini" %% "clue-http4s-jdk-client" % LibraryVersions.clue val ClueGenerator = "edu.gemini" %% "clue-generator" % LibraryVersions.clue - val Sttp = Def.setting( - Seq( - "com.softwaremill.sttp.client3" %%% "core" % LibraryVersions.sttp, - "com.softwaremill.sttp.client3" %%% "circe" % LibraryVersions.sttp, - "com.softwaremill.sttp.client3" %%% "cats" % LibraryVersions.sttp - ) - ) - val Natchez = "org.tpolecat" %% "natchez-core" % LibraryVersions.natchez } diff --git a/project/plugins.sbt b/project/plugins.sbt index 26e8527e..0b64edef 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,13 +1,11 @@ resolvers ++= Resolver.sonatypeOssRepos("public") addDependencyTreePlugin -addSbtPlugin("edu.gemini" % "sbt-lucuma-app" % "0.11.10") +addSbtPlugin("edu.gemini" % "sbt-lucuma-app" % "0.11.10") // sbt revolver lets launching applications from the sbt console -addSbtPlugin("io.spray" % "sbt-revolver" % "0.10.0") -addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.6.4") -addSbtPlugin("ch.epfl.scala" % "sbt-scalajs-bundler" % "0.21.1") +addSbtPlugin("io.spray" % "sbt-revolver" % "0.10.0") // Support making distributions -addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.9.16") +addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.9.16") // Extract metadata from sbt and make it available to the code addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.11.0")