diff --git a/.envrc b/.envrc index e58403f185c..637107cd10e 100644 --- a/.envrc +++ b/.envrc @@ -152,8 +152,8 @@ require MOVE_MIL_DOD_TLS_CERT "See 'chamber read app-devlocal move_mil_dod_tls_c require MOVE_MIL_DOD_TLS_KEY "See 'chamber read app-devlocal move_mil_dod_tls_key'" export MOVE_MIL_DOD_CA_CERT -# Prevent user sessions from timing out -export NO_SESSION_TIMEOUT=true +# Set a long inactivity timeout for local sessions +export SESSION_IDLE_TIMEOUT_IN_MINUTES=30 # Use UTC timezone export TZ="UTC" diff --git a/.spelling b/.spelling index 75212cf213c..2c7895bbebe 100644 --- a/.spelling +++ b/.spelling @@ -599,5 +599,6 @@ prac-frontend VSCode PascalCased camelCased +redis-cli # Put all custom terms BEFORE this comment, lest 'pre-commit' and 'make spellcheck' yield different errors. - ./docs/backend/route-planner.md diff --git a/Makefile b/Makefile index 3a14ae211e7..8c541545ba8 100644 --- a/Makefile +++ b/Makefile @@ -8,6 +8,8 @@ DB_DOCKER_CONTAINER_TEST = milmove-db-test # as possible. # https://github.com/transcom/transcom-infrasec-com/blob/c32c45078f29ea6fd58b0c246f994dbea91be372/transcom-com-legacy/app-prod/main.tf#L62 DB_DOCKER_CONTAINER_IMAGE = postgres:12.2 +REDIS_DOCKER_CONTAINER_IMAGE = redis:5.0.6 +REDIS_DOCKER_CONTAINER = milmove-redis TASKS_DOCKER_CONTAINER = tasks export PGPASSWORD=mysecretpassword @@ -34,6 +36,8 @@ DB_PORT_DEV=5432 DB_PORT_TEST=5433 DB_PORT_DEPLOYED_MIGRATIONS=5434 DB_PORT_DOCKER=5432 +REDIS_PORT=6379 +REDIS_PORT_DOCKER=6379 ifdef CIRCLECI DB_PORT_DEV=5432 DB_PORT_TEST=5432 @@ -118,7 +122,7 @@ check_docker_size: ## Check the amount of disk space used by docker scripts/check-docker-size .PHONY: deps -deps: prereqs ensure_pre_commit client_deps bin/rds-ca-2019-root.pem ## Run all checks and install all depdendencies +deps: prereqs ensure_pre_commit client_deps redis_pull bin/rds-ca-2019-root.pem ## Run all checks and install all depdendencies .PHONY: test test: client_test server_test e2e_test ## Run all tests @@ -302,7 +306,7 @@ server_build: bin/milmove ## Build the server # This command is for running the server by itself, it will serve the compiled frontend on its own # Note: Don't double wrap with aws-vault because the pkg/cli/vault.go will handle it -server_run_standalone: check_log_dir server_build client_build db_dev_run +server_run_standalone: check_log_dir server_build client_build db_dev_run redis_run DEBUG_LOGGING=true ./bin/milmove serve 2>&1 | tee -a log/dev.log # This command will rebuild the swagger go code and rerun server on any changes @@ -311,7 +315,7 @@ server_run: # This command runs the server behind gin, a hot-reload server # Note: Gin is not being used as a proxy so assigning odd port and laddr to keep in IPv4 space. # Note: The INTERFACE envar is set to configure the gin build, milmove_gin, local IP4 space with default port GIN_PORT. -server_run_default: .check_hosts.stamp .check_go_version.stamp .check_gopath.stamp .check_node_version.stamp check_log_dir bin/gin build/index.html server_generate db_dev_run +server_run_default: .check_hosts.stamp .check_go_version.stamp .check_gopath.stamp .check_node_version.stamp check_log_dir bin/gin build/index.html server_generate db_dev_run redis_run INTERFACE=localhost DEBUG_LOGGING=true \ $(AWS_VAULT) ./bin/gin \ --build ./cmd/milmove \ @@ -324,7 +328,7 @@ server_run_default: .check_hosts.stamp .check_go_version.stamp .check_gopath.sta 2>&1 | tee -a log/dev.log .PHONY: server_run_debug -server_run_debug: .check_hosts.stamp .check_go_version.stamp .check_gopath.stamp .check_node_version.stamp check_log_dir build/index.html server_generate db_dev_run ## Debug the server +server_run_debug: .check_hosts.stamp .check_go_version.stamp .check_gopath.stamp .check_node_version.stamp check_log_dir build/index.html server_generate db_dev_run redis_run ## Debug the server scripts/kill-process-on-port 8080 scripts/kill-process-on-port 9443 $(AWS_VAULT) dlv debug cmd/milmove/*.go -- serve 2>&1 | tee -a log/dev.log @@ -396,7 +400,7 @@ mocks_generate: bin/mockery ## Generate mockery mocks for tests go generate $$(go list ./... | grep -v \\/pkg\\/gen\\/ | grep -v \\/cmd\\/) .PHONY: server_test -server_test: db_test_reset db_test_migrate server_test_standalone ## Run server unit tests +server_test: db_test_reset db_test_migrate redis_reset server_test_standalone ## Run server unit tests .PHONY: server_test_standalone server_test_standalone: ## Run server unit tests with no deps @@ -407,12 +411,12 @@ server_test_build: NO_DB=1 DRY_RUN=1 scripts/run-server-test .PHONY: server_test_all -server_test_all: db_dev_reset db_dev_migrate ## Run all server unit tests +server_test_all: db_dev_reset db_dev_migrate redis_reset ## Run all server unit tests # Like server_test but runs extended tests that may hit external services. LONG_TEST=1 scripts/run-server-test .PHONY: server_test_coverage_generate -server_test_coverage_generate: db_test_reset db_test_migrate server_test_coverage_generate_standalone ## Run server unit test coverage +server_test_coverage_generate: db_test_reset db_test_migrate redis_reset server_test_coverage_generate_standalone ## Run server unit test coverage .PHONY: server_test_coverage_generate_standalone server_test_coverage_generate_standalone: ## Run server unit tests with coverage and no deps @@ -420,7 +424,7 @@ server_test_coverage_generate_standalone: ## Run server unit tests with coverage NO_DB=1 COVERAGE=1 scripts/run-server-test .PHONY: server_test_coverage -server_test_coverage: db_test_reset db_test_migrate server_test_coverage_generate ## Run server unit test coverage with html output +server_test_coverage: db_test_reset db_test_migrate redis_reset server_test_coverage_generate ## Run server unit test coverage with html output DB_PORT=$(DB_PORT_TEST) go tool cover -html=coverage.out .PHONY: server_test_docker @@ -435,6 +439,38 @@ server_test_docker_down: # ----- END SERVER TARGETS ----- # +# +# ----- START REDIS TARGETS ----- +# + +.PHONY: redis_pull +redis_pull: ## Pull redis image + docker pull $(REDIS_DOCKER_CONTAINER_IMAGE) + +.PHONY: redis_destroy +redis_destroy: ## Destroy Redis + @echo "Destroying the ${REDIS_DOCKER_CONTAINER} docker redis container..." + docker rm -f $(REDIS_DOCKER_CONTAINER) || echo "No Redis container" + +.PHONY: redis_run +redis_run: ## Run Redis +ifndef CIRCLECI + @echo "Stopping the Redis brew service in case it's running..." + brew services stop redis 2> /dev/null || true +endif + @echo "Starting the ${REDIS_DOCKER_CONTAINER} docker redis container..." + docker start $(REDIS_DOCKER_CONTAINER) || \ + docker run -d --name $(REDIS_DOCKER_CONTAINER) \ + -p $(REDIS_PORT):$(REDIS_PORT_DOCKER) \ + $(REDIS_DOCKER_CONTAINER_IMAGE) + +.PHONY: redis_reset +redis_reset: redis_destroy redis_run ## Reset Redis + +# +# ----- END REDIS TARGETS ----- +# + # # ----- START DB_DEV TARGETS ----- # @@ -667,7 +703,7 @@ db_e2e_up: bin/generate-test-data ## Truncate Test DB and Generate e2e (end-to-e DB_PORT=$(DB_PORT_TEST) bin/generate-test-data --named-scenario="e2e_basic" --db-env="test" .PHONY: db_e2e_init -db_e2e_init: db_test_reset db_test_migrate db_e2e_up ## Initialize e2e (end-to-end) DB (reset, migrate, up) +db_e2e_init: db_test_reset db_test_migrate redis_reset db_e2e_up ## Initialize e2e (end-to-end) DB (reset, migrate, up) .PHONY: db_dev_e2e_populate db_dev_e2e_populate: db_dev_reset db_dev_migrate ## Populate Dev DB with generated e2e (end-to-end) data @@ -677,7 +713,7 @@ db_dev_e2e_populate: db_dev_reset db_dev_migrate ## Populate Dev DB with generat go run github.com/transcom/mymove/cmd/generate-test-data --named-scenario="e2e_basic" --db-env="development" .PHONY: db_test_e2e_populate -db_test_e2e_populate: db_test_reset db_test_migrate build_tools db_e2e_up ## Populate Test DB with generated e2e (end-to-end) data +db_test_e2e_populate: db_test_reset db_test_migrate redis_reset build_tools db_e2e_up ## Populate Test DB with generated e2e (end-to-end) data .PHONY: db_test_e2e_backup db_test_e2e_backup: ## Backup Test DB as 'e2e_test' diff --git a/cmd/milmove/serve.go b/cmd/milmove/serve.go index ccc71141458..4752145fa76 100644 --- a/cmd/milmove/serve.go +++ b/cmd/milmove/serve.go @@ -18,15 +18,17 @@ import ( "strings" "sync" "syscall" + "time" - "github.com/transcom/mymove/pkg/handlers/supportapi" - + "github.com/alexedwards/scs/redisstore" "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/credentials/stscreds" awssession "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/sts" "github.com/dgrijalva/jwt-go" "github.com/gobuffalo/pop" + + "github.com/gomodule/redigo/redis" "github.com/gorilla/csrf" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -51,6 +53,7 @@ import ( "github.com/transcom/mymove/pkg/handlers/internalapi" "github.com/transcom/mymove/pkg/handlers/ordersapi" "github.com/transcom/mymove/pkg/handlers/primeapi" + "github.com/transcom/mymove/pkg/handlers/supportapi" "github.com/transcom/mymove/pkg/iws" "github.com/transcom/mymove/pkg/logging" "github.com/transcom/mymove/pkg/middleware" @@ -140,6 +143,12 @@ func initServeFlags(flag *pflag.FlagSet) { // Service Flags cli.InitServiceFlags(flag) + // Redis Flags + cli.InitRedisFlags(flag) + + // SessionFlags + cli.InitSessionFlags(flag) + // Sort command line flags flag.SortFlags = true } @@ -248,6 +257,14 @@ func checkServeConfig(v *viper.Viper, logger logger) error { return err } + if err := cli.CheckRedis(v); err != nil { + return err + } + + if err := cli.CheckSession(v); err != nil { + return err + } + return nil } @@ -298,11 +315,28 @@ func indexHandler(buildDir string, logger logger) http.HandlerFunc { } } +func redisHealthCheck(pool *redis.Pool, logger *zap.Logger, data map[string]interface{}) map[string]interface{} { + conn := pool.Get() + defer conn.Close() + + pong, err := redis.String(conn.Do("PING")) + if err != nil { + logger.Error("Failed to ping Redis during health check", zap.Error(err)) + } + logger.Info("Health check Redis ping", zap.String("ping_response", pong)) + + data["redis"] = err == nil + + return data +} + func serveFunction(cmd *cobra.Command, args []string) error { var logger *zap.Logger var dbConnection *pop.Connection dbClose := &sync.Once{} + var redisPool *redis.Pool + redisClose := &sync.Once{} defer func() { if logger != nil { @@ -317,6 +351,14 @@ func serveFunction(cmd *cobra.Command, args []string) error { } }) } + if redisPool != nil { + redisClose.Do(func() { + logger.Info("closing redis connections") + if err := redisPool.Close(); err != nil { + logger.Error("error closing redis connections", zap.Error(err)) + } + }) + } logger.Sync() } }() @@ -462,6 +504,12 @@ func serveFunction(cmd *cobra.Command, args []string) error { } } + // Create a connection to Redis + redisPool, errRedisConnection := cli.InitRedis(v, logger) + if errRedisConnection != nil { + logger.Fatal("Invalid Redis Configuration", zap.Error(errRedisConnection)) + } + // Collect the servernames into a handy struct appnames := auth.ApplicationServername{ MilServername: v.GetString(cli.HTTPMyServerNameFlag), @@ -480,9 +528,17 @@ func serveFunction(cmd *cobra.Command, args []string) error { } useSecureCookie := !isDevOrTest + redisEnabled := v.GetBool(cli.RedisEnabledFlag) + sessionStore := redisstore.New(redisPool) + idleTimeout := v.GetDuration(cli.SessionIdleTimeoutInMinutesFlag) * time.Minute + lifetime := v.GetDuration(cli.SessionLifetimeInHoursFlag) * time.Hour + sessionManagers := auth.SetupSessionManagers(redisEnabled, sessionStore, useSecureCookie, idleTimeout, lifetime) + milSession := sessionManagers[0] + adminSession := sessionManagers[1] + officeSession := sessionManagers[2] + // Session management and authentication middleware - noSessionTimeout := v.GetBool(cli.NoSessionTimeoutFlag) - sessionCookieMiddleware := auth.SessionCookieMiddleware(logger, clientAuthSecretKey, noSessionTimeout, appnames, useSecureCookie) + sessionCookieMiddleware := auth.SessionCookieMiddleware(logger, appnames, sessionManagers) maskedCSRFMiddleware := auth.MaskedCSRFMiddleware(logger, useSecureCookie) userAuthMiddleware := authentication.UserAuthMiddleware(logger) if v.GetBool(cli.FeatureFlagRoleBasedAuth) { @@ -493,12 +549,9 @@ func serveFunction(cmd *cobra.Command, args []string) error { roleAuthMiddleware := authentication.RoleAuthMiddleware(logger) handlerContext := handlers.NewHandlerContext(dbConnection, logger) + handlerContext.SetSessionManagers(sessionManagers) handlerContext.SetCookieSecret(clientAuthSecretKey) handlerContext.SetUseSecureCookie(useSecureCookie) - if noSessionTimeout { - handlerContext.SetNoSessionTimeout() - } - handlerContext.SetAppNames(appnames) // Email @@ -628,12 +681,10 @@ func serveFunction(cmd *cobra.Command, args []string) error { // Stub health check site.HandleFunc(pat.Get("/health"), func(w http.ResponseWriter, r *http.Request) { - data := map[string]interface{}{ "gitBranch": gitBranch, "gitCommit": gitCommit, } - // Check and see if we should disable DB query with '?database=false' // Disabling the DB is useful for Route53 health checks which require the TLS // handshake be less than 4 seconds and the status code return in less than @@ -642,13 +693,16 @@ func serveFunction(cmd *cobra.Command, args []string) error { // Always show DB unless key set to "false" if !ok || (ok && showDB[0] != "false") { + logger.Info("connecting to the DB for health check") dbErr := dbConnection.RawQuery("SELECT 1;").Exec() if dbErr != nil { logger.Error("Failed database health check", zap.Error(dbErr)) } data["database"] = dbErr == nil + if redisEnabled { + data = redisHealthCheck(redisPool, logger, data) + } } - newEncoderErr := json.NewEncoder(w).Encode(data) if newEncoderErr != nil { logger.Error("Failed encoding health check response", zap.Error(newEncoderErr)) @@ -834,7 +888,9 @@ func serveFunction(cmd *cobra.Command, args []string) error { root.Use(csrf.Protect(csrfAuthKey, csrf.Secure(!isDevOrTest), csrf.Path("/"), csrf.CookieName(auth.GorillaCSRFToken))) root.Use(maskedCSRFMiddleware) - site.Handle(pat.New("/*"), root) + site.Handle( + pat.New("/*"), + milSession.LoadAndSave(adminSession.LoadAndSave(officeSession.LoadAndSave(root)))) if v.GetBool(cli.ServeAPIInternalFlag) { internalMux := goji.SubMux() @@ -902,7 +958,7 @@ func serveFunction(cmd *cobra.Command, args []string) error { } } - authContext := authentication.NewAuthContext(logger, loginGovProvider, loginGovCallbackProtocol, loginGovCallbackPort) + authContext := authentication.NewAuthContext(logger, loginGovProvider, loginGovCallbackProtocol, loginGovCallbackPort, sessionManagers) authContext.SetFeatureFlag( authentication.FeatureFlag{ Name: cli.FeatureFlagRoleBasedAuth, @@ -911,18 +967,18 @@ func serveFunction(cmd *cobra.Command, args []string) error { ) authMux := goji.SubMux() root.Handle(pat.New("/auth/*"), authMux) - authMux.Handle(pat.Get("/login-gov"), authentication.RedirectHandler{Context: authContext, UseSecureCookie: useSecureCookie}) - authMux.Handle(pat.Get("/login-gov/callback"), authentication.NewCallbackHandler(authContext, dbConnection, clientAuthSecretKey, noSessionTimeout, useSecureCookie)) - authMux.Handle(pat.Post("/logout"), authentication.NewLogoutHandler(authContext, clientAuthSecretKey, noSessionTimeout, useSecureCookie)) + authMux.Handle(pat.Get("/login-gov"), authentication.RedirectHandler{Context: authContext}) + authMux.Handle(pat.Get("/login-gov/callback"), authentication.NewCallbackHandler(authContext, dbConnection)) + authMux.Handle(pat.Post("/logout"), authentication.NewLogoutHandler(authContext)) if v.GetBool(cli.DevlocalAuthFlag) { logger.Info("Enabling devlocal auth") localAuthMux := goji.SubMux() root.Handle(pat.New("/devlocal-auth/*"), localAuthMux) - localAuthMux.Handle(pat.Get("/login"), authentication.NewUserListHandler(authContext, dbConnection, clientAuthSecretKey, noSessionTimeout, useSecureCookie)) - localAuthMux.Handle(pat.Post("/login"), authentication.NewAssignUserHandler(authContext, dbConnection, appnames, clientAuthSecretKey, noSessionTimeout, useSecureCookie)) - localAuthMux.Handle(pat.Post("/new"), authentication.NewCreateAndLoginUserHandler(authContext, dbConnection, appnames, clientAuthSecretKey, noSessionTimeout, useSecureCookie)) - localAuthMux.Handle(pat.Post("/create"), authentication.NewCreateUserHandler(authContext, dbConnection, appnames, clientAuthSecretKey, noSessionTimeout, useSecureCookie)) + localAuthMux.Handle(pat.Get("/login"), authentication.NewUserListHandler(authContext, dbConnection)) + localAuthMux.Handle(pat.Post("/login"), authentication.NewAssignUserHandler(authContext, dbConnection, appnames)) + localAuthMux.Handle(pat.Post("/new"), authentication.NewCreateAndLoginUserHandler(authContext, dbConnection, appnames)) + localAuthMux.Handle(pat.Post("/create"), authentication.NewCreateUserHandler(authContext, dbConnection, appnames)) if stringSliceContains([]string{cli.EnvironmentTest, cli.EnvironmentDevelopment}, v.GetString(cli.EnvironmentFlag)) { logger.Info("Adding devlocal CA to root CAs") @@ -1058,6 +1114,12 @@ func serveFunction(cmd *cobra.Command, args []string) error { dbCloseErr = dbConnection.Close() }) + var redisCloseErr error + redisClose.Do(func() { + logger.Info("closing redis connections") + redisCloseErr = redisPool.Close() + }) + shutdownError := false shutdownErrors.Range(func(key, value interface{}) bool { if srv, ok := key.(*server.NamedServer); ok { @@ -1075,6 +1137,10 @@ func serveFunction(cmd *cobra.Command, args []string) error { logger.Error("error closing database connections", zap.Error(dbCloseErr)) } + if redisCloseErr != nil { + logger.Error("error closing redis connections", zap.Error(redisCloseErr)) + } + logger.Sync() if shutdownError { diff --git a/config/env/experimental.app-client-tls.env b/config/env/experimental.app-client-tls.env index 934840103ad..ccf9abf2ef0 100644 --- a/config/env/experimental.app-client-tls.env +++ b/config/env/experimental.app-client-tls.env @@ -26,6 +26,7 @@ IWS_RBS_HOST=pkict.dmdc.osd.mil LOGIN_GOV_HOSTNAME=idp.int.identitysandbox.gov LOG_TASK_METADATA=true MUTUAL_TLS_ENABLED=true +REDIS_ENABLED=false SERVE_DPS=true SERVE_ORDERS=true SERVE_API_PRIME=true diff --git a/config/env/experimental.app.env b/config/env/experimental.app.env index 7f4fdb6550b..5e8f38814f3 100644 --- a/config/env/experimental.app.env +++ b/config/env/experimental.app.env @@ -31,6 +31,9 @@ IWS_RBS_ENABLED=1 IWS_RBS_HOST=pkict.dmdc.osd.mil LOGIN_GOV_HOSTNAME=idp.int.identitysandbox.gov LOG_TASK_METADATA=true +REDIS_ENABLED=true +REDIS_SSL_ENABLED=true +REDIS_PORT=6379 SERVE_ADMIN=true SERVE_API_GHC=true SERVE_API_INTERNAL=true diff --git a/config/env/prod.app-client-tls.env b/config/env/prod.app-client-tls.env index d4a3447dde7..bb9887be04a 100644 --- a/config/env/prod.app-client-tls.env +++ b/config/env/prod.app-client-tls.env @@ -23,6 +23,7 @@ IWS_RBS_HOST=sadr.dmdc.osd.mil LOGIN_GOV_HOSTNAME=secure.login.gov LOG_TASK_METADATA=true MUTUAL_TLS_ENABLED=true +REDIS_ENABLED=false SERVE_DPS=true SERVE_ORDERS=true SERVE_SWAGGER_UI=false diff --git a/config/env/prod.app.env b/config/env/prod.app.env index bb1247015e4..68df93cf2cc 100644 --- a/config/env/prod.app.env +++ b/config/env/prod.app.env @@ -27,6 +27,9 @@ IWS_RBS_ENABLED=1 IWS_RBS_HOST=sadr.dmdc.osd.mil LOGIN_GOV_HOSTNAME=secure.login.gov LOG_TASK_METADATA=true +REDIS_ENABLED=true +REDIS_SSL_ENABLED=true +REDIS_PORT=6379 SERVE_ADMIN=true SERVE_API_GHC=false SERVE_API_INTERNAL=true diff --git a/config/env/staging.app-client-tls.env b/config/env/staging.app-client-tls.env index a4178910d7a..a69a1b4fcc3 100644 --- a/config/env/staging.app-client-tls.env +++ b/config/env/staging.app-client-tls.env @@ -25,6 +25,7 @@ IWS_RBS_HOST=pkict.dmdc.osd.mil LOGIN_GOV_HOSTNAME=idp.int.identitysandbox.gov LOG_TASK_METADATA=true MUTUAL_TLS_ENABLED=true +REDIS_ENABLED=false SERVE_DPS=true SERVE_ORDERS=true SERVE_API_PRIME=true diff --git a/config/env/staging.app.env b/config/env/staging.app.env index 5f7cc7bcb65..4946fddbe60 100644 --- a/config/env/staging.app.env +++ b/config/env/staging.app.env @@ -30,6 +30,9 @@ IWS_RBS_ENABLED=1 IWS_RBS_HOST=pkict.dmdc.osd.mil LOGIN_GOV_HOSTNAME=idp.int.identitysandbox.gov LOG_TASK_METADATA=true +REDIS_ENABLED=true +REDIS_SSL_ENABLED=true +REDIS_PORT=6379 SERVE_ADMIN=true SERVE_API_GHC=true SERVE_API_INTERNAL=true diff --git a/docker-compose.circle.yml b/docker-compose.circle.yml index 282c5a73623..2339ae4032e 100644 --- a/docker-compose.circle.yml +++ b/docker-compose.circle.yml @@ -14,9 +14,13 @@ services: - POSTGRES_PASSWORD=mysecretpassword - POSTGRES_DB=test_db + redis: + image: redis:5.0.6 + server_test: depends_on: - database + - redis image: milmove/circleci-docker:milmove-app-1e5a1038cb264651fbd844b82489401a476e9f7e deploy: resources: @@ -76,6 +80,7 @@ services: - NO_TLS_ENABLED=1 - NO_TLS_PORT=4000 - PGPASSWORD=mysecretpassword + - REDIS_HOST=redis - SERVE_ADMIN=true - SERVE_API_GHC=false - SERVE_API_INTERNAL=true diff --git a/docker-compose.local.yml b/docker-compose.local.yml index f717f47f952..adbb439b40d 100644 --- a/docker-compose.local.yml +++ b/docker-compose.local.yml @@ -11,6 +11,9 @@ services: - POSTGRES_PASSWORD=mysecretpassword - POSTGRES_DB=dev_db + redis: + image: redis:5.0.6 + milmove_migrate: depends_on: - database @@ -40,6 +43,7 @@ services: depends_on: - database - milmove_migrate + - redis build: context: . dockerfile: Dockerfile.local @@ -97,6 +101,7 @@ services: - NO_TLS_ENABLED=1 - NO_TLS_PORT=4000 - PGPASSWORD=mysecretpassword + - REDIS_HOST=redis - SERVE_ADMIN=true - SERVE_API_GHC=false - SERVE_API_INTERNAL=true diff --git a/docker-compose.mtls.yml b/docker-compose.mtls.yml index 4832272598b..0b859b4bae1 100644 --- a/docker-compose.mtls.yml +++ b/docker-compose.mtls.yml @@ -82,6 +82,7 @@ services: - MOVE_MIL_DOD_TLS_KEY - MUTUAL_TLS_ENABLED=1 - PGPASSWORD=mysecretpassword + - REDIS_ENABLED=false - SERVE_API_PRIME=true - STORAGE_BACKEND=local - TZ=UTC diff --git a/docker-compose.mtls_local.yml b/docker-compose.mtls_local.yml index fbafaacdc50..3282f17e749 100644 --- a/docker-compose.mtls_local.yml +++ b/docker-compose.mtls_local.yml @@ -86,6 +86,7 @@ services: - MOVE_MIL_DOD_TLS_KEY - MUTUAL_TLS_ENABLED=1 - PGPASSWORD=mysecretpassword + - REDIS_ENABLED=false - SERVE_API_PRIME=true - STORAGE_BACKEND=local - TZ=UTC diff --git a/docker-compose.yml b/docker-compose.yml index 609406e2f62..584f2088b66 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,6 +11,9 @@ services: - POSTGRES_PASSWORD=mysecretpassword - POSTGRES_DB=dev_db + redis: + image: redis:5.0.6 + milmove_migrate: depends_on: - database @@ -38,6 +41,7 @@ services: depends_on: - database - milmove_migrate + - redis image: 923914045601.dkr.ecr.us-west-2.amazonaws.com/app:git-branch-placeholder_branch_name links: - database @@ -91,6 +95,7 @@ services: - NO_TLS_ENABLED=1 - NO_TLS_PORT=4000 - PGPASSWORD=mysecretpassword + - REDIS_HOST=redis - SERVE_ADMIN=true - SERVE_API_GHC=false - SERVE_API_INTERNAL=true diff --git a/go.mod b/go.mod index ef0b7e50059..54b8392cebd 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,8 @@ require ( github.com/0xAX/notificator v0.0.0-20191016112426-3962a5ea8da1 // indirect github.com/99designs/aws-vault v4.5.1+incompatible github.com/99designs/keyring v1.1.5 + github.com/alexedwards/scs/redisstore v0.0.0-20200225172727-3308e1066830 + github.com/alexedwards/scs/v2 v2.3.0 github.com/aws/aws-sdk-go v1.32.2 github.com/cockroachdb/cockroach-go v0.0.0-20200411195601-6f5842749cfc // indirect github.com/codegangsta/envy v0.0.0-20141216192214-4b78388c8ce4 // indirect @@ -38,6 +40,7 @@ require ( github.com/gocarina/gocsv v0.0.0-20190927101021-3ecffd272576 github.com/gofrs/flock v0.7.1 github.com/gofrs/uuid v3.3.0+incompatible + github.com/gomodule/redigo v2.0.0+incompatible github.com/google/go-github/v31 v31.0.0 github.com/gorilla/csrf v1.7.0 github.com/imdario/mergo v0.3.9 @@ -67,7 +70,7 @@ require ( go.mozilla.org/pkcs7 v0.0.0-20181213175627-3cffc6fbfe83 go.uber.org/zap v1.15.0 goji.io v2.0.2+incompatible - golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4 + golang.org/x/crypto v0.0.0-20200317142112-1b76d66859c6 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 golang.org/x/text v0.3.2 diff --git a/go.sum b/go.sum index 169f14e2edf..9e616404c88 100644 --- a/go.sum +++ b/go.sum @@ -34,13 +34,16 @@ github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdko github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alexedwards/scs/redisstore v0.0.0-20200225172727-3308e1066830 h1:84mrg6CV1OZ8RnRZ6zkJ4bvjg/6CHXPPVDKQKecVb+0= +github.com/alexedwards/scs/redisstore v0.0.0-20200225172727-3308e1066830/go.mod h1:u2uSOc9yz8e3S+beMudSPwYL36kcbBChOLBJDAQNy38= +github.com/alexedwards/scs/v2 v2.3.0 h1:V8rtn2P5QGh8C9S7T/ikBo/AdA27vDoQJPbiAaOCmFg= +github.com/alexedwards/scs/v2 v2.3.0/go.mod h1:ToaROZxyKukJKT/xLcVQAChi5k6+Pn1Gvmdl7h3RRj8= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 h1:zV3ejI06GQ59hwDQAvmK1qxOQGB3WuVTRoY0okPTAv0= github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= @@ -113,15 +116,12 @@ github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpR github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= github.com/go-openapi/analysis v0.19.4/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= -github.com/go-openapi/analysis v0.19.5 h1:8b2ZgKfKIUTVQpTb77MoRDIMEIwvDVw40o3aOXdfYzI= github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU= github.com/go-openapi/analysis v0.19.10 h1:5BHISBAXOc/aJK25irLZnx2D3s6WyYaY9D4gmuz9fdE= github.com/go-openapi/analysis v0.19.10/go.mod h1:qmhS3VNFxBlquFJ0RGoDtylO9y4pgTAUNE9AEEMdlJQ= github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= -github.com/go-openapi/errors v0.19.2 h1:a2kIyV3w+OS3S97zxUndRVD46+FhGOUBDFY7nmu4CsY= github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= -github.com/go-openapi/errors v0.19.3 h1:7MGZI1ibQDLasvAz8HuhvYk9eNJbJkCOXWsSjjMS+Zc= github.com/go-openapi/errors v0.19.3/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= github.com/go-openapi/errors v0.19.4 h1:fSGwO1tSYHFu70NKaWJt5Qh0qoBRtCm/mXS1yhf+0W0= github.com/go-openapi/errors v0.19.4/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= @@ -132,7 +132,6 @@ github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm7232 github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= -github.com/go-openapi/jsonreference v0.19.2 h1:o20suLFB4Ri0tuzpWtyHlh7E7HnkqTNLq6aR6WVNS1w= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= github.com/go-openapi/jsonreference v0.19.3 h1:5cxNfTy0UVC3X8JL5ymxzyoUZmo8iZb+jeTWn7tUa8o= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= @@ -141,7 +140,6 @@ github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs= github.com/go-openapi/loads v0.19.3/go.mod h1:YVfqhUCdahYwR3f3iiwQLhicVRvLlU/WO5WPaZvcvSI= -github.com/go-openapi/loads v0.19.4 h1:5I4CCSqoWzT+82bBkNIvmLc0UOsoKKQ4Fz+3VxOB7SY= github.com/go-openapi/loads v0.19.4/go.mod h1:zZVHonKd8DXyxyw4yfnVjPzBjIQcLt0CCsn0N0ZrQsk= github.com/go-openapi/loads v0.19.5 h1:jZVYWawIQiA1NBnHla28ktg6hrcfTHsCE+3QLVRBIls= github.com/go-openapi/loads v0.19.5/go.mod h1:dswLCAdonkRufe/gSUC3gN8nTSaB9uaS2es0x5/IbjY= @@ -154,7 +152,6 @@ github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsd github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= -github.com/go-openapi/spec v0.19.6 h1:rMMMj8cV38KVXK7SFc+I2MWClbEfbK705+j+dyqun5g= github.com/go-openapi/spec v0.19.6/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= github.com/go-openapi/spec v0.19.8 h1:qAdZLh1r6QF/hI/gTq+TJTvsQUodZsM7KLqkAJdiJNg= github.com/go-openapi/spec v0.19.8/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= @@ -162,18 +159,14 @@ github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pL github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY= github.com/go-openapi/strfmt v0.19.2/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= -github.com/go-openapi/strfmt v0.19.3 h1:eRfyY5SkaNJCAwmmMcADjY31ow9+N7MCLW7oRkbsINA= github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= -github.com/go-openapi/strfmt v0.19.4 h1:eRvaqAhpL0IL6Trh5fDsGnGhiXndzHFuA05w6sXH6/g= github.com/go-openapi/strfmt v0.19.4/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk= github.com/go-openapi/strfmt v0.19.5 h1:0utjKrw+BAh8s57XE9Xz8DUBsVvPmRUB6styvl9wWIM= github.com/go-openapi/strfmt v0.19.5/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk= github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.7 h1:VRuXN2EnMSsZdauzdss6JBC29YotDqG59BZ+tdlIL1s= github.com/go-openapi/swag v0.19.7/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= github.com/go-openapi/swag v0.19.9 h1:1IxuqvBUU3S2Bi4YC7tlP9SJF1gVpCvqN0T2Qof4azE= github.com/go-openapi/swag v0.19.9/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= @@ -212,7 +205,6 @@ github.com/gobuffalo/flect v0.2.1/go.mod h1:vmkQwuZYhN5Pc4ljYQZzP+1sq+NEkK+lh20j github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28= github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo= -github.com/gobuffalo/genny v0.1.1 h1:iQ0D6SpNXIxu52WESsD+KoQ7af2e3nCfnSBoSF/hKe0= github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk= github.com/gobuffalo/genny v0.6.0 h1:d7c6d66ZrTHHty01hDX1/TcTWvAJQxRZl885KWX5kHY= github.com/gobuffalo/genny v0.6.0/go.mod h1:Vigx9VDiNscYpa/LwrURqGXLSIbzTfapt9+K6gF1kTA= @@ -228,13 +220,11 @@ github.com/gobuffalo/helpers v0.6.0/go.mod h1:pncVrer7x/KRvnL5aJABLAuT/RhKRR9klL github.com/gobuffalo/here v0.6.0 h1:hYrd0a6gDmWxBM4TnrGw8mQg24iSVoIkHEk7FodQcBI= github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM= github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= -github.com/gobuffalo/logger v1.0.0 h1:xw9Ko9EcC5iAFprrjJ6oZco9UpzS5MQ4jAwghsLHdy4= github.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs= github.com/gobuffalo/logger v1.0.1/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs= github.com/gobuffalo/logger v1.0.3 h1:YaXOTHNPCvkqqA7w05A4v0k2tCdpr+sgFlgINbQ6gqc= github.com/gobuffalo/logger v1.0.3/go.mod h1:SoeejUwldiS7ZsyCBphOGURmWdwUFXs0J7TCjEhjKxM= github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= -github.com/gobuffalo/mapi v1.0.2 h1:fq9WcL1BYrm36SzK6+aAnZ8hcp+SrmnDyAxhNx8dvJk= github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= github.com/gobuffalo/nulls v0.4.0 h1:xi+JHGWIetYqLmS520dSWc8Ifj1P0aNXKTVDMVsPXmw= github.com/gobuffalo/nulls v0.4.0/go.mod h1:2KmsoLnMrxpwPLN5LmBbm6tmttHSIZr/v/OdGsATM3M= @@ -246,7 +236,6 @@ github.com/gobuffalo/packr v1.30.1 h1:hu1fuVR3fXEZR7rXNW3h8rqSML8EVAf6KNm0NKO/wK github.com/gobuffalo/packr v1.30.1/go.mod h1:ljMyFO2EcrnzsHsN99cvbq055Y9OhRrIaviy289eRuk= github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= -github.com/gobuffalo/packr/v2 v2.5.1 h1:TFOeY2VoGamPjQLiNDT3mn//ytzk236VMO2j7iHxJR4= github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWSVlXRN9X1Iw= github.com/gobuffalo/packr/v2 v2.7.1 h1:n3CIW5T17T8v4GGK5sWXLVWJhCz7b5aNLSxW6gYim4o= github.com/gobuffalo/packr/v2 v2.7.1/go.mod h1:qYEvAazPaVxy7Y7KR0W8qYEE+RymX74kETFqjFoFlOc= @@ -261,9 +250,7 @@ github.com/gobuffalo/tags v2.1.7+incompatible h1:GUxxh34f9SI4U0Pj3ZqvopO9SlzuqSf github.com/gobuffalo/tags v2.1.7+incompatible/go.mod h1:9XmhOkyaB7UzvuY4UoZO4s67q8/xRMVJEaakauVQYeY= github.com/gobuffalo/tags/v3 v3.0.2 h1:gxE6c6fA5radwQeg59aPIeYgCG8YA8AZd3Oh6fh5UXA= github.com/gobuffalo/tags/v3 v3.0.2/go.mod h1:ZQeN6TCTiwAFnS0dNcbDtSgZDwNKSpqajvVtt6mlYpA= -github.com/gobuffalo/uuid v2.0.5+incompatible h1:c5uWRuEnYggYCrT9AJm0U2v1QTG7OVDAvxhj8tIV5Gc= github.com/gobuffalo/uuid v2.0.5+incompatible/go.mod h1:ErhIzkRhm0FtRuiE/PeORqcw4cVi1RtSpnwYrxuvkfE= -github.com/gobuffalo/validate v2.0.3+incompatible h1:6f4JCEz11Zi6iIlexMv7Jz10RBPvgI795AOaubtCwTE= github.com/gobuffalo/validate v2.0.3+incompatible/go.mod h1:N+EtDe0J8252BgfzQUChBgfd6L93m9weay53EWFVsMM= github.com/gobuffalo/validate v2.0.4+incompatible h1:ZTxozrIw8qQ5nfhShmc4izjYPTsPhfdXTdhXOd5OS9o= github.com/gobuffalo/validate v2.0.4+incompatible/go.mod h1:N+EtDe0J8252BgfzQUChBgfd6L93m9weay53EWFVsMM= @@ -275,7 +262,6 @@ github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+ github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/gofrs/flock v0.7.1 h1:DP+LD/t0njgoPBvT5MJLeliUIVQR03hiKR6vezdwHlc= github.com/gofrs/flock v0.7.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= -github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v3.3.0+incompatible h1:8K4tyRfvU1CYPgJsveYFQMhpFd/wXNM7iK6rR7UHz84= github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= @@ -287,11 +273,12 @@ github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0= +github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -378,7 +365,6 @@ github.com/jung-kurt/gofpdf v1.16.2 h1:jgbatWHfRlPYiK85qgevsZTHviWXKwB1TTiKdz5Pt github.com/jung-kurt/gofpdf v1.16.2/go.mod h1:1hl7y57EsiPAkLbOwzpzqgx1A30nQCk/YmFV8S2vmK0= github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= -github.com/karrick/godirwalk v1.10.12 h1:BqUm+LuJcXjGv1d2mj3gBiQyrQ57a0rYoAmhvJQ7RDU= github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= @@ -390,7 +376,6 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -410,18 +395,14 @@ github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czP github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= github.com/mailru/easyjson v0.7.1 h1:mdxE1MF9o53iCb2Ghj1VfWvh7ZOwHpnVG/xwXrV90U8= github.com/mailru/easyjson v0.7.1/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= github.com/markbates/going v1.0.0/go.mod h1:I6mnB4BPnEeqo85ynXIx1ZFLLbtiLHNXVgWeFO9OGOA= github.com/markbates/goth v1.64.1 h1:hHtHjx/QJKblyqUtqL1z3eWSyYmZO4YXGKRdUwk1yvQ= github.com/markbates/goth v1.64.1/go.mod h1:qh2QfwZoWRucQ+DR5KVKC6dUGkNCToWh4vS45GIzFsY= -github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2 h1:JgVTCPf0uBVcUSWpyXmGpgOc62nK5HWUBKAGc3Qqa5k= github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= -github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= @@ -432,7 +413,6 @@ github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGe github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-shellwords v1.0.6 h1:9Jok5pILi5S1MnDirGVTufYGtksUs/V2BWUP3ZkeUUI= github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= -github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= @@ -450,7 +430,6 @@ github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eI github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.3.2 h1:mRS76wmkOn3KkKAyXDu42V+6ebnXWIztFSYGN7GeoRg= github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= @@ -473,13 +452,11 @@ github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FI github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pdfcpu/pdfcpu v0.2.5 h1:7jBh0EOQgxxpe35XjTtEzjHJzVMHO3ZwUn8EYNEA6Ng= github.com/pdfcpu/pdfcpu v0.2.5/go.mod h1:VLoFmLCCnUkneQe2uTjK1ZgPveTUZKGgIb2OP20+W5c= -github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.4.0 h1:u3Z1r+oOXJIkxqw34zVhyPgjBsm6X2wn21NWs/HfSeg= github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= github.com/phpdave11/gofpdi v1.0.7/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -536,7 +513,6 @@ github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTd github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/cobra v0.0.7 h1:FfTH+vuMXOas8jmfb5/M7dzEYx7LpcLb7a0LPe34uOU= github.com/spf13/cobra v0.0.7/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= @@ -555,9 +531,7 @@ github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -585,9 +559,7 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= -go.mongodb.org/mongo-driver v1.1.1 h1:Sq1fR+0c58RME5EoqKdjkiQAmPjmfHlZOoRI6fTUOcs= go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= -go.mongodb.org/mongo-driver v1.1.2 h1:jxcFYjlkl8xaERsgLo+RNquI0epW6zuy/ZRQs6jnrFA= go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.3.0 h1:ew6uUIeJOo+qdUUv7LxFCUhtWmVv7ZV/Xuy4FAUsw2E= go.mongodb.org/mongo-driver v1.3.0/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE= @@ -623,8 +595,8 @@ golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4 h1:QmwruyY+bKbDDL0BaglrbZABEali68eoMFhTZpCjYVA= -golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200317142112-1b76d66859c6 h1:TjszyFsQsyZNHwdVdZ5m7bjmreu0znc2kRYsEml9/Ww= +golang.org/x/crypto v0.0.0-20200317142112-1b76d66859c6/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -633,7 +605,6 @@ golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20190823064033-3a9bac650e44/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a h1:gHevYm0pO4QUbwy8Dmdr01R5r1BuKtfYqRqF0h/Cbh0= golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -675,7 +646,6 @@ golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180620175406-ef147856a6dd/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= @@ -685,7 +655,6 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -712,7 +681,6 @@ golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 h1:LepdCS8Gf/MVejFIt8lsiexZATdoGVyp5bcyS+rYoUI= golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -750,12 +718,10 @@ golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191004055002-72853e10c5a3/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191224055732-dd894d0a8a40 h1:UyP2XDSgSc8ldYCxAK735zQxeH3Gd81sK7Iy7AoaVxk= golang.org/x/tools v0.0.0-20191224055732-dd894d0a8a40/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= @@ -764,7 +730,6 @@ google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEn google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.2.0 h1:S0iUepdCWODXRvtE+gcRDd15L+k+k1AiHlMiMjefH24= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -808,7 +773,6 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= diff --git a/pkg/auth/authentication/auth.go b/pkg/auth/authentication/auth.go index 5a893b82b19..0f41836089a 100644 --- a/pkg/auth/authentication/auth.go +++ b/pkg/auth/authentication/auth.go @@ -13,6 +13,7 @@ import ( "github.com/transcom/mymove/pkg/models/roles" + "github.com/alexedwards/scs/v2" "github.com/gobuffalo/pop" "github.com/gofrs/uuid" "github.com/markbates/goth" @@ -96,6 +97,7 @@ func UserAuthMiddleware(logger Logger) func(next http.Handler) http.Handler { http.Error(w, http.StatusText(401), http.StatusUnauthorized) return } + // DO NOT CHECK MILMOVE SESSION BECAUSE NEW SERVICE MEMBERS WON'T HAVE AN ID RIGHT AWAY // This must be the right type of user for the application if session.IsOfficeApp() && !session.IsOfficeUser() { @@ -232,12 +234,27 @@ func (context *Context) GetFeatureFlag(flag string) bool { return false } +// sessionManager returns the session manager corresponding to the current app. +// A user can be signed in at the same time across multiple apps. +func (context Context) sessionManager(session *auth.Session) *scs.SessionManager { + if session.IsMilApp() { + return context.sessionManagers[0] + } else if session.IsAdminApp() { + return context.sessionManagers[1] + } else if session.IsOfficeApp() { + return context.sessionManagers[2] + } + + return nil +} + // Context is the common handler type for auth handlers type Context struct { logger Logger loginGovProvider LoginGovProvider callbackTemplate string featureFlags map[string]bool + sessionManagers [3]*scs.SessionManager } // FeatureFlag holds the name of a feature flag and if it is enabled @@ -247,11 +264,12 @@ type FeatureFlag struct { } // NewAuthContext creates an Context -func NewAuthContext(logger Logger, loginGovProvider LoginGovProvider, callbackProtocol string, callbackPort int) Context { +func NewAuthContext(logger Logger, loginGovProvider LoginGovProvider, callbackProtocol string, callbackPort int, sessionManagers [3]*scs.SessionManager) Context { context := Context{ logger: logger, loginGovProvider: loginGovProvider, callbackTemplate: fmt.Sprintf("%s://%%s:%d/", callbackProtocol, callbackPort), + sessionManagers: sessionManagers, } return context } @@ -259,24 +277,19 @@ func NewAuthContext(logger Logger, loginGovProvider LoginGovProvider, callbackPr // LogoutHandler handles logging the user out of login.gov type LogoutHandler struct { Context - clientAuthSecretKey string - noSessionTimeout bool - useSecureCookie bool } // NewLogoutHandler creates a new LogoutHandler -func NewLogoutHandler(ac Context, clientAuthSecretKey string, noSessionTimeout bool, useSecureCookie bool) LogoutHandler { - handler := LogoutHandler{ - Context: ac, - clientAuthSecretKey: clientAuthSecretKey, - noSessionTimeout: noSessionTimeout, - useSecureCookie: useSecureCookie, +func NewLogoutHandler(ac Context) LogoutHandler { + logoutHandler := LogoutHandler{ + Context: ac, } - return handler + return logoutHandler } func (h LogoutHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { session := auth.SessionFromRequestContext(r) + if session != nil { redirectURL := h.landingURL(session) if session.IDToken != "" { @@ -289,11 +302,9 @@ func (h LogoutHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } else { logoutURL = h.loginGovProvider.LogoutURL(redirectURL, session.IDToken) } - // This operation will delete all cookies from the session - session.IDToken = "" - session.UserID = uuid.Nil - auth.WriteSessionCookie(w, session, h.clientAuthSecretKey, h.noSessionTimeout, h.logger, h.useSecureCookie) + h.sessionManager(session).Destroy(r.Context()) auth.DeleteCSRFCookies(w) + fmt.Fprint(w, logoutURL) } else { // Can't log out of login.gov without a token, redirect and let them re-auth @@ -362,28 +373,22 @@ func (h RedirectHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // CallbackHandler processes a callback from login.gov type CallbackHandler struct { Context - db *pop.Connection - clientAuthSecretKey string - noSessionTimeout bool - useSecureCookie bool + db *pop.Connection } // NewCallbackHandler creates a new CallbackHandler -func NewCallbackHandler(ac Context, db *pop.Connection, clientAuthSecretKey string, noSessionTimeout bool, useSecureCookie bool) CallbackHandler { +func NewCallbackHandler(ac Context, db *pop.Connection) CallbackHandler { handler := CallbackHandler{ - Context: ac, - db: db, - clientAuthSecretKey: clientAuthSecretKey, - noSessionTimeout: noSessionTimeout, - useSecureCookie: useSecureCookie, + Context: ac, + db: db, } return handler } // AuthorizationCallbackHandler handles the callback from the Login.gov authorization flow func (h CallbackHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - session := auth.SessionFromRequestContext(r) + if session == nil { h.logger.Error("Session missing") http.Error(w, http.StatusText(500), http.StatusInternalServerError) @@ -391,6 +396,7 @@ func (h CallbackHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } rawLandingURL := h.landingURL(session) + landingURL, err := url.Parse(rawLandingURL) if err != nil { h.logger.Error("Error parsing landing URL") @@ -432,13 +438,12 @@ func (h CallbackHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { zap.String("cookie", hash), zap.String("hash", shaAsString(returnedState))) - // This operation will delete all cookies from the session - session.IDToken = "" - session.UserID = uuid.Nil - auth.WriteSessionCookie(w, session, h.clientAuthSecretKey, h.noSessionTimeout, h.logger, h.useSecureCookie) // Delete lg_state cookie auth.DeleteCookie(w, StateCookieName(session)) + // This operation will delete all cookies from the session + h.sessionManager(session).Destroy(r.Context()) + // set error query landingQuery := landingURL.Query() landingQuery.Add("error", "SIGNIN_ERROR") @@ -474,6 +479,7 @@ func (h CallbackHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { session.IDToken = openIDSession.IDToken session.Email = openIDUser.Email + h.logger.Info("New Login", zap.String("OID_User", openIDUser.UserID), zap.String("OID_Email", openIDUser.Email), zap.String("Host", session.Hostname)) userIdentity, err := models.FetchUserIdentity(h.db, openIDUser.UserID) @@ -522,7 +528,14 @@ var authorizeUnknownUserNew = func(openIDUser goth.User, h CallbackHandler, sess return } } - auth.WriteSessionCookie(w, session, h.clientAuthSecretKey, h.noSessionTimeout, h.logger, h.useSecureCookie) + err = h.sessionManager(session).RenewToken(r.Context()) + if err != nil { + h.logger.Error("Error renewing session token", zap.Error(err)) + http.Error(w, http.StatusText(500), http.StatusInternalServerError) + return + } + h.sessionManager(session).Put(r.Context(), "session", session) + h.logger.Info("logged in", zap.Any("session", session)) http.Redirect(w, r, h.landingURL(session), http.StatusTemporaryRedirect) return } @@ -636,9 +649,16 @@ var authorizeKnownUserNew = func(userIdentity *models.UserIdentity, h CallbackHa session.LastName = userIdentity.LastName() session.Middle = userIdentity.Middle() + error := h.sessionManager(session).RenewToken(r.Context()) + if error != nil { + h.logger.Error("Error renewing session token", zap.Error(error)) + http.Error(w, http.StatusText(500), http.StatusInternalServerError) + return + } + h.sessionManager(session).Put(r.Context(), "session", session) + h.logger.Info("logged in", zap.Any("session", session)) - auth.WriteSessionCookie(w, session, h.clientAuthSecretKey, h.noSessionTimeout, h.logger, h.useSecureCookie) http.Redirect(w, r, lURL, http.StatusTemporaryRedirect) } @@ -744,9 +764,18 @@ var authorizeKnownUser = func(userIdentity *models.UserIdentity, h CallbackHandl session.LastName = userIdentity.LastName() session.Middle = userIdentity.Middle() + // The session token must be renewed during sign in to prevent + // session fixation attacks + err := h.sessionManager(session).RenewToken(r.Context()) + if err != nil { + h.logger.Error("Error renewing session token", zap.Error(err)) + http.Error(w, http.StatusText(500), http.StatusInternalServerError) + return + } + h.sessionManager(session).Put(r.Context(), "session", session) + h.logger.Info("logged in", zap.Any("session", session)) - auth.WriteSessionCookie(w, session, h.clientAuthSecretKey, h.noSessionTimeout, h.logger, h.useSecureCookie) http.Redirect(w, r, lURL, http.StatusTemporaryRedirect) } @@ -815,9 +844,16 @@ var authorizeUnknownUser = func(openIDUser goth.User, h CallbackHandler, session return } + err = h.sessionManager(session).RenewToken(r.Context()) + if err != nil { + h.logger.Error("Error renewing session token", zap.Error(err)) + http.Error(w, http.StatusText(500), http.StatusInternalServerError) + return + } + h.sessionManager(session).Put(r.Context(), "session", session) + h.logger.Info("logged in", zap.Any("session", session)) - auth.WriteSessionCookie(w, session, h.clientAuthSecretKey, h.noSessionTimeout, h.logger, h.useSecureCookie) http.Redirect(w, r, h.landingURL(session), http.StatusTemporaryRedirect) } diff --git a/pkg/auth/authentication/auth_test.go b/pkg/auth/authentication/auth_test.go index 9e6cb26af7e..6d0b7ebd14e 100644 --- a/pkg/auth/authentication/auth_test.go +++ b/pkg/auth/authentication/auth_test.go @@ -1,6 +1,8 @@ package authentication import ( + "context" + "encoding/gob" "flag" "fmt" "log" @@ -9,7 +11,10 @@ import ( "net/url" "strconv" "testing" + "time" + "github.com/alexedwards/scs/v2" + "github.com/alexedwards/scs/v2/memstore" "github.com/markbates/goth" middleware "github.com/go-openapi/runtime/middleware" @@ -19,13 +24,11 @@ import ( "github.com/stretchr/testify/suite" "go.uber.org/zap" - "github.com/transcom/mymove/pkg/testdatagen" - "github.com/transcom/mymove/pkg/auth" "github.com/transcom/mymove/pkg/models" - "github.com/transcom/mymove/pkg/testingsuite" - "github.com/transcom/mymove/pkg/models/roles" + "github.com/transcom/mymove/pkg/testdatagen" + "github.com/transcom/mymove/pkg/testingsuite" ) const ( @@ -97,6 +100,7 @@ func (suite *AuthSuite) SetupTest() { if *useNewAuth { authorizeUnknownUser = authorizeUnknownUserNew } + gob.Register(auth.Session{}) } func TestAuthSuite(t *testing.T) { @@ -116,6 +120,36 @@ func fakeLoginGovProvider(logger Logger) LoginGovProvider { return NewLoginGovProvider("fakeHostname", "secret_key", logger) } +func setupScsSession(ctx context.Context, session *auth.Session, sessionManager *scs.SessionManager) context.Context { + values := make(map[string]interface{}) + values["session"] = session + expiry := time.Now().Add(30 * time.Minute).UTC() + b, _ := sessionManager.Codec.Encode(expiry, values) + + sessionManager.Store.Commit("session_token", b, expiry) + scsContext, _ := sessionManager.Load(ctx, "session_token") + sessionManager.Commit(scsContext) + return scsContext +} + +func setupSessionManagers() [3]*scs.SessionManager { + var milSession, adminSession, officeSession *scs.SessionManager + store := memstore.New() + milSession = scs.New() + milSession.Store = store + milSession.Cookie.Name = "mil_session_token" + + adminSession = scs.New() + adminSession.Store = store + adminSession.Cookie.Name = "admin_session_token" + + officeSession = scs.New() + officeSession.Store = store + officeSession.Cookie.Name = "office_session_token" + + return [3]*scs.SessionManager{milSession, adminSession, officeSession} +} + func (suite *AuthSuite) TestGenerateNonce() { t := suite.T() nonce := generateNonce() @@ -141,9 +175,10 @@ func (suite *AuthSuite) TestAuthorizationLogoutHandler() { } ctx := auth.SetSessionInRequestContext(req, &session) req = req.WithContext(ctx) - - authContext := NewAuthContext(suite.logger, fakeLoginGovProvider(suite.logger), "http", callbackPort) - handler := LogoutHandler{authContext, "fake key", false, false} + sessionManagers := setupSessionManagers() + officeSession := sessionManagers[2] + authContext := NewAuthContext(suite.logger, fakeLoginGovProvider(suite.logger), "http", callbackPort, sessionManagers) + handler := officeSession.LoadAndSave(LogoutHandler{authContext}) rr := httptest.NewRecorder() handler.ServeHTTP(rr, req.WithContext(ctx)) @@ -188,7 +223,9 @@ func (suite *AuthSuite) TestRequireAuthMiddleware() { handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { handlerSession = auth.SessionFromRequestContext(r) }) - middleware := UserAuthMiddleware(suite.logger)(handler) + var sessionManager *scs.SessionManager + sessionManager = scs.New() + middleware := sessionManager.LoadAndSave(UserAuthMiddleware(suite.logger)(handler)) middleware.ServeHTTP(rr, req) @@ -201,7 +238,9 @@ func (suite *AuthSuite) TestIsLoggedInWhenNoUserLoggedIn() { req := httptest.NewRequest("GET", "/is_logged_in", nil) rr := httptest.NewRecorder() - handler := http.HandlerFunc(IsLoggedInMiddleware(suite.logger)) + var sessionManager *scs.SessionManager + sessionManager = scs.New() + handler := sessionManager.LoadAndSave(IsLoggedInMiddleware(suite.logger)) handler.ServeHTTP(rr, req) @@ -224,13 +263,15 @@ func (suite *AuthSuite) TestIsLoggedInWhenUserLoggedIn() { req := httptest.NewRequest("GET", "/is_logged_in", nil) + var sessionManager *scs.SessionManager + sessionManager = scs.New() // And: the context contains the auth values session := auth.Session{UserID: user.ID, IDToken: "fake Token"} ctx := auth.SetSessionInRequestContext(req, &session) req = req.WithContext(ctx) rr := httptest.NewRecorder() - handler := IsLoggedInMiddleware(suite.logger) + handler := sessionManager.LoadAndSave(IsLoggedInMiddleware(suite.logger)) handler.ServeHTTP(rr, req) @@ -250,7 +291,9 @@ func (suite *AuthSuite) TestRequireAuthMiddlewareUnauthorized() { req := httptest.NewRequest("GET", "/moves", nil) handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - middleware := UserAuthMiddleware(suite.logger)(handler) + var sessionManager *scs.SessionManager + sessionManager = scs.New() + middleware := sessionManager.LoadAndSave(UserAuthMiddleware(suite.logger)(handler)) middleware.ServeHTTP(rr, req) @@ -328,13 +371,12 @@ func (suite *AuthSuite) TestAuthorizeDeactivateUser() { } ctx := auth.SetSessionInRequestContext(req, &session) callbackPort := 1234 - authContext := NewAuthContext(suite.logger, fakeLoginGovProvider(suite.logger), "http", callbackPort) + sessionManagers := setupSessionManagers() + authContext := NewAuthContext(suite.logger, fakeLoginGovProvider(suite.logger), "http", callbackPort, sessionManagers) + h := CallbackHandler{ authContext, suite.DB(), - "fake key", - false, - false, } rr := httptest.NewRecorder() authorizeKnownUser(&userIdentity, h, &session, rr, req.WithContext(ctx), "") @@ -361,16 +403,18 @@ func (suite *AuthSuite) TestAuthKnownSingleRoleOffice() { } ctx := auth.SetSessionInRequestContext(req, &session) callbackPort := 1234 - authContext := NewAuthContext(suite.logger, fakeLoginGovProvider(suite.logger), "http", callbackPort) + sessionManagers := setupSessionManagers() + authContext := NewAuthContext(suite.logger, fakeLoginGovProvider(suite.logger), "http", callbackPort, sessionManagers) + + officeSession := sessionManagers[2] + scsContext := setupScsSession(ctx, &session, officeSession) + h := CallbackHandler{ authContext, suite.DB(), - "fake key", - false, - false, } rr := httptest.NewRecorder() - authorizeKnownUser(&userIdentity, h, &session, rr, req.WithContext(ctx), "") + authorizeKnownUser(&userIdentity, h, &session, rr, req.WithContext(scsContext), "") // Office app, so should only have office ID information suite.Equal(officeUserID, session.OfficeUserID) @@ -395,13 +439,11 @@ func (suite *AuthSuite) TestAuthorizeDeactivateOfficeUser() { } ctx := auth.SetSessionInRequestContext(req, &session) callbackPort := 1234 - authContext := NewAuthContext(suite.logger, fakeLoginGovProvider(suite.logger), "http", callbackPort) + sessionManagers := setupSessionManagers() + authContext := NewAuthContext(suite.logger, fakeLoginGovProvider(suite.logger), "http", callbackPort, sessionManagers) h := CallbackHandler{ authContext, suite.DB(), - "fake key", - false, - false, } rr := httptest.NewRecorder() authorizeKnownUser(&userIdentity, h, &session, rr, req.WithContext(ctx), "") @@ -437,20 +479,23 @@ func (suite *AuthSuite) TestRedirectLoginGovErrorMsg() { req.AddCookie(&cookie) ctx := auth.SetSessionInRequestContext(req, &session) + callbackPort := 1234 - authContext := NewAuthContext(suite.logger, fakeLoginGovProvider(suite.logger), "http", callbackPort) + sessionManagers := setupSessionManagers() + authContext := NewAuthContext(suite.logger, fakeLoginGovProvider(suite.logger), "http", callbackPort, sessionManagers) + + officeSession := sessionManagers[2] + scsContext := setupScsSession(ctx, &session, officeSession) + h := CallbackHandler{ authContext, suite.DB(), - "fake key", - false, - false, } rr := httptest.NewRecorder() - authorizeKnownUser(&userIdentity, h, &session, rr, req.WithContext(ctx), "") + authorizeKnownUser(&userIdentity, h, &session, rr, req.WithContext(scsContext), "") rr2 := httptest.NewRecorder() - h.ServeHTTP(rr2, req.WithContext(ctx)) + officeSession.LoadAndSave(h).ServeHTTP(rr2, req.WithContext(scsContext)) // Office app, so should only have office ID information suite.Equal(officeUserID, session.OfficeUserID) @@ -458,8 +503,8 @@ func (suite *AuthSuite) TestRedirectLoginGovErrorMsg() { suite.Equal(2, len(rr2.Result().Cookies())) // check for blank value for cookie login gov state value and the session cookie value for _, cookie := range rr2.Result().Cookies() { - if cookie.Name == cookieName || cookie.Name == fmt.Sprintf("%s_%s", string(session.ApplicationName), auth.UserSessionCookieName) { - suite.Equal("blank", cookie.Value) + if cookie.Name == cookieName || cookie.Name == "office_session_token" { + suite.Equal("", cookie.Value) suite.Equal("/", cookie.Path) } } @@ -492,16 +537,18 @@ func (suite *AuthSuite) TestAuthKnownSingleRoleAdmin() { ctx := auth.SetSessionInRequestContext(req, &session) callbackPort := 1234 - authContext := NewAuthContext(suite.logger, fakeLoginGovProvider(suite.logger), "http", callbackPort) + sessionManagers := setupSessionManagers() + authContext := NewAuthContext(suite.logger, fakeLoginGovProvider(suite.logger), "http", callbackPort, sessionManagers) + + adminSession := sessionManagers[1] + scsContext := setupScsSession(ctx, &session, adminSession) + h := CallbackHandler{ authContext, suite.DB(), - "fake key", - false, - false, } rr := httptest.NewRecorder() - authorizeKnownUser(&userIdentity, h, &session, rr, req.WithContext(ctx), "") + authorizeKnownUser(&userIdentity, h, &session, rr, req.WithContext(scsContext), "") // admin app, so should only have admin ID information suite.Equal(adminUserID, session.AdminUserID) @@ -530,13 +577,11 @@ func (suite *AuthSuite) TestAuthorizeDeactivateAdmin() { } ctx := auth.SetSessionInRequestContext(req, &session) callbackPort := 1234 - authContext := NewAuthContext(suite.logger, fakeLoginGovProvider(suite.logger), "http", callbackPort) + sessionManagers := setupSessionManagers() + authContext := NewAuthContext(suite.logger, fakeLoginGovProvider(suite.logger), "http", callbackPort, sessionManagers) h := CallbackHandler{ authContext, suite.DB(), - "fake key", - false, - false, } rr := httptest.NewRecorder() authorizeKnownUser(&userIdentity, h, &session, rr, req.WithContext(ctx), "") @@ -567,13 +612,11 @@ func (suite *AuthSuite) TestAuthorizeUnknownUserOfficeDeactivated() { } callbackPort := 1234 - authContext := NewAuthContext(suite.logger, fakeLoginGovProvider(suite.logger), "http", callbackPort) + sessionManagers := setupSessionManagers() + authContext := NewAuthContext(suite.logger, fakeLoginGovProvider(suite.logger), "http", callbackPort, sessionManagers) h := CallbackHandler{ authContext, suite.DB(), - "fake key", - false, - false, } rr := httptest.NewRecorder() @@ -603,13 +646,11 @@ func (suite *AuthSuite) TestAuthorizeUnknownUserOfficeNotFound() { } callbackPort := 1234 - authContext := NewAuthContext(suite.logger, fakeLoginGovProvider(suite.logger), "http", callbackPort) + sessionManagers := setupSessionManagers() + authContext := NewAuthContext(suite.logger, fakeLoginGovProvider(suite.logger), "http", callbackPort, sessionManagers) h := CallbackHandler{ authContext, suite.DB(), - "fake key", - false, - false, } rr := httptest.NewRecorder() @@ -650,17 +691,19 @@ func (suite *AuthSuite) TestAuthorizeUnknownUserOfficeLogsIn() { } callbackPort := 1234 - authContext := NewAuthContext(suite.logger, fakeLoginGovProvider(suite.logger), "http", callbackPort) + sessionManagers := setupSessionManagers() + authContext := NewAuthContext(suite.logger, fakeLoginGovProvider(suite.logger), "http", callbackPort, sessionManagers) + + officeSession := sessionManagers[2] + scsContext := setupScsSession(ctx, &session, officeSession) + h := CallbackHandler{ authContext, suite.DB(), - "fake key", - false, - false, } rr := httptest.NewRecorder() - authorizeUnknownUser(user, h, &session, rr, req.WithContext(ctx), "") + authorizeUnknownUser(user, h, &session, rr, req.WithContext(scsContext), "") // Office app, so should only have office ID information suite.Equal(officeUser.ID, session.OfficeUserID) @@ -686,13 +729,11 @@ func (suite *AuthSuite) TestAuthorizeUnknownUserAdminDeactivated() { } callbackPort := 1234 - authContext := NewAuthContext(suite.logger, fakeLoginGovProvider(suite.logger), "http", callbackPort) + sessionManagers := setupSessionManagers() + authContext := NewAuthContext(suite.logger, fakeLoginGovProvider(suite.logger), "http", callbackPort, sessionManagers) h := CallbackHandler{ authContext, suite.DB(), - "fake key", - false, - false, } rr := httptest.NewRecorder() @@ -722,13 +763,11 @@ func (suite *AuthSuite) TestAuthorizeUnknownUserAdminNotFound() { } callbackPort := 1234 - authContext := NewAuthContext(suite.logger, fakeLoginGovProvider(suite.logger), "http", callbackPort) + sessionManagers := setupSessionManagers() + authContext := NewAuthContext(suite.logger, fakeLoginGovProvider(suite.logger), "http", callbackPort, sessionManagers) h := CallbackHandler{ authContext, suite.DB(), - "fake key", - false, - false, } rr := httptest.NewRecorder() @@ -763,17 +802,19 @@ func (suite *AuthSuite) TestAuthorizeUnknownUserAdminLogsIn() { } callbackPort := 1234 - authContext := NewAuthContext(suite.logger, fakeLoginGovProvider(suite.logger), "http", callbackPort) + sessionManagers := setupSessionManagers() + authContext := NewAuthContext(suite.logger, fakeLoginGovProvider(suite.logger), "http", callbackPort, sessionManagers) + + adminSession := sessionManagers[1] + scsContext := setupScsSession(ctx, &session, adminSession) + h := CallbackHandler{ authContext, suite.DB(), - FakeRSAKey, - false, - false, } rr := httptest.NewRecorder() - authorizeUnknownUser(user, h, &session, rr, req.WithContext(ctx), "") + authorizeUnknownUser(user, h, &session, rr, req.WithContext(scsContext), "") // Office app, so should only have office ID information suite.Equal(adminUser.ID, session.AdminUserID) diff --git a/pkg/auth/authentication/authgch_test.go b/pkg/auth/authentication/authgch_test.go index 248793efd17..930b5cc7bab 100644 --- a/pkg/auth/authentication/authgch_test.go +++ b/pkg/auth/authentication/authgch_test.go @@ -48,18 +48,16 @@ func (suite *AuthSuite) TestCreateTOO() { } req.AddCookie(&cookie) callbackPort := 1234 - authContext := NewAuthContext(suite.logger, fakeLoginGovProvider(suite.logger), "http", callbackPort) + sessionManagers := setupSessionManagers() + officeSession := sessionManagers[2] + authContext := NewAuthContext(suite.logger, fakeLoginGovProvider(suite.logger), "http", callbackPort, sessionManagers) h := CallbackHandler{ authContext, suite.DB(), - FakeRSAKey, - false, - false, } rr := httptest.NewRecorder() h.SetFeatureFlag(FeatureFlag{Name: cli.FeatureFlagRoleBasedAuth, Active: true}) - h.ServeHTTP(rr, req) - + officeSession.LoadAndSave(h).ServeHTTP(rr, req) suite.Equal(rr.Code, 307) } diff --git a/pkg/auth/authentication/authghc.go b/pkg/auth/authentication/authghc.go index 79a39700554..c0d076a7c14 100644 --- a/pkg/auth/authentication/authghc.go +++ b/pkg/auth/authentication/authghc.go @@ -137,7 +137,6 @@ func (uua UnknownUserAuthorizer) AuthorizeUnknownUser(openIDUser goth.User, sess return err } } - uua.logger.Info("logged in", zap.Any("session", session)) return nil } diff --git a/pkg/auth/authentication/devlocal.go b/pkg/auth/authentication/devlocal.go index 497504a9c93..aaf12911292 100644 --- a/pkg/auth/authentication/devlocal.go +++ b/pkg/auth/authentication/devlocal.go @@ -33,19 +33,13 @@ const ( type UserListHandler struct { db *pop.Connection Context - clientAuthSecretKey string - noSessionTimeout bool - useSecureCookie bool } // NewUserListHandler returns a new UserListHandler -func NewUserListHandler(ac Context, db *pop.Connection, clientAuthSecretKey string, noSessionTimeout bool, useSecureCookie bool) UserListHandler { +func NewUserListHandler(ac Context, db *pop.Connection) UserListHandler { handler := UserListHandler{ - Context: ac, - db: db, - clientAuthSecretKey: clientAuthSecretKey, - noSessionTimeout: noSessionTimeout, - useSecureCookie: useSecureCookie, + Context: ac, + db: db, } return handler } @@ -57,9 +51,7 @@ func (h UserListHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // User is already authenticated, so clear out their current session and have // them try again. This the issue where a developer will get stuck with a stale // session and have to manually clear cookies to get back to the login page. - session.IDToken = "" - session.UserID = uuid.Nil - auth.WriteSessionCookie(w, session, h.clientAuthSecretKey, h.noSessionTimeout, h.logger, h.useSecureCookie) + h.sessionManager(session).Destroy(r.Context()) auth.DeleteCSRFCookies(w) http.Redirect(w, r, h.landingURL(session), http.StatusTemporaryRedirect) @@ -199,25 +191,19 @@ func (h UserListHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { type devlocalAuthHandler struct { Context - db *pop.Connection - appnames auth.ApplicationServername - clientAuthSecretKey string - noSessionTimeout bool - useSecureCookie bool + db *pop.Connection + appnames auth.ApplicationServername } // AssignUserHandler logs a user in directly type AssignUserHandler devlocalAuthHandler // NewAssignUserHandler creates a new AssignUserHandler -func NewAssignUserHandler(ac Context, db *pop.Connection, appnames auth.ApplicationServername, clientAuthSecretKey string, noSessionTimeout bool, useSecureCookie bool) AssignUserHandler { +func NewAssignUserHandler(ac Context, db *pop.Connection, appnames auth.ApplicationServername) AssignUserHandler { handler := AssignUserHandler{ - Context: ac, - db: db, - appnames: appnames, - clientAuthSecretKey: clientAuthSecretKey, - noSessionTimeout: noSessionTimeout, - useSecureCookie: useSecureCookie, + Context: ac, + db: db, + appnames: appnames, } return handler } @@ -269,14 +255,11 @@ func (h AssignUserHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { type CreateUserHandler devlocalAuthHandler // NewCreateUserHandler creates a new CreateUserHandler -func NewCreateUserHandler(ac Context, db *pop.Connection, appnames auth.ApplicationServername, clientAuthSecretKey string, noSessionTimeout bool, useSecureCookie bool) CreateUserHandler { +func NewCreateUserHandler(ac Context, db *pop.Connection, appnames auth.ApplicationServername) CreateUserHandler { handler := CreateUserHandler{ - Context: ac, - db: db, - appnames: appnames, - clientAuthSecretKey: clientAuthSecretKey, - noSessionTimeout: noSessionTimeout, - useSecureCookie: useSecureCookie, + Context: ac, + db: db, + appnames: appnames, } return handler } @@ -302,14 +285,11 @@ func (h CreateUserHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { type CreateAndLoginUserHandler devlocalAuthHandler // NewCreateAndLoginUserHandler creates a new CreateAndLoginUserHandler -func NewCreateAndLoginUserHandler(ac Context, db *pop.Connection, appnames auth.ApplicationServername, clientAuthSecretKey string, noSessionTimeout bool, useSecureCookie bool) CreateAndLoginUserHandler { +func NewCreateAndLoginUserHandler(ac Context, db *pop.Connection, appnames auth.ApplicationServername) CreateAndLoginUserHandler { handler := CreateAndLoginUserHandler{ - Context: ac, - db: db, - appnames: appnames, - clientAuthSecretKey: clientAuthSecretKey, - noSessionTimeout: noSessionTimeout, - useSecureCookie: useSecureCookie, + Context: ac, + db: db, + appnames: appnames, } return handler } @@ -535,10 +515,9 @@ func createSession(h devlocalAuthHandler, user *models.User, userType string, w session.LastName = userIdentity.LastName() session.Middle = userIdentity.Middle() + h.sessionManager(session).Put(r.Context(), "session", session) // Writing out the session cookie logs in the user h.logger.Info("logged in", zap.Any("session", session)) - auth.WriteSessionCookie(w, session, h.clientAuthSecretKey, h.noSessionTimeout, h.logger, h.useSecureCookie) - return session, nil } diff --git a/pkg/auth/authentication/devlocal_test.go b/pkg/auth/authentication/devlocal_test.go index e522c51984e..3cbeaaa4ef5 100644 --- a/pkg/auth/authentication/devlocal_test.go +++ b/pkg/auth/authentication/devlocal_test.go @@ -37,11 +37,13 @@ func (suite *AuthSuite) TestCreateUserHandlerMilMove() { req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value") req.ParseForm() - authContext := NewAuthContext(suite.logger, fakeLoginGovProvider(suite.logger), "http", callbackPort) - handler := NewCreateUserHandler(authContext, suite.DB(), appnames, FakeRSAKey, false, false) + sessionManagers := setupSessionManagers() + milSession := sessionManagers[0] + authContext := NewAuthContext(suite.logger, fakeLoginGovProvider(suite.logger), "http", callbackPort, sessionManagers) + handler := NewCreateUserHandler(authContext, suite.DB(), appnames) rr := httptest.NewRecorder() - handler.ServeHTTP(rr, req) + milSession.LoadAndSave(handler).ServeHTTP(rr, req) suite.Equal(http.StatusOK, rr.Code, "handler returned wrong status code") if status := rr.Code; status != http.StatusOK { @@ -78,11 +80,13 @@ func (suite *AuthSuite) TestCreateUserHandlerOffice() { req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value") req.ParseForm() - authContext := NewAuthContext(suite.logger, fakeLoginGovProvider(suite.logger), "http", callbackPort) - handler := NewCreateUserHandler(authContext, suite.DB(), appnames, FakeRSAKey, false, false) + sessionManagers := setupSessionManagers() + officeSession := sessionManagers[2] + authContext := NewAuthContext(suite.logger, fakeLoginGovProvider(suite.logger), "http", callbackPort, sessionManagers) + handler := NewCreateUserHandler(authContext, suite.DB(), appnames) rr := httptest.NewRecorder() - handler.ServeHTTP(rr, req) + officeSession.LoadAndSave(handler).ServeHTTP(rr, req) suite.Equal(http.StatusOK, rr.Code, "handler returned wrong status code") if status := rr.Code; status != http.StatusOK { @@ -123,11 +127,13 @@ func (suite *AuthSuite) TestCreateUserHandlerDPS() { req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value") req.ParseForm() - authContext := NewAuthContext(suite.logger, fakeLoginGovProvider(suite.logger), "http", callbackPort) - handler := NewCreateUserHandler(authContext, suite.DB(), appnames, FakeRSAKey, false, false) + sessionManagers := setupSessionManagers() + milSession := sessionManagers[0] + authContext := NewAuthContext(suite.logger, fakeLoginGovProvider(suite.logger), "http", callbackPort, sessionManagers) + handler := NewCreateUserHandler(authContext, suite.DB(), appnames) rr := httptest.NewRecorder() - handler.ServeHTTP(rr, req) + milSession.LoadAndSave(handler).ServeHTTP(rr, req) suite.Equal(http.StatusOK, rr.Code, "handler returned wrong status code") if status := rr.Code; status != http.StatusOK { @@ -164,11 +170,13 @@ func (suite *AuthSuite) TestCreateUserHandlerAdmin() { req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value") req.ParseForm() - authContext := NewAuthContext(suite.logger, fakeLoginGovProvider(suite.logger), "http", callbackPort) - handler := NewCreateUserHandler(authContext, suite.DB(), appnames, FakeRSAKey, false, false) + sessionManagers := setupSessionManagers() + adminSession := sessionManagers[1] + authContext := NewAuthContext(suite.logger, fakeLoginGovProvider(suite.logger), "http", callbackPort, sessionManagers) + handler := NewCreateUserHandler(authContext, suite.DB(), appnames) rr := httptest.NewRecorder() - handler.ServeHTTP(rr, req) + adminSession.LoadAndSave(handler).ServeHTTP(rr, req) suite.Equal(http.StatusOK, rr.Code, "handler returned wrong status code") if status := rr.Code; status != http.StatusOK { @@ -210,11 +218,13 @@ func (suite *AuthSuite) TestCreateAndLoginUserHandlerFromMilMoveToMilMove() { req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value") req.ParseForm() - authContext := NewAuthContext(suite.logger, fakeLoginGovProvider(suite.logger), "http", callbackPort) - handler := NewCreateAndLoginUserHandler(authContext, suite.DB(), appnames, FakeRSAKey, false, false) + sessionManagers := setupSessionManagers() + milSession := sessionManagers[0] + authContext := NewAuthContext(suite.logger, fakeLoginGovProvider(suite.logger), "http", callbackPort, sessionManagers) + handler := NewCreateAndLoginUserHandler(authContext, suite.DB(), appnames) rr := httptest.NewRecorder() - handler.ServeHTTP(rr, req) + milSession.LoadAndSave(handler).ServeHTTP(rr, req) suite.Equal(http.StatusSeeOther, rr.Code, "handler returned wrong status code") if status := rr.Code; status != http.StatusSeeOther { @@ -242,11 +252,13 @@ func (suite *AuthSuite) TestCreateAndLoginUserHandlerFromMilMoveToOffice() { req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value") req.ParseForm() - authContext := NewAuthContext(suite.logger, fakeLoginGovProvider(suite.logger), "http", callbackPort) - handler := NewCreateAndLoginUserHandler(authContext, suite.DB(), appnames, FakeRSAKey, false, false) + sessionManagers := setupSessionManagers() + officeSession := sessionManagers[2] + authContext := NewAuthContext(suite.logger, fakeLoginGovProvider(suite.logger), "http", callbackPort, sessionManagers) + handler := NewCreateAndLoginUserHandler(authContext, suite.DB(), appnames) rr := httptest.NewRecorder() - handler.ServeHTTP(rr, req) + officeSession.LoadAndSave(handler).ServeHTTP(rr, req) suite.Equal(http.StatusSeeOther, rr.Code, "handler returned wrong status code") if status := rr.Code; status != http.StatusSeeOther { @@ -274,11 +286,13 @@ func (suite *AuthSuite) TestCreateAndLoginUserHandlerFromMilMoveToAdmin() { req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value") req.ParseForm() - authContext := NewAuthContext(suite.logger, fakeLoginGovProvider(suite.logger), "http", callbackPort) - handler := NewCreateAndLoginUserHandler(authContext, suite.DB(), appnames, FakeRSAKey, false, false) + sessionManagers := setupSessionManagers() + adminSession := sessionManagers[1] + authContext := NewAuthContext(suite.logger, fakeLoginGovProvider(suite.logger), "http", callbackPort, sessionManagers) + handler := NewCreateAndLoginUserHandler(authContext, suite.DB(), appnames) rr := httptest.NewRecorder() - handler.ServeHTTP(rr, req) + adminSession.LoadAndSave(handler).ServeHTTP(rr, req) suite.Equal(http.StatusSeeOther, rr.Code, "handler returned wrong status code") if status := rr.Code; status != http.StatusSeeOther { @@ -306,11 +320,13 @@ func (suite *AuthSuite) TestCreateAndLoginUserHandlerFromOfficeToMilMove() { req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value") req.ParseForm() - authContext := NewAuthContext(suite.logger, fakeLoginGovProvider(suite.logger), "http", callbackPort) - handler := NewCreateAndLoginUserHandler(authContext, suite.DB(), appnames, FakeRSAKey, false, false) + sessionManagers := setupSessionManagers() + milSession := sessionManagers[0] + authContext := NewAuthContext(suite.logger, fakeLoginGovProvider(suite.logger), "http", callbackPort, sessionManagers) + handler := NewCreateAndLoginUserHandler(authContext, suite.DB(), appnames) rr := httptest.NewRecorder() - handler.ServeHTTP(rr, req) + milSession.LoadAndSave(handler).ServeHTTP(rr, req) suite.Equal(http.StatusSeeOther, rr.Code, "handler returned wrong status code") if status := rr.Code; status != http.StatusSeeOther { @@ -338,11 +354,13 @@ func (suite *AuthSuite) TestCreateAndLoginUserHandlerFromOfficeToAdmin() { req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value") req.ParseForm() - authContext := NewAuthContext(suite.logger, fakeLoginGovProvider(suite.logger), "http", callbackPort) - handler := NewCreateAndLoginUserHandler(authContext, suite.DB(), appnames, FakeRSAKey, false, false) + sessionManagers := setupSessionManagers() + authContext := NewAuthContext(suite.logger, fakeLoginGovProvider(suite.logger), "http", callbackPort, sessionManagers) + handler := NewCreateAndLoginUserHandler(authContext, suite.DB(), appnames) + adminSession := sessionManagers[1] rr := httptest.NewRecorder() - handler.ServeHTTP(rr, req) + adminSession.LoadAndSave(handler).ServeHTTP(rr, req) suite.Equal(http.StatusSeeOther, rr.Code, "handler returned wrong status code") if status := rr.Code; status != http.StatusSeeOther { @@ -370,11 +388,13 @@ func (suite *AuthSuite) TestCreateAndLoginUserHandlerFromAdminToMilMove() { req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value") req.ParseForm() - authContext := NewAuthContext(suite.logger, fakeLoginGovProvider(suite.logger), "http", callbackPort) - handler := NewCreateAndLoginUserHandler(authContext, suite.DB(), appnames, FakeRSAKey, false, false) + sessionManagers := setupSessionManagers() + milSession := sessionManagers[0] + authContext := NewAuthContext(suite.logger, fakeLoginGovProvider(suite.logger), "http", callbackPort, sessionManagers) + handler := NewCreateAndLoginUserHandler(authContext, suite.DB(), appnames) rr := httptest.NewRecorder() - handler.ServeHTTP(rr, req) + milSession.LoadAndSave(handler).ServeHTTP(rr, req) suite.Equal(http.StatusSeeOther, rr.Code, "handler returned wrong status code") if status := rr.Code; status != http.StatusSeeOther { @@ -402,11 +422,13 @@ func (suite *AuthSuite) TestCreateAndLoginUserHandlerFromAdminToOffice() { req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value") req.ParseForm() - authContext := NewAuthContext(suite.logger, fakeLoginGovProvider(suite.logger), "http", callbackPort) - handler := NewCreateAndLoginUserHandler(authContext, suite.DB(), appnames, FakeRSAKey, false, false) + sessionManagers := setupSessionManagers() + officeSession := sessionManagers[2] + authContext := NewAuthContext(suite.logger, fakeLoginGovProvider(suite.logger), "http", callbackPort, sessionManagers) + handler := NewCreateAndLoginUserHandler(authContext, suite.DB(), appnames) rr := httptest.NewRecorder() - handler.ServeHTTP(rr, req) + officeSession.LoadAndSave(handler).ServeHTTP(rr, req) suite.Equal(http.StatusSeeOther, rr.Code, "handler returned wrong status code") if status := rr.Code; status != http.StatusSeeOther { diff --git a/pkg/auth/cookie.go b/pkg/auth/cookie.go index e2838650af9..c182489104c 100644 --- a/pkg/auth/cookie.go +++ b/pkg/auth/cookie.go @@ -6,8 +6,7 @@ import ( "strings" "time" - jwt "github.com/dgrijalva/jwt-go" - "github.com/gofrs/uuid" + "github.com/alexedwards/scs/v2" "github.com/gorilla/csrf" "github.com/pkg/errors" "go.uber.org/zap" @@ -37,9 +36,6 @@ func (e *errInvalidHostname) Error() string { return fmt.Sprintf("invalid hostname %s, must be one of %s, %s, or %s", e.Hostname, e.MilApp, e.OfficeApp, e.AdminApp) } -// UserSessionCookieName is the key suffix at which we're storing our token cookie -const UserSessionCookieName = "session_token" - // GorillaCSRFToken is the name of the base CSRF token const GorillaCSRFToken = "_gorilla_csrf" // #nosec G101 @@ -48,12 +44,6 @@ const MaskedGorillaCSRFToken = "masked_gorilla_csrf" // SessionExpiryInMinutes is the number of minutes before a fallow session is harvested const SessionExpiryInMinutes = 15 -const sessionExpiryInSeconds = 15 * 60 - -// A representable date far in the future. The trouble with something like https://stackoverflow.com/a/32620397 -// is that it produces a date which may not marshall well into JSON which makes logging problematic -var likeForever = time.Date(9999, 1, 1, 12, 0, 0, 0, time.UTC) -var likeForeverInSeconds = 99999999 // GetExpiryTimeFromMinutes returns 'min' minutes from now func GetExpiryTimeFromMinutes(min int64) time.Time { @@ -73,69 +63,16 @@ func GetCookie(name string, r *http.Request) (*http.Cookie, error) { // DeleteCookie sends a delete request for the named cookie func DeleteCookie(w http.ResponseWriter, name string) { c := http.Cookie{ - Name: name, - Value: "blank", - MaxAge: -1, - Path: "/", + Name: name, + Value: "", + Path: "/", + HttpOnly: true, + Expires: time.Unix(1, 0), + MaxAge: -1, } http.SetCookie(w, &c) } -// SessionClaims wraps StandardClaims with some Session info -type SessionClaims struct { - jwt.StandardClaims - SessionValue Session -} - -func signTokenStringWithUserInfo(expiry time.Time, session *Session, secret string) (string, error) { - claims := SessionClaims{ - StandardClaims: jwt.StandardClaims{ExpiresAt: expiry.Unix()}, - SessionValue: *session, - } - - token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims) - rsaKey, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(secret)) - if err != nil { - err = errors.Wrap(err, "Parsing RSA key from PEM") - return "", err - } - - ss, err := token.SignedString(rsaKey) - if err != nil { - err = errors.Wrap(err, "Signing string with token") - return "", err - } - return ss, err -} - -func sessionClaimsFromRequest(logger Logger, secret string, appName Application, r *http.Request) (claims *SessionClaims, ok bool) { - // Name the cookie with the app name - cookieName := fmt.Sprintf("%s_%s", string(appName), UserSessionCookieName) - cookie, err := r.Cookie(cookieName) - if err != nil { - // No cookie set on client - return - } - - token, err := jwt.ParseWithClaims(cookie.Value, &SessionClaims{}, func(token *jwt.Token) (interface{}, error) { - rsaKey, parseRSAPrivateKeyFromPEMErr := jwt.ParseRSAPrivateKeyFromPEM([]byte(secret)) - return &rsaKey.PublicKey, parseRSAPrivateKeyFromPEMErr - }) - - if err != nil || token == nil || !token.Valid { - logger.Error("Failed token validation", zap.Error(err)) - return - } - - // The token actually just stores a Claims interface, so we need to explicitly cast back to UserClaims - claims, ok = token.Claims.(*SessionClaims) - if !ok { - logger.Error("Failed getting claims from token") - return - } - return claims, ok -} - // WriteMaskedCSRFCookie update the masked_gorilla_csrf cookie value func WriteMaskedCSRFCookie(w http.ResponseWriter, csrfToken string, logger Logger, useSecureCookie bool) { // Match expiration settings of the _gorilla_csrf cookie (a session cookie); don't set Expires or MaxAge. @@ -171,48 +108,6 @@ func MaskedCSRFMiddleware(logger Logger, useSecureCookie bool) func(next http.Ha } } -// WriteSessionCookie update the cookie for the session -func WriteSessionCookie(w http.ResponseWriter, session *Session, secret string, noSessionTimeout bool, logger Logger, useSecureCookie bool) { - // Delete the cookie - cookieName := fmt.Sprintf("%s_%s", string(session.ApplicationName), UserSessionCookieName) - cookie := http.Cookie{ - Name: cookieName, - Value: "blank", - Path: "/", - Expires: time.Unix(0, 0), - MaxAge: -1, - HttpOnly: true, - SameSite: http.SameSiteLaxMode, // Using 'lax' mode now since 'strict' breaks the use of the login.gov redirect - Secure: useSecureCookie, - } - - // unless we have a valid session - if session.IDToken != "" && session.UserID != uuid.Nil { - expiry := GetExpiryTimeFromMinutes(SessionExpiryInMinutes) - maxAge := sessionExpiryInSeconds - // Never expire token if in development - if noSessionTimeout { - expiry = likeForever - maxAge = likeForeverInSeconds - } - - ss, err := signTokenStringWithUserInfo(expiry, session, secret) - if err != nil { - logger.Error("Generating signed token string", zap.Error(err)) - } else { - logger.Info("Cookie", zap.Int("Size", len(ss))) - cookie.Value = ss - cookie.Expires = expiry - cookie.MaxAge = maxAge - } - } - // http.SetCookie calls Header().Add() instead of .Set(), which can result in duplicate cookies - // It's ok to use this here because we want to delete and rewrite `Set-Cookie` on login or if the - // session token is lost. However, we would normally use http.SetCookie for any other cookie operations - // so as not to delete the session token. - w.Header().Set("Set-Cookie", cookie.String()) -} - // ApplicationName returns the application name given the hostname func ApplicationName(hostname string, appnames ApplicationServername) (Application, error) { var appName Application @@ -232,8 +127,20 @@ func ApplicationName(hostname string, appnames ApplicationServername) (Applicati }, fmt.Sprintf("%s is invalid", hostname)) } +func sessionManager(session Session, sessionManagers [3]*scs.SessionManager) *scs.SessionManager { + if session.IsMilApp() { + return sessionManagers[0] + } else if session.IsAdminApp() { + return sessionManagers[1] + } else if session.IsOfficeApp() { + return sessionManagers[2] + } + + return nil +} + // SessionCookieMiddleware handle serializing and de-serializing the session between the user_session cookie and the request context -func SessionCookieMiddleware(serverLogger Logger, secret string, noSessionTimeout bool, appnames ApplicationServername, useSecureCookie bool) func(next http.Handler) http.Handler { +func SessionCookieMiddleware(serverLogger Logger, appnames ApplicationServername, sessionManagers [3]*scs.SessionManager) func(next http.Handler) http.Handler { serverLogger.Info("Creating session", zap.String("milServername", appnames.MilServername), zap.String("officeServername", appnames.OfficeServername), @@ -261,17 +168,20 @@ func SessionCookieMiddleware(serverLogger Logger, secret string, noSessionTimeou http.Error(w, http.StatusText(400), http.StatusBadRequest) return } - claims, ok := sessionClaimsFromRequest(logger, secret, appName, r) - if ok { - session = claims.SessionValue - } // Set more information on the session session.ApplicationName = appName session.Hostname = strings.ToLower(hostname) + sessionManager := sessionManager(session, sessionManagers) + + existingSession := sessionManager.Get(r.Context(), "session") + if existingSession != nil { + session = existingSession.(Session) + } + // And update the cookie. May get over-ridden later - WriteSessionCookie(w, &session, secret, noSessionTimeout, logger, useSecureCookie) + sessionManager.Put(r.Context(), "session", session) // And put the session info into the request context next.ServeHTTP(w, r.WithContext(SetSessionInContext(ctx, &session))) diff --git a/pkg/auth/cookie_test.go b/pkg/auth/cookie_test.go index 43cbeec086b..85cd0d4e1a5 100644 --- a/pkg/auth/cookie_test.go +++ b/pkg/auth/cookie_test.go @@ -1,35 +1,37 @@ package auth import ( - "crypto/rand" - "crypto/rsa" - "crypto/x509" - "encoding/pem" + "encoding/gob" "fmt" "net/http" "net/http/httptest" "strings" "time" - "github.com/gofrs/uuid" - "github.com/pkg/errors" + "github.com/alexedwards/scs/v2" + "github.com/alexedwards/scs/v2/memstore" ) -func createRandomRSAPEM() (s string, err error) { - priv, err := rsa.GenerateKey(rand.Reader, 2048) - if err != nil { - err = errors.Wrap(err, "failed to generate key") - return - } +func (suite *authSuite) SetupTest() { + gob.Register(Session{}) +} - asn1 := x509.MarshalPKCS1PrivateKey(priv) - privBytes := pem.EncodeToMemory(&pem.Block{ - Type: "RSA PRIVATE KEY", - Bytes: asn1, - }) - s = string(privBytes[:]) +func setupSessionManagers() [3]*scs.SessionManager { + var milSession, adminSession, officeSession *scs.SessionManager + store := memstore.New() + milSession = scs.New() + milSession.Store = store + milSession.Cookie.Name = "mil_session_token" + + adminSession = scs.New() + adminSession.Store = store + adminSession.Cookie.Name = "admin_session_token" + + officeSession = scs.New() + officeSession.Store = store + officeSession.Cookie.Name = "office_session_token" - return + return [3]*scs.SessionManager{milSession, adminSession, officeSession} } func getHandlerParamsWithToken(ss string, expiry time.Time) (*httptest.ResponseRecorder, *http.Request) { @@ -40,7 +42,7 @@ func getHandlerParamsWithToken(ss string, expiry time.Time) (*httptest.ResponseR appName, _ := ApplicationName(req.Host, appnames) // Set a secure cookie on the request - cookieName := fmt.Sprintf("%s_%s", strings.ToLower(string(appName)), UserSessionCookieName) + cookieName := fmt.Sprintf("%s_%s", strings.ToLower(string(appName)), "session_token") cookie := http.Cookie{ Name: cookieName, Value: ss, @@ -52,24 +54,21 @@ func getHandlerParamsWithToken(ss string, expiry time.Time) (*httptest.ResponseR } func (suite *authSuite) TestSessionCookieMiddlewareWithBadToken() { - t := suite.T() fakeToken := "some_token" - pem, err := createRandomRSAPEM() - if err != nil { - t.Error("error creating RSA key", err) - } + sessionManagers := setupSessionManagers() + milSession := sessionManagers[0] var resultingSession *Session handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { resultingSession = SessionFromRequestContext(r) }) appnames := ApplicationTestServername() - middleware := SessionCookieMiddleware(suite.logger, pem, false, appnames, false)(handler) + middleware := SessionCookieMiddleware(suite.logger, appnames, sessionManagers)(handler) expiry := GetExpiryTimeFromMinutes(SessionExpiryInMinutes) rr, req := getHandlerParamsWithToken(fakeToken, expiry) - middleware.ServeHTTP(rr, req) + milSession.LoadAndSave(middleware).ServeHTTP(rr, req) // We should be not be redirected since we're not enforcing auth suite.Equal(http.StatusOK, rr.Code, "handler returned wrong status code") @@ -79,142 +78,6 @@ func (suite *authSuite) TestSessionCookieMiddlewareWithBadToken() { suite.Equal("", resultingSession.IDToken, "Expected empty IDToken from bad cookie") } -func (suite *authSuite) TestSessionCookieMiddlewareWithValidToken() { - t := suite.T() - email := "some_email@domain.com" - idToken := "fake_id_token" - fakeUUID, _ := uuid.FromString("39b28c92-0506-4bef-8b57-e39519f42dc2") - - pem, err := createRandomRSAPEM() - if err != nil { - t.Fatal(err) - } - - expiry := GetExpiryTimeFromMinutes(SessionExpiryInMinutes) - incomingSession := Session{ - UserID: fakeUUID, - Email: email, - IDToken: idToken, - } - ss, err := signTokenStringWithUserInfo(expiry, &incomingSession, pem) - if err != nil { - t.Fatal(err) - } - rr, req := getHandlerParamsWithToken(ss, expiry) - - var resultingSession *Session - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - resultingSession = SessionFromRequestContext(r) - }) - appnames := ApplicationTestServername() - middleware := SessionCookieMiddleware(suite.logger, pem, false, appnames, false)(handler) - - middleware.ServeHTTP(rr, req) - - // We should get a 200 OK - suite.Equal(http.StatusOK, rr.Code, "handler returned wrong status code") - - // And there should be an ID token in the request context - suite.NotNil(resultingSession) - suite.Equal(idToken, resultingSession.IDToken, "handler returned wrong id_token") - - // And the cookie should be renewed - setCookies := rr.HeaderMap["Set-Cookie"] - suite.Equal(1, len(setCookies), "expected cookie to be set") -} - -func (suite *authSuite) TestSessionCookieMiddlewareWithExpiredToken() { - t := suite.T() - email := "some_email@domain.com" - idToken := "fake_id_token" - fakeUUID, _ := uuid.FromString("39b28c92-0506-4bef-8b57-e39519f42dc2") - - pem, err := createRandomRSAPEM() - if err != nil { - t.Fatal(err) - } - - expiry := GetExpiryTimeFromMinutes(-1) - incomingSession := Session{ - UserID: fakeUUID, - Email: email, - IDToken: idToken, - } - ss, err := signTokenStringWithUserInfo(expiry, &incomingSession, pem) - if err != nil { - t.Fatal(err) - } - - var resultingSession *Session - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - resultingSession = SessionFromRequestContext(r) - }) - appnames := ApplicationTestServername() - middleware := SessionCookieMiddleware(suite.logger, pem, false, appnames, false)(handler) - - rr, req := getHandlerParamsWithToken(ss, expiry) - - middleware.ServeHTTP(rr, req) - - // We should be not be redirected since we're not enforcing auth - suite.Equal(http.StatusOK, rr.Code, "handler returned wrong status code") - - // And there should be no token passed through - // And there should be no token passed through - suite.NotNil(resultingSession) - suite.Equal("", resultingSession.IDToken, "Expected empty IDToken from expired") - suite.Equal(uuid.Nil, resultingSession.UserID, "Expected no UUID from expired cookie") - - // And the cookie should be set - setCookies := rr.HeaderMap["Set-Cookie"] - suite.Equal(1, len(setCookies), "expected cookie to be set") -} - -func (suite *authSuite) TestSessionCookiePR161162731() { - t := suite.T() - email := "some_email@domain.com" - idToken := "fake_id_token" - fakeUUID, _ := uuid.FromString("39b28c92-0506-4bef-8b57-e39519f42dc2") - - pem, err := createRandomRSAPEM() - if err != nil { - t.Fatal(err) - } - - expiry := GetExpiryTimeFromMinutes(SessionExpiryInMinutes) - incomingSession := Session{ - UserID: fakeUUID, - Email: email, - IDToken: idToken, - } - ss, err := signTokenStringWithUserInfo(expiry, &incomingSession, pem) - if err != nil { - t.Fatal(err) - } - rr, req := getHandlerParamsWithToken(ss, expiry) - - var resultingSession *Session - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - resultingSession = SessionFromRequestContext(r) - WriteSessionCookie(w, resultingSession, "freddy", false, suite.logger, false) - }) - appnames := ApplicationTestServername() - middleware := SessionCookieMiddleware(suite.logger, pem, false, appnames, false)(handler) - - middleware.ServeHTTP(rr, req) - - // We should get a 200 OK - suite.Equal(http.StatusOK, rr.Code, "handler returned wrong status code") - - // And there should be an ID token in the request context - suite.NotNil(resultingSession) - suite.Equal(idToken, resultingSession.IDToken, "handler returned wrong id_token") - - // And the cookie should be renewed - setCookies := rr.HeaderMap["Set-Cookie"] - suite.Equal(1, len(setCookies), "expected cookie to be set") -} - func (suite *authSuite) TestMaskedCSRFMiddleware() { rr := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/", nil) @@ -261,7 +124,9 @@ func (suite *authSuite) TestMaskedCSRFMiddlewareCreatesNewToken() { func (suite *authSuite) TestMiddlewareConstructor() { appnames := ApplicationTestServername() - adm := SessionCookieMiddleware(suite.logger, "secret", false, appnames, false) + sessionManagers := setupSessionManagers() + + adm := SessionCookieMiddleware(suite.logger, appnames, sessionManagers) suite.NotNil(adm) } @@ -276,16 +141,18 @@ func (suite *authSuite) TestMiddlewareMilApp() { suite.False(session.IsAdminApp(), "first should not be admin app") suite.Equal(appnames.MilServername, session.Hostname) }) - milMoveMiddleware := SessionCookieMiddleware(suite.logger, "secret", false, appnames, false)(milMoveTestHandler) + sessionManagers := setupSessionManagers() + milSession := sessionManagers[0] + milMoveMiddleware := SessionCookieMiddleware(suite.logger, appnames, sessionManagers)(milMoveTestHandler) req := httptest.NewRequest("GET", fmt.Sprintf("http://%s/some_url", appnames.MilServername), nil) - milMoveMiddleware.ServeHTTP(rr, req) + milSession.LoadAndSave(milMoveMiddleware).ServeHTTP(rr, req) req, _ = http.NewRequest("GET", fmt.Sprintf("http://%s:8080/some_url", appnames.MilServername), nil) - milMoveMiddleware.ServeHTTP(rr, req) + milSession.LoadAndSave(milMoveMiddleware).ServeHTTP(rr, req) req, _ = http.NewRequest("GET", fmt.Sprintf("http://%s:8080/some_url", strings.ToUpper(appnames.MilServername)), nil) - milMoveMiddleware.ServeHTTP(rr, req) + milSession.LoadAndSave(milMoveMiddleware).ServeHTTP(rr, req) } func (suite *authSuite) TestMiddlwareOfficeApp() { @@ -299,16 +166,18 @@ func (suite *authSuite) TestMiddlwareOfficeApp() { suite.False(session.IsAdminApp(), "should not be admin app") suite.Equal(appnames.OfficeServername, session.Hostname) }) - officeMiddleware := SessionCookieMiddleware(suite.logger, "secret", false, appnames, false)(officeTestHandler) + sessionManagers := setupSessionManagers() + officeSession := sessionManagers[2] + officeMiddleware := SessionCookieMiddleware(suite.logger, appnames, sessionManagers)(officeTestHandler) req := httptest.NewRequest("GET", fmt.Sprintf("http://%s/some_url", appnames.OfficeServername), nil) - officeMiddleware.ServeHTTP(rr, req) + officeSession.LoadAndSave(officeMiddleware).ServeHTTP(rr, req) req, _ = http.NewRequest("GET", fmt.Sprintf("http://%s:8080/some_url", appnames.OfficeServername), nil) - officeMiddleware.ServeHTTP(rr, req) + officeSession.LoadAndSave(officeMiddleware).ServeHTTP(rr, req) req, _ = http.NewRequest("GET", fmt.Sprintf("http://%s:8080/some_url", strings.ToUpper(appnames.OfficeServername)), nil) - officeMiddleware.ServeHTTP(rr, req) + officeSession.LoadAndSave(officeMiddleware).ServeHTTP(rr, req) } func (suite *authSuite) TestMiddlwareAdminApp() { @@ -322,16 +191,18 @@ func (suite *authSuite) TestMiddlwareAdminApp() { suite.True(session.IsAdminApp(), "should be admin app") suite.Equal(AdminTestHost, session.Hostname) }) - adminMiddleware := SessionCookieMiddleware(suite.logger, "secret", false, appnames, false)(adminTestHandler) + sessionManagers := setupSessionManagers() + adminSession := sessionManagers[1] + adminMiddleware := SessionCookieMiddleware(suite.logger, appnames, sessionManagers)(adminTestHandler) req := httptest.NewRequest("GET", fmt.Sprintf("http://%s/some_url", AdminTestHost), nil) - adminMiddleware.ServeHTTP(rr, req) + adminSession.LoadAndSave(adminMiddleware).ServeHTTP(rr, req) req, _ = http.NewRequest("GET", fmt.Sprintf("http://%s:8080/some_url", AdminTestHost), nil) - adminMiddleware.ServeHTTP(rr, req) + adminSession.LoadAndSave(adminMiddleware).ServeHTTP(rr, req) req, _ = http.NewRequest("GET", fmt.Sprintf("http://%s:8080/some_url", strings.ToUpper(AdminTestHost)), nil) - adminMiddleware.ServeHTTP(rr, req) + adminSession.LoadAndSave(adminMiddleware).ServeHTTP(rr, req) } func (suite *authSuite) TestMiddlewareBadApp() { @@ -341,9 +212,11 @@ func (suite *authSuite) TestMiddlewareBadApp() { suite.Fail("Should not be called") }) appnames := ApplicationTestServername() - noAppMiddleware := SessionCookieMiddleware(suite.logger, "secret", false, appnames, false)(noAppTestHandler) + sessionManagers := setupSessionManagers() + milSession := sessionManagers[0] + noAppMiddleware := SessionCookieMiddleware(suite.logger, appnames, sessionManagers)(noAppTestHandler) req := httptest.NewRequest("GET", "http://totally.bogus.hostname/some_url", nil) - noAppMiddleware.ServeHTTP(rr, req) + milSession.LoadAndSave(noAppMiddleware).ServeHTTP(rr, req) suite.Equal(http.StatusBadRequest, rr.Code, "Should get an error ") } diff --git a/pkg/auth/session.go b/pkg/auth/session.go index d55dce911cd..daffc930a2a 100644 --- a/pkg/auth/session.go +++ b/pkg/auth/session.go @@ -2,8 +2,11 @@ package auth import ( "context" + "encoding/gob" "net/http" + "time" + "github.com/alexedwards/scs/v2" "github.com/gofrs/uuid" "github.com/transcom/mymove/pkg/models/roles" @@ -25,6 +28,62 @@ const ( AdminApp Application = "admin" ) +// SetupSessionManagers configures the session manager for each app: mil, admin, +// and office. It's necessary to have separate session managers to allow users +// to be signed in on multiple apps at the same time. +func SetupSessionManagers(redisEnabled bool, sessionStore scs.Store, useSecureCookie bool, idleTimeout time.Duration, lifetime time.Duration) [3]*scs.SessionManager { + if !redisEnabled { + return [3]*scs.SessionManager{} + } + var milSession, adminSession, officeSession *scs.SessionManager + gob.Register(Session{}) + + milSession = scs.New() + milSession.Store = sessionStore + milSession.Cookie.Name = "mil_session_token" + + adminSession = scs.New() + adminSession.Store = sessionStore + adminSession.Cookie.Name = "admin_session_token" + + officeSession = scs.New() + officeSession.Store = sessionStore + officeSession.Cookie.Name = "office_session_token" + + // IdleTimeout controls the maximum length of time a session can be inactive + // before it expires. The default is 15 minutes. To disable idle timeout in + // a non-production environment, set SESSION_IDLE_TIMEOUT_IN_MINUTES to 0. + milSession.IdleTimeout = idleTimeout + adminSession.IdleTimeout = idleTimeout + officeSession.IdleTimeout = idleTimeout + + // Lifetime controls the maximum length of time that a session is valid for + // before it expires. The lifetime is an 'absolute expiry' which is set when + // the session is first created or renewed (such as when a user signs in) + // and does not change. The default value is 24 hours. + milSession.Lifetime = lifetime + adminSession.Lifetime = lifetime + officeSession.Lifetime = lifetime + + milSession.Cookie.Path = "/" + adminSession.Cookie.Path = "/" + officeSession.Cookie.Path = "/" + + // A value of false means the session cookie will be deleted when the + // browser is closed. + milSession.Cookie.Persist = false + adminSession.Cookie.Persist = false + officeSession.Cookie.Persist = false + + if useSecureCookie { + milSession.Cookie.Secure = true + adminSession.Cookie.Secure = true + officeSession.Cookie.Secure = true + } + + return [3]*scs.SessionManager{milSession, adminSession, officeSession} +} + // IsOfficeApp returns true iff the request is for the office.move.mil host func (s *Session) IsOfficeApp() bool { return s.ApplicationName == OfficeApp diff --git a/pkg/auth/session_test.go b/pkg/auth/session_test.go new file mode 100644 index 00000000000..a75558d2673 --- /dev/null +++ b/pkg/auth/session_test.go @@ -0,0 +1,84 @@ +package auth + +import ( + "time" + + "github.com/alexedwards/scs/v2" + "github.com/alexedwards/scs/v2/memstore" +) + +func (suite *authSuite) TestSetupSessionManagers() { + idleTimeout := 15 * time.Minute + lifetime := 24 * time.Hour + useSecureCookie := true + redisEnabled := true + sessionStore := memstore.New() + + sessionManagers := SetupSessionManagers( + redisEnabled, sessionStore, useSecureCookie, idleTimeout, lifetime, + ) + milSession := sessionManagers[0] + adminSession := sessionManagers[1] + officeSession := sessionManagers[2] + + suite.Run("With a supported scs.Store other than redisstore", func() { + sessionStore = memstore.New() + sessionManagers := SetupSessionManagers( + redisEnabled, sessionStore, useSecureCookie, idleTimeout, lifetime, + ) + milSession = sessionManagers[0] + adminSession = sessionManagers[1] + officeSession = sessionManagers[2] + + suite.Equal(sessionStore, milSession.Store) + suite.Equal(sessionStore, adminSession.Store) + suite.Equal(sessionStore, officeSession.Store) + + }) + + suite.Run("With Redis disabled", func() { + redisEnabled := false + + sessionManagers := SetupSessionManagers( + redisEnabled, sessionStore, useSecureCookie, idleTimeout, lifetime, + ) + + suite.Equal([3]*scs.SessionManager{}, sessionManagers) + }) + + suite.Run("Session cookie names must be unique per app", func() { + suite.Equal("mil_session_token", milSession.Cookie.Name) + suite.Equal("admin_session_token", adminSession.Cookie.Name) + suite.Equal("office_session_token", officeSession.Cookie.Name) + }) + + suite.Run("All session managers have the same secure cookie setting", func() { + suite.Equal(useSecureCookie, milSession.Cookie.Secure) + suite.Equal(useSecureCookie, adminSession.Cookie.Secure) + suite.Equal(useSecureCookie, officeSession.Cookie.Secure) + }) + + suite.Run("All session managers have the same idleTimeout", func() { + suite.Equal(idleTimeout, milSession.IdleTimeout) + suite.Equal(idleTimeout, adminSession.IdleTimeout) + suite.Equal(idleTimeout, officeSession.IdleTimeout) + }) + + suite.Run("All session managers have the same lifetime", func() { + suite.Equal(lifetime, milSession.Lifetime) + suite.Equal(lifetime, adminSession.Lifetime) + suite.Equal(lifetime, officeSession.Lifetime) + }) + + suite.Run("All session managers have cookie path set to root", func() { + suite.Equal("/", milSession.Cookie.Path) + suite.Equal("/", adminSession.Cookie.Path) + suite.Equal("/", officeSession.Cookie.Path) + }) + + suite.Run("All session managers do not persist cookie", func() { + suite.Equal(false, milSession.Cookie.Persist) + suite.Equal(false, adminSession.Cookie.Persist) + suite.Equal(false, officeSession.Cookie.Persist) + }) +} diff --git a/pkg/cli/auth.go b/pkg/cli/auth.go index 1962515274e..1f9cc28948d 100644 --- a/pkg/cli/auth.go +++ b/pkg/cli/auth.go @@ -14,9 +14,6 @@ const ( // ClientAuthSecretKeyFlag is the Client Auth Secret Key Flag #nosec G101 ClientAuthSecretKeyFlag string = "client-auth-secret-key" - // NoSessionTimeoutFlag is the No Session Timeout Flag - NoSessionTimeoutFlag string = "no-session-timeout" - // LoginGovCallbackProtocolFlag is the Login.gov Callback Protocol Flag LoginGovCallbackProtocolFlag string = "login-gov-callback-protocol" // LoginGovCallbackPortFlag is the Login.gov Callback Port Flag @@ -45,8 +42,6 @@ func (e *errInvalidClientID) Error() string { func InitAuthFlags(flag *pflag.FlagSet) { flag.String(ClientAuthSecretKeyFlag, "", "Client auth secret JWT key.") - flag.Bool(NoSessionTimeoutFlag, false, "whether user sessions should timeout.") - flag.String(LoginGovCallbackProtocolFlag, "https", "Protocol for non local environments.") flag.Int(LoginGovCallbackPortFlag, 443, "The port for callback urls.") flag.String(LoginGovSecretKeyFlag, "", "Login.gov auth secret JWT key.") diff --git a/pkg/cli/redis.go b/pkg/cli/redis.go new file mode 100644 index 00000000000..350342b43db --- /dev/null +++ b/pkg/cli/redis.go @@ -0,0 +1,187 @@ +package cli + +import ( + "crypto/tls" + "fmt" + "strconv" + "time" + + "github.com/gomodule/redigo/redis" + "github.com/pkg/errors" + "github.com/spf13/pflag" + "github.com/spf13/viper" + "go.uber.org/zap" +) + +const ( + // RedisPasswordFlag is the ENV var for the Redis password + RedisPasswordFlag string = "redis-password" + // RedisHostFlag is the ENV var for the Redis hostname + RedisHostFlag string = "redis-host" + // RedisPortFlag is the ENV var for the Redis port + RedisPortFlag string = "redis-port" + // RedisDBNameFlag is the ENV var for the Redis database name, which + // is represented by a positive integer. Using multiple databases in + // the same Redis instance allows separating concerns. + RedisDBNameFlag string = "redis-db-name" + // RedisConnectTimeoutFlag specifies how long to wait to establish a + // connection to the Redis instance + RedisConnectTimeoutFlag string = "redis-connect-timeout-in-seconds" + // RedisEnabledFlag specifies whether or not we attempt to connect + // to Redis. For example, apps that use mTLS don't need Redis. + RedisEnabledFlag string = "redis-enabled" + // RedisSSLEnabledFlag specifies if SSL mode is enabled for connections + RedisSSLEnabledFlag string = "redis-ssl-enabled" + // RedisMaxIdleFlag specifies the maximum number of idle connections in the pool + RedisMaxIdleFlag string = "redis-max-idle" + // RedisIdleTimeoutFlag Closes connections after this duration + RedisIdleTimeoutFlag string = "redis-idle-timeout" +) + +// InitRedisFlags initializes RedisFlags command line flags +func InitRedisFlags(flag *pflag.FlagSet) { + flag.String(RedisPasswordFlag, "", "Redis password") + flag.String(RedisHostFlag, "localhost", "Redis hostname") + flag.Int(RedisPortFlag, 6379, "Redis port") + flag.Int(RedisDBNameFlag, 0, "Redis database") + flag.Duration(RedisConnectTimeoutFlag, 2*time.Second, "Redis connect timeout in seconds") + flag.Int(RedisMaxIdleFlag, 10, "Redis maximum number of idle connections in the pool") + flag.Duration(RedisIdleTimeoutFlag, 240*time.Second, "Redis idle timeout in seconds") + flag.Bool(RedisEnabledFlag, true, "Whether or not Redis is enabled") + flag.Bool(RedisSSLEnabledFlag, false, "Whether or not Redis SSL is enabled") +} + +// CheckRedis validates Redis command line flags +func CheckRedis(v *viper.Viper) error { + enabled := v.GetBool(RedisEnabledFlag) + if !enabled { + return nil + } + + if err := ValidatePort(v, RedisPortFlag); err != nil { + return err + } + + if err := ValidateHost(v, RedisHostFlag); err != nil { + return err + } + + connectTimeout := v.GetDuration(RedisConnectTimeoutFlag) + if connectTimeout < 1*time.Second || connectTimeout > 5*time.Second { + return errors.Errorf("%s should be between 1 and 5 seconds", RedisConnectTimeoutFlag) + } + + maxIdle := v.GetInt(RedisMaxIdleFlag) + if maxIdle < 1 || maxIdle > 20 { + return errors.Errorf("%s should be between 1 and 20", RedisMaxIdleFlag) + } + + idleTimeout := v.GetDuration(RedisIdleTimeoutFlag) + if idleTimeout < 30*time.Second || idleTimeout > 300*time.Second { + return errors.Errorf("%s should be between 30 and 300 seconds", RedisIdleTimeoutFlag) + } + + return nil +} + +// InitRedis initializes a Redis pool from command line flags. +// v is the viper Configuration. +// logger is the application logger. +func InitRedis(v *viper.Viper, logger Logger) (*redis.Pool, error) { + enabled := v.GetBool(RedisEnabledFlag) + if !enabled { + return nil, nil + } + + redisPassword := v.GetString(RedisPasswordFlag) + redisHost := v.GetString(RedisHostFlag) + redisPort := v.GetInt(RedisPortFlag) + redisDBName := v.GetInt(RedisDBNameFlag) + redisConnectTimeout := v.GetDuration(RedisConnectTimeoutFlag) + redisSSLEnabled := v.GetBool(RedisSSLEnabledFlag) + redisMaxIdle := v.GetInt(RedisMaxIdleFlag) + redisIdleTimeout := v.GetDuration(RedisIdleTimeoutFlag) + + // Log the redis URI + s := "redis://:%s@%s:%d?db=%d" + redisURI := fmt.Sprintf(s, "*****", redisHost, redisPort, redisDBName) + if redisPassword == "" { + s = "redis://%s:%d?db=%d" + redisURI = fmt.Sprintf(s, redisHost, redisPort, redisDBName) + } + logger.Info("Connecting to Redis", zap.String("url", redisURI)) + + // Configure Redis TLS Config + redisTLSConfig := tls.Config{ + MinVersion: tls.VersionTLS12, + } + + // Redis Dial requires a minimal URI containing just the host and port + redisURLTemplate := "%s:%s" + redisURL := fmt.Sprintf(redisURLTemplate, redisHost, strconv.Itoa(redisPort)) + + if testRedisErr := testRedisConnection(redisURL, redisPassword, redisDBName, redisConnectTimeout, redisSSLEnabled, &redisTLSConfig, logger); testRedisErr != nil { + return nil, testRedisErr + } + + pool := &redis.Pool{ + MaxIdle: redisMaxIdle, + IdleTimeout: redisIdleTimeout, + Dial: func() (redis.Conn, error) { + connection, connectionErr := redis.Dial( + "tcp", + redisURL, + redis.DialDatabase(redisDBName), + redis.DialPassword(redisPassword), + redis.DialConnectTimeout(redisConnectTimeout), + redis.DialUseTLS(redisSSLEnabled), + redis.DialTLSConfig(&redisTLSConfig), + ) + if connectionErr != nil { + return nil, connectionErr + } + return connection, nil + }, + } + + return pool, nil +} + +func testRedisConnection(redisURL, redisPassword string, redisDBName int, redisConnectTimeout time.Duration, redisSSLEnabled bool, redisTLSConfig *tls.Config, logger Logger) error { + // Confirm the connection works + logger.Info("Testing Redis connection...") + + redisConnection, redisConnectionErr := redis.Dial( + "tcp", + redisURL, + redis.DialDatabase(redisDBName), + redis.DialPassword(redisPassword), + redis.DialConnectTimeout(redisConnectTimeout), + redis.DialUseTLS(redisSSLEnabled), + redis.DialTLSConfig(redisTLSConfig), + ) + defer redisConnection.Close() + + errorString := fmt.Sprintf("Failed to connect to Redis after %s", redisConnectTimeout) + var finalErrorString string + if redisPassword == "" { + finalErrorString = errorString + ". No password provided." + } + + if redisConnectionErr != nil { + logger.Error(finalErrorString, zap.Error(redisConnectionErr)) + return redisConnectionErr + } + logger.Info("...Redis connection successful!") + + logger.Info("Starting Redis ping...") + _, pingErr := redis.String(redisConnection.Do("PING")) + if pingErr != nil { + logger.Error("failed to ping Redis", zap.Error(pingErr)) + return pingErr + } + + logger.Info("...Redis ping successful!") + + return nil +} diff --git a/pkg/cli/redis_test.go b/pkg/cli/redis_test.go new file mode 100644 index 00000000000..24003128ca6 --- /dev/null +++ b/pkg/cli/redis_test.go @@ -0,0 +1,6 @@ +package cli + +func (suite *cliTestSuite) TestConfigRedis() { + suite.Setup(InitRedisFlags, []string{}) + suite.NoError(CheckRedis(suite.viper)) +} diff --git a/pkg/cli/session.go b/pkg/cli/session.go new file mode 100644 index 00000000000..248bfa84d47 --- /dev/null +++ b/pkg/cli/session.go @@ -0,0 +1,68 @@ +package cli + +import ( + "time" + + "github.com/pkg/errors" + "github.com/spf13/pflag" + "github.com/spf13/viper" +) + +const ( + // SessionIdleTimeoutInMinutesFlag sets the session's Idle Timeout in minutes + SessionIdleTimeoutInMinutesFlag string = "session-idle-timeout-in-minutes" + // SessionLifetimeInHoursFlag sets the session's absolute expiry in hours + SessionLifetimeInHoursFlag string = "session-lifetime-in-hours" + + // SessionIdleTimeoutInMinutes is the default idle timeout in minutes + SessionIdleTimeoutInMinutes int = 15 + // SessionLifetimeInHours is the default session lifetime in hours + SessionLifetimeInHours int = 24 +) + +// InitSessionFlags initializes SessionFlags command line flags +func InitSessionFlags(flag *pflag.FlagSet) { + flag.Duration(SessionIdleTimeoutInMinutesFlag, (time.Duration(SessionIdleTimeoutInMinutes) * time.Minute), "Session idle timeout in minutes") + flag.Duration(SessionLifetimeInHoursFlag, (time.Duration(SessionLifetimeInHours) * time.Hour), "Session absolute expiry in hours") +} + +// CheckSession validates session command line flags +func CheckSession(v *viper.Viper) error { + if err := ValidateSessionTimeout(v, SessionIdleTimeoutInMinutesFlag); err != nil { + return err + } + + if err := ValidateSessionLifetime(v, SessionLifetimeInHoursFlag); err != nil { + return err + } + + return nil +} + +// ValidateSessionTimeout validates session idle timeout +func ValidateSessionTimeout(v *viper.Viper, flagname string) error { + environment := v.GetString(EnvironmentFlag) + timeout := v.GetDuration(flagname) + + if environment == EnvironmentProd && (timeout < 15 || timeout > 60) { + return errors.Errorf("%s must be an integer between 15 and 60", SessionIdleTimeoutInMinutesFlag) + } + + return nil +} + +// ValidateSessionLifetime validates session lifetime +func ValidateSessionLifetime(v *viper.Viper, flagname string) error { + environment := v.GetString(EnvironmentFlag) + lifetime := v.GetDuration(flagname) + + if environment == EnvironmentProd && lifetime < 12 { + return errors.Errorf("%s must be at least 12 hours in production", SessionLifetimeInHoursFlag) + } + + if lifetime < 1 { + return errors.Errorf("%s must be at least 1", SessionLifetimeInHoursFlag) + } + + return nil +} diff --git a/pkg/cli/session_test.go b/pkg/cli/session_test.go new file mode 100644 index 00000000000..7c69cf10930 --- /dev/null +++ b/pkg/cli/session_test.go @@ -0,0 +1,6 @@ +package cli + +func (suite *cliTestSuite) TestConfigSession() { + suite.Setup(InitSessionFlags, []string{}) + suite.NoError(CheckSession(suite.viper)) +} diff --git a/pkg/handlers/contexts.go b/pkg/handlers/contexts.go index 685da85cca4..6b52c3dfbb8 100644 --- a/pkg/handlers/contexts.go +++ b/pkg/handlers/contexts.go @@ -4,6 +4,7 @@ import ( "context" "net/http" + "github.com/alexedwards/scs/v2" "github.com/gobuffalo/pop" "github.com/gofrs/uuid" @@ -36,8 +37,6 @@ type HandlerContext interface { SetPlanner(planner route.Planner) CookieSecret() string SetCookieSecret(secret string) - NoSessionTimeout() bool - SetNoSessionTimeout() IWSPersonLookup() iws.PersonLookup SetIWSPersonLookup(rbs iws.PersonLookup) SendProductionInvoice() bool @@ -57,6 +56,8 @@ type HandlerContext interface { SetDPSAuthParams(params dpsauth.Params) SetTraceID(traceID uuid.UUID) GetTraceID() uuid.UUID + SetSessionManagers(sessionManagers [3]*scs.SessionManager) + SessionManager(session *auth.Session) *scs.SessionManager } // FeatureFlag struct for feature flags @@ -70,7 +71,6 @@ type handlerContext struct { db *pop.Connection logger Logger cookieSecret string - noSessionTimeout bool planner route.Planner storage storage.FileStorer notificationSender notifications.NotificationSender @@ -83,6 +83,7 @@ type handlerContext struct { appNames auth.ApplicationServername featureFlags map[string]bool traceID uuid.UUID + sessionManagers [3]*scs.SessionManager } // NewHandlerContext returns a new handlerContext with its required private fields set. @@ -175,16 +176,6 @@ func (hctx *handlerContext) SetCookieSecret(cookieSecret string) { hctx.cookieSecret = cookieSecret } -// NoSessionTimeout is a flag which, when true, indicates that sessions should not timeout. Used in dev. -func (hctx *handlerContext) NoSessionTimeout() bool { - return hctx.noSessionTimeout -} - -// SetNoSessionTimeout is a simple setter for the noSessionTimeout private Field -func (hctx *handlerContext) SetNoSessionTimeout() { - hctx.noSessionTimeout = true -} - func (hctx *handlerContext) IWSPersonLookup() iws.PersonLookup { return hctx.iwsPersonLookup } @@ -259,3 +250,21 @@ func (hctx *handlerContext) SetTraceID(traceID uuid.UUID) { func (hctx *handlerContext) GetTraceID() uuid.UUID { return hctx.traceID } + +func (hctx *handlerContext) SetSessionManagers(sessionManagers [3]*scs.SessionManager) { + hctx.sessionManagers = sessionManagers +} + +// SessionManager returns the session manager corresponding to the current app. +// A user can be signed in at the same time across multiple apps. +func (hctx *handlerContext) SessionManager(session *auth.Session) *scs.SessionManager { + if session.IsMilApp() { + return hctx.sessionManagers[0] + } else if session.IsAdminApp() { + return hctx.sessionManagers[1] + } else if session.IsOfficeApp() { + return hctx.sessionManagers[2] + } + + return nil +} diff --git a/pkg/handlers/internalapi/service_members.go b/pkg/handlers/internalapi/service_members.go index 5add7efbac9..9cd2386211a 100644 --- a/pkg/handlers/internalapi/service_members.go +++ b/pkg/handlers/internalapi/service_members.go @@ -151,7 +151,8 @@ func (h CreateServiceMemberHandler) Handle(params servicememberop.CreateServiceM // And return serviceMemberPayload := payloadForServiceMemberModel(h.FileStorer(), newServiceMember, h.HandlerContext.GetFeatureFlag(cli.FeatureFlagAccessCode)) responder := servicememberop.NewCreateServiceMemberCreated().WithPayload(serviceMemberPayload) - return handlers.NewCookieUpdateResponder(params.HTTPRequest, h.CookieSecret(), h.NoSessionTimeout(), logger, responder, h.UseSecureCookie()) + sessionManager := h.SessionManager(session) + return handlers.NewCookieUpdateResponder(params.HTTPRequest, logger, responder, sessionManager, session) } // ShowServiceMemberHandler returns a serviceMember for a user and service member ID diff --git a/pkg/handlers/internalapi/service_members_test.go b/pkg/handlers/internalapi/service_members_test.go index 481c3ec6fc5..e4a8173be59 100644 --- a/pkg/handlers/internalapi/service_members_test.go +++ b/pkg/handlers/internalapi/service_members_test.go @@ -85,7 +85,6 @@ func (suite *HandlerSuite) TestSubmitServiceMemberHandlerNoValues() { CreateServiceMemberPayload: &newServiceMemberPayload, HTTPRequest: req, } - handler := CreateServiceMemberHandler{handlers.NewHandlerContext(suite.DB(), suite.TestLogger())} response := handler.Handle(params) diff --git a/pkg/handlers/mocks/HandlerContext.go b/pkg/handlers/mocks/HandlerContext.go index a7f5969651d..4d5da8a1d74 100644 --- a/pkg/handlers/mocks/HandlerContext.go +++ b/pkg/handlers/mocks/HandlerContext.go @@ -23,6 +23,8 @@ import ( route "github.com/transcom/mymove/pkg/route" + scs "github.com/alexedwards/scs/v2" + sequence "github.com/transcom/mymove/pkg/db/sequence" services "github.com/transcom/mymove/pkg/services" @@ -221,20 +223,6 @@ func (_m *HandlerContext) LoggerFromRequest(r *http.Request) handlers.Logger { return r0 } -// NoSessionTimeout provides a mock function with given fields: -func (_m *HandlerContext) NoSessionTimeout() bool { - ret := _m.Called() - - var r0 bool - if rf, ok := ret.Get(0).(func() bool); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(bool) - } - - return r0 -} - // NotificationSender provides a mock function with given fields: func (_m *HandlerContext) NotificationSender() notifications.NotificationSender { ret := _m.Called() @@ -363,6 +351,22 @@ func (_m *HandlerContext) SessionFromRequest(r *http.Request) *auth.Session { return r0 } +// SessionManager provides a mock function with given fields: session +func (_m *HandlerContext) SessionManager(session *auth.Session) *scs.SessionManager { + ret := _m.Called(session) + + var r0 *scs.SessionManager + if rf, ok := ret.Get(0).(func(*auth.Session) *scs.SessionManager); ok { + r0 = rf(session) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*scs.SessionManager) + } + } + + return r0 +} + // SetAppNames provides a mock function with given fields: appNames func (_m *HandlerContext) SetAppNames(appNames auth.ApplicationServername) { _m.Called(appNames) @@ -403,11 +407,6 @@ func (_m *HandlerContext) SetIWSPersonLookup(rbs iws.PersonLookup) { _m.Called(rbs) } -// SetNoSessionTimeout provides a mock function with given fields: -func (_m *HandlerContext) SetNoSessionTimeout() { - _m.Called() -} - // SetNotificationSender provides a mock function with given fields: sender func (_m *HandlerContext) SetNotificationSender(sender notifications.NotificationSender) { _m.Called(sender) @@ -423,6 +422,11 @@ func (_m *HandlerContext) SetSendProductionInvoice(sendProductionInvoice bool) { _m.Called(sendProductionInvoice) } +// SetSessionManagers provides a mock function with given fields: sessionManagers +func (_m *HandlerContext) SetSessionManagers(sessionManagers [3]*scs.SessionManager) { + _m.Called(sessionManagers) +} + // SetTraceID provides a mock function with given fields: traceID func (_m *HandlerContext) SetTraceID(traceID uuid.UUID) { _m.Called(traceID) diff --git a/pkg/handlers/responders.go b/pkg/handlers/responders.go index 4ba7220b538..2bb870b5ef3 100644 --- a/pkg/handlers/responders.go +++ b/pkg/handlers/responders.go @@ -1,8 +1,10 @@ package handlers import ( + "context" "net/http" + "github.com/alexedwards/scs/v2" "github.com/go-openapi/runtime" "github.com/go-openapi/runtime/middleware" @@ -12,28 +14,26 @@ import ( // CookieUpdateResponder wraps a swagger middleware.Responder in code which sets the session_cookie // See: https://github.com/go-swagger/go-swagger/issues/748 type CookieUpdateResponder struct { - session *auth.Session - cookieSecret string - noSessionTimeout bool - logger Logger - Responder middleware.Responder - useSecureCookie bool + session *auth.Session + logger Logger + Responder middleware.Responder + sessionManager *scs.SessionManager + ctx context.Context } // NewCookieUpdateResponder constructs a wrapper for the responder which will update cookies -func NewCookieUpdateResponder(request *http.Request, secret string, noSessionTimeout bool, logger Logger, responder middleware.Responder, useSecureCookie bool) middleware.Responder { +func NewCookieUpdateResponder(request *http.Request, logger Logger, responder middleware.Responder, sessionManager *scs.SessionManager, session *auth.Session) middleware.Responder { return &CookieUpdateResponder{ - session: auth.SessionFromRequestContext(request), - cookieSecret: secret, - noSessionTimeout: noSessionTimeout, - logger: logger, - Responder: responder, - useSecureCookie: useSecureCookie, + session: session, + logger: logger, + Responder: responder, + sessionManager: sessionManager, + ctx: request.Context(), } } // WriteResponse updates the session cookie before writing out the details of the response func (cur *CookieUpdateResponder) WriteResponse(rw http.ResponseWriter, p runtime.Producer) { - auth.WriteSessionCookie(rw, cur.session, cur.cookieSecret, cur.noSessionTimeout, cur.logger, cur.useSecureCookie) + cur.sessionManager.Put(cur.ctx, "session", cur.session) cur.Responder.WriteResponse(rw, p) } diff --git a/scripts/README.md b/scripts/README.md index cf4dc9dd83c..fbcec34e3ee 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -153,6 +153,7 @@ These scripts are primarily used for working with the database | `psql-schema` | Convenience script to dump the schema from the postgres DB | | `psql-test` | Convenience script to drop into testing postgres DB | | `psql-wrapper` | A wrapper around `psql` that sets correct values | +| `redis-dev` | Convenience script to drop into redis-cli | | `update-migrations-manifest` | Update manifest for migrations | | `wait-for-db` | waits for an available database connection, or until a timeout is reached | | `wait-for-db-docker` | waits for an available database connection, or until a timeout is reached using docker | diff --git a/scripts/redis-dev b/scripts/redis-dev new file mode 100755 index 00000000000..30fe669258a --- /dev/null +++ b/scripts/redis-dev @@ -0,0 +1,10 @@ +#! /usr/bin/env bash + +# +# Convenience script to drop into redis-cli +# + +set -eu -o pipefail + +make redis_run +docker exec -it milmove-redis redis-cli