diff --git a/app/WebknossosModule.scala b/app/WebknossosModule.scala index 8832d9b5a9..69cd6d79b2 100644 --- a/app/WebknossosModule.scala +++ b/app/WebknossosModule.scala @@ -1,6 +1,6 @@ import com.google.inject.AbstractModule import com.scalableminds.webknossos.datastore.storage.DataVaultService -import controllers.InitialDataService +import controllers.{Application, InitialDataService} import files.TempFileService import mail.MailchimpTicker import models.analytics.AnalyticsSessionService @@ -17,6 +17,7 @@ import utils.sql.SqlClient class WebknossosModule extends AbstractModule { override def configure(): Unit = { + bind(classOf[Application]).asEagerSingleton() bind(classOf[Startup]).asEagerSingleton() bind(classOf[SqlClient]).asEagerSingleton() bind(classOf[InitialDataService]).asEagerSingleton() diff --git a/app/controllers/Application.scala b/app/controllers/Application.scala index b781a488ef..a1249fac35 100755 --- a/app/controllers/Application.scala +++ b/app/controllers/Application.scala @@ -7,7 +7,7 @@ import models.organization.OrganizationDAO import models.user.UserService import org.apache.pekko.actor.ActorSystem import play.api.libs.json.Json -import play.api.mvc.{Action, AnyContent} +import play.api.mvc.{Action, AnyContent, Result} import play.silhouette.api.Silhouette import security.WkEnv import utils.sql.{SimpleSQLDAO, SqlClient} @@ -51,9 +51,12 @@ class Application @Inject()(actorSystem: ActorSystem, } } + // This only changes on server restart, so we can cache the full result. + private lazy val cachedFeaturesResult: Result = addNoCacheHeaderFallback( + Ok(conf.raw.underlying.getConfig("features").resolve.root.render(ConfigRenderOptions.concise())).as(jsonMimeType)) + def features: Action[AnyContent] = sil.UserAwareAction { - addNoCacheHeaderFallback( - Ok(conf.raw.underlying.getConfig("features").resolve.root.render(ConfigRenderOptions.concise())).as(jsonMimeType)) + cachedFeaturesResult } def health: Action[AnyContent] = Action { diff --git a/app/controllers/AuthenticationController.scala b/app/controllers/AuthenticationController.scala index c186e6203a..4da99a69d5 100755 --- a/app/controllers/AuthenticationController.scala +++ b/app/controllers/AuthenticationController.scala @@ -277,7 +277,8 @@ class AuthenticationController @Inject()( case None => Future.successful(NotFound(Messages("error.noUser"))) case Some(user) => for { - token <- bearerTokenAuthenticatorService.createAndInit(user.loginInfo, TokenType.ResetPassword) + token <- bearerTokenAuthenticatorService + .createAndInit(user.loginInfo, TokenType.ResetPassword, deleteOld = true) } yield { Mailer ! Send(defaultMails.resetPasswordMail(user.name, email.toLowerCase, token)) Ok diff --git a/app/controllers/OrganizationController.scala b/app/controllers/OrganizationController.scala index f1bd61c9c4..f7bf617079 100755 --- a/app/controllers/OrganizationController.scala +++ b/app/controllers/OrganizationController.scala @@ -41,9 +41,9 @@ class OrganizationController @Inject()( def organizationsIsEmpty: Action[AnyContent] = Action.async { implicit request => for { - allOrgs <- organizationDAO.findAll(GlobalAccessContext) ?~> "organization.list.failed" + orgaTableIsEmpty <- organizationDAO.isEmpty ?~> "organization.list.failed" } yield { - Ok(Json.toJson(allOrgs.isEmpty)) + Ok(Json.toJson(orgaTableIsEmpty)) } } diff --git a/app/models/organization/Organization.scala b/app/models/organization/Organization.scala index f7896604ab..cc4f701eae 100644 --- a/app/models/organization/Organization.scala +++ b/app/models/organization/Organization.scala @@ -80,6 +80,12 @@ class OrganizationDAO @Inject()(sqlClient: SqlClient)(implicit ec: ExecutionCont parsed <- parseAll(r) } yield parsed + def isEmpty: Fox[Boolean] = + for { + rows <- run(q"SELECT COUNT(*) FROM $existingCollectionName".as[Int]) + value <- rows.headOption + } yield value == 0 + @deprecated("use findOne with string type instead", since = "") override def findOne(id: ObjectId)(implicit ctx: DBAccessContext): Fox[Organization] = Fox.failure("Cannot find organization by ObjectId. Use findOne with string type instead") diff --git a/app/security/BearerTokenAuthenticatorRepository.scala b/app/security/BearerTokenAuthenticatorRepository.scala index fcf22cef41..36c86d1674 100644 --- a/app/security/BearerTokenAuthenticatorRepository.scala +++ b/app/security/BearerTokenAuthenticatorRepository.scala @@ -56,16 +56,20 @@ class BearerTokenAuthenticatorRepository(tokenDAO: TokenDAO)(implicit ec: Execut def add(authenticator: BearerTokenAuthenticator, tokenType: TokenType, - deleteOld: Boolean = true): Future[BearerTokenAuthenticator] = + deleteOld: Boolean = true): Future[BearerTokenAuthenticator] = { + if (deleteOld) { + removeByLoginInfoIfPresent(authenticator.loginInfo, tokenType) + } for { - oldAuthenticatorOpt <- findOneByLoginInfo(authenticator.loginInfo, tokenType) _ <- insert(authenticator, tokenType).futureBox - } yield { - if (deleteOld) { - oldAuthenticatorOpt.map(a => remove(a.id)) - } - authenticator - } + } yield authenticator + } + + private def removeByLoginInfoIfPresent(loginInfo: LoginInfo, tokenType: TokenType): Unit = + for { + oldOpt <- findOneByLoginInfo(loginInfo, tokenType) + _ = oldOpt.foreach(old => remove(old.id)) + } yield () private def insert(authenticator: BearerTokenAuthenticator, tokenType: TokenType): Fox[Unit] = for { diff --git a/app/security/CombinedAuthenticatorService.scala b/app/security/CombinedAuthenticatorService.scala index 090ba741e8..6cd0a5f303 100644 --- a/app/security/CombinedAuthenticatorService.scala +++ b/app/security/CombinedAuthenticatorService.scala @@ -39,14 +39,14 @@ case class CombinedAuthenticatorService(cookieSettings: CookieAuthenticatorSetti private val cookieSigner = new JcaSigner(JcaSignerSettings(conf.Silhouette.CookieAuthenticator.signerSecret)) - val cookieAuthenticatorService = new CookieAuthenticatorService(cookieSettings, - None, - cookieSigner, - cookieHeaderEncoding, - new Base64AuthenticatorEncoder, - fingerprintGenerator, - idGenerator, - clock) + private val cookieAuthenticatorService = new CookieAuthenticatorService(cookieSettings, + None, + cookieSigner, + cookieHeaderEncoding, + new Base64AuthenticatorEncoder, + fingerprintGenerator, + idGenerator, + clock) val tokenAuthenticatorService = new WebknossosBearerTokenAuthenticatorService(tokenSettings, tokenDao, idGenerator, clock, userService, conf) @@ -55,9 +55,9 @@ case class CombinedAuthenticatorService(cookieSettings: CookieAuthenticatorSetti override def create(loginInfo: LoginInfo)(implicit request: RequestHeader): Future[CombinedAuthenticator] = cookieAuthenticatorService.create(loginInfo).map(CombinedAuthenticator(_)) - def createToken(loginInfo: LoginInfo): Future[CombinedAuthenticator] = { + private def createToken(loginInfo: LoginInfo): Future[CombinedAuthenticator] = { val tokenAuthenticator = tokenAuthenticatorService.create(loginInfo, TokenType.Authentication) - tokenAuthenticator.map(tokenAuthenticatorService.init(_, TokenType.Authentication)) + tokenAuthenticator.map(tokenAuthenticatorService.init(_, TokenType.Authentication, deleteOld = true)) tokenAuthenticator.map(CombinedAuthenticator(_)) } diff --git a/app/security/WebknossosBearerTokenAuthenticatorService.scala b/app/security/WebknossosBearerTokenAuthenticatorService.scala index 4ab34475ba..9b3b4958d6 100644 --- a/app/security/WebknossosBearerTokenAuthenticatorService.scala +++ b/app/security/WebknossosBearerTokenAuthenticatorService.scala @@ -54,7 +54,7 @@ class WebknossosBearerTokenAuthenticatorService(settings: BearerTokenAuthenticat } } - def init(authenticator: BearerTokenAuthenticator, tokenType: TokenType, deleteOld: Boolean = true): Future[String] = + def init(authenticator: BearerTokenAuthenticator, tokenType: TokenType, deleteOld: Boolean): Future[String] = repository .add(authenticator, tokenType, deleteOld) .map { a => @@ -64,10 +64,15 @@ class WebknossosBearerTokenAuthenticatorService(settings: BearerTokenAuthenticat case e => throw new AuthenticatorInitializationException(InitError.format(ID, authenticator), Some(e)) } - def createAndInitDataStoreTokenForUser(user: User): Fox[String] = - createAndInit(user.loginInfo, TokenType.DataStore, deleteOld = false) + def createAndInitDataStoreTokenForUser(user: User): Fox[String] = { + val before = Instant.now + for { + res <- createAndInit(user.loginInfo, TokenType.DataStore, deleteOld = false) + _ = Instant.logSince(before, "createAndInit") + } yield res + } - def createAndInit(loginInfo: LoginInfo, tokenType: TokenType, deleteOld: Boolean = true): Future[String] = + def createAndInit(loginInfo: LoginInfo, tokenType: TokenType, deleteOld: Boolean): Future[String] = for { tokenAuthenticator <- create(loginInfo, tokenType) tokenId <- init(tokenAuthenticator, tokenType, deleteOld)