app/AppComponents.scala (111 lines of code) (raw):

import aws.Clients import com.gu.googleauth.AuthAction import com.gu.play.secretrotation._ import com.gu.play.secretrotation.aws.parameterstore import com.typesafe.config.ConfigException import conf.Config import controllers._ import filters.HstsFilter import models._ import play.api.libs.ws.ahc.AhcWSComponents import play.api.mvc.{AnyContent, EssentialFilter} import play.api.routing.Router import play.api.{ApplicationLoader, BuiltInComponentsFromContext, Logging, Mode} import play.filters.HttpFiltersComponents import router.Routes import software.amazon.awssdk.regions.Region.EU_WEST_1 import software.amazon.awssdk.services.dynamodb.DynamoDbClient import java.time.Duration class AppComponents(context: ApplicationLoader.Context) extends BuiltInComponentsFromContext(context) with AhcWSComponents with AssetsComponents with HttpFiltersComponents with RotatingSecretComponents with Logging { override def httpFilters: Seq[EssentialFilter] = super.httpFilters :+ new HstsFilter // used by the template to detect development environment // in that situation, it'll load assets directly from npm vs production, where they'll come from the bundled files val mode: Mode = context.environment.mode // Janus has no Code stage private val stage = mode match { case Mode.Prod => "PROD" case _ => "DEV" } // Reads Play secret from SSM val secretStateSupplier: SnapshotProvider = new parameterstore.SecretSupplier( TransitionTiming( // When a new secret value is read it isn't used immediately, to keep all EC2 instances in sync. The new value is used after the usageDelay has passed. usageDelay = Duration.ofMinutes(3), // Old secret values are still respected for an overlapDuration. overlapDuration = Duration.ofHours(2) ), s"/$stage/security/janus/play.http.secret.key", parameterstore.AwsSdkV2(Clients.ssm) ) val host = Config.host(configuration) val googleAuthConfig = Config.googleSettings(configuration, secretStateSupplier) val googleGroupChecker = Config.googleGroupChecker(configuration) val requiredGoogleGroups = Set(Config.twoFAGroup(configuration)) val dynamodDB = if (context.environment.mode == play.api.Mode.Prod) DynamoDbClient.builder().region(EU_WEST_1).build() else Clients.localDb val janusData = Config.janusData(configuration) Config.validateAccountConfig(janusData, configuration) match { case FederationConfigError(causedBy) => throw new ConfigException.Missing("federation", causedBy) case ConfigWarn(accounts) => logger.warn( s"Account(s) present in config that are not present in the JanusData: ${accounts.mkString(", ")}" ) case ConfigError(accounts) => throw new RuntimeException( s"One or more accounts are missing from config: ${accounts.mkString(", ")}" ) case ConfigSuccess => } val authAction = new AuthAction[AnyContent]( googleAuthConfig, routes.AuthController.login, controllerComponents.parsers.default )(executionContext) override def router: Router = new Routes( httpErrorHandler, new Janus( janusData, controllerComponents, authAction, host, Clients.stsClient, configuration )(dynamodDB, mode, assetsFinder), new Audit(janusData, controllerComponents, authAction)( dynamodDB, mode, assetsFinder ), new RevokePermissions( janusData, controllerComponents, authAction, Clients.stsClient, configuration )(mode, assetsFinder), new AuthController( janusData, controllerComponents, googleAuthConfig, googleGroupChecker, requiredGoogleGroups )(wsClient, executionContext, mode, assetsFinder), new PasskeyController(controllerComponents, authAction, host, janusData)( dynamodDB, mode, assetsFinder, executionContext ), new Utility(janusData, controllerComponents, authAction, configuration)( mode, assetsFinder ), assets ) }