diff --git a/prism-agent/service/server/src/main/scala/io/iohk/atala/iam/authentication/apikey/ApiKeyAuthenticatorImpl.scala b/prism-agent/service/server/src/main/scala/io/iohk/atala/iam/authentication/apikey/ApiKeyAuthenticatorImpl.scala index d5fb15e61e..68302a3668 100644 --- a/prism-agent/service/server/src/main/scala/io/iohk/atala/iam/authentication/apikey/ApiKeyAuthenticatorImpl.scala +++ b/prism-agent/service/server/src/main/scala/io/iohk/atala/iam/authentication/apikey/ApiKeyAuthenticatorImpl.scala @@ -42,6 +42,8 @@ case class ApiKeyAuthenticatorImpl( UnexpectedError("Internal error") case AuthenticationRepositoryError.ServiceError(message) => UnexpectedError("Internal error") + case AuthenticationRepositoryError.AuthenticationCompromised(entityId, amt, secret) => + InvalidCredentials("API key is compromised") } } } else { @@ -89,11 +91,12 @@ case class ApiKeyAuthenticatorImpl( .mapError(cause => AuthenticationError.UnexpectedError(cause.getMessage)) _ <- repository .insert(entityId, AuthenticationMethodType.ApiKey, secret) + .logError(s"Insert operation failed for entityId: $entityId") .mapError(are => AuthenticationError.UnexpectedError(are.message)) } yield () } - override def delete(entityId: _root_.java.util.UUID, apiKey: String): IO[AuthenticationError, Unit] = { + override def delete(entityId: UUID, apiKey: String): IO[AuthenticationError, Unit] = { for { saltAndApiKey <- ZIO.succeed(apiKeyConfig.salt + apiKey) secret <- ZIO @@ -101,7 +104,7 @@ case class ApiKeyAuthenticatorImpl( .logError("Failed to compute SHA256 hash") .mapError(cause => AuthenticationError.UnexpectedError(cause.getMessage)) _ <- repository - .deleteByEntityIdAndSecret(entityId, secret) + .delete(entityId, AuthenticationMethodType.ApiKey, secret) .mapError(are => AuthenticationError.UnexpectedError(are.message)) } yield () } diff --git a/prism-agent/service/server/src/main/scala/io/iohk/atala/iam/authentication/apikey/AuthenticationRepository.scala b/prism-agent/service/server/src/main/scala/io/iohk/atala/iam/authentication/apikey/AuthenticationRepository.scala index 5c137a5686..d52d27ab1b 100644 --- a/prism-agent/service/server/src/main/scala/io/iohk/atala/iam/authentication/apikey/AuthenticationRepository.scala +++ b/prism-agent/service/server/src/main/scala/io/iohk/atala/iam/authentication/apikey/AuthenticationRepository.scala @@ -7,52 +7,79 @@ import zio.IO import zio.* import zio.interop.catz.* +import java.time.OffsetDateTime import java.util.UUID enum AuthenticationMethodType(val value: String) { case ApiKey extends AuthenticationMethodType("apikey") } +object AuthenticationMethodType { + def fromString(value: String) = { + AuthenticationMethodType.values.find(_.value == value).get + } +} + case class AuthenticationMethod( - id: UUID, `type`: AuthenticationMethodType, entityId: UUID, - secret: String -) + secret: String, + createdAt: OffsetDateTime = OffsetDateTime.now(), + deletedAt: Option[OffsetDateTime] = None +) { + def isDeleted = deletedAt.isDefined +} trait AuthenticationRepository { def insert( entityId: UUID, - authenticationMethod: AuthenticationMethodType, + amt: AuthenticationMethodType, secret: String - ): zio.IO[AuthenticationRepositoryError, UUID] + ): zio.IO[AuthenticationRepositoryError, Unit] def getEntityIdByMethodAndSecret( - method: AuthenticationMethodType, + amt: AuthenticationMethodType, secret: String ): zio.IO[AuthenticationRepositoryError, UUID] - def deleteById(id: UUID): zio.IO[AuthenticationRepositoryError, Unit] + def findAuthenticationMethodByTypeAndSecret( + amt: AuthenticationMethodType, + secret: String + ): zio.IO[AuthenticationRepositoryError, Option[AuthenticationMethod]] def deleteByMethodAndEntityId( - method: AuthenticationMethodType, - entityId: UUID + entityId: UUID, + amt: AuthenticationMethodType ): zio.IO[AuthenticationRepositoryError, Unit] - def deleteByEntityIdAndSecret(id: UUID, secret: String): zio.IO[AuthenticationRepositoryError, Unit] + def delete( + entityId: UUID, + amt: AuthenticationMethodType, + secret: String + ): zio.IO[AuthenticationRepositoryError, Unit] } -type AuthenticationMethodConfiguration = zio.json.ast.Json - +//TODO: reconsider the hierarchy of the service and dal layers sealed trait AuthenticationRepositoryError { def message: String } object AuthenticationRepositoryError { + + def hide(secret: String) = secret.take(8) + "****" case class AuthenticationNotFound(authenticationMethodType: AuthenticationMethodType, secret: String) extends AuthenticationRepositoryError { def message = - s"Authentication method not found for type ${authenticationMethodType.value} and secret $secret" + s"Authentication method not found for type:${authenticationMethodType.value} and secret:${hide(secret)}" + } + + case class AuthenticationCompromised( + entityId: UUID, + authenticationMethodType: AuthenticationMethodType, + secret: String + ) extends AuthenticationRepositoryError { + def message = + s"Authentication method is compromised for entityId:$entityId, type:${authenticationMethodType.value}, and secret:${hide(secret)}" } case class ServiceError(message: String) extends AuthenticationRepositoryError @@ -71,52 +98,61 @@ object AuthenticationRepositorySql extends DoobieContext.Postgres(SnakeCase) wit MappedEncoding[AuthenticationMethodType, String](_.value) implicit val string2AuthenticationMethodType: MappedEncoding[String, AuthenticationMethodType] = - MappedEncoding[String, AuthenticationMethodType](str => AuthenticationMethodType.valueOf(str)) + MappedEncoding[String, AuthenticationMethodType](AuthenticationMethodType.fromString) def insert(authenticationMethod: AuthenticationMethod) = { run { quote { - query[AuthenticationMethod].insertValue(lift(authenticationMethod)).returning(_.id) + query[AuthenticationMethod].insertValue(lift(authenticationMethod)) } } } - def getEntityIdByMethodAndSecret(method: AuthenticationMethodType, secret: String) = { + def getEntityIdByMethodAndSecret(amt: AuthenticationMethodType, secret: String) = { run { quote { query[AuthenticationMethod] - .filter(am => am.secret == lift(secret) && am.`type` == lift(method)) + .filter(am => am.secret == lift(secret) && am.`type` == lift(amt) && am.deletedAt.isEmpty) .map(_.entityId) .take(1) } } } - def deleteById(id: UUID) = { + def filterByTypeAndSecret(amt: AuthenticationMethodType, secret: String) = { run { quote { - query[AuthenticationMethod].filter(_.id == lift(id)).delete + query[AuthenticationMethod] + .filter(am => am.secret == lift(secret) && am.`type` == lift(amt)) } } } - def deleteByMethodAndEntityId(method: AuthenticationMethodType, entityId: UUID) = { + def softDeleteByEntityIdAndType( + entityId: UUID, + amt: AuthenticationMethodType, + deletedAt: Option[OffsetDateTime] + ) = { run { quote { - query[AuthenticationMethod].filter(am => am.`type` == lift(method) && am.entityId == lift(entityId)).delete + query[AuthenticationMethod] + .filter(am => am.`type` == lift(amt) && am.entityId == lift(entityId)) + .update(_.deletedAt -> lift(deletedAt)) } } } - def deleteByEntityIdAndSecret(entityId: UUID, secret: String) = { + def softDeleteBy( + entityId: UUID, + amt: AuthenticationMethodType, + secret: String, + deletedAt: Option[OffsetDateTime] + ) = { run { quote { query[AuthenticationMethod] - .filter(am => - am.entityId == lift(entityId) && - am.secret == lift(secret) - ) - .delete + .filter(am => am.entityId == lift(entityId) && am.`type` == lift(amt) && am.secret == lift(secret)) + .update(_.deletedAt -> lift(deletedAt)) } } } diff --git a/prism-agent/service/server/src/main/scala/io/iohk/atala/iam/authentication/apikey/JdbcAuthenticationRepository.scala b/prism-agent/service/server/src/main/scala/io/iohk/atala/iam/authentication/apikey/JdbcAuthenticationRepository.scala index 7cfde00af1..e96cfc8d58 100644 --- a/prism-agent/service/server/src/main/scala/io/iohk/atala/iam/authentication/apikey/JdbcAuthenticationRepository.scala +++ b/prism-agent/service/server/src/main/scala/io/iohk/atala/iam/authentication/apikey/JdbcAuthenticationRepository.scala @@ -2,74 +2,134 @@ package io.iohk.atala.iam.authentication.apikey import doobie.* import doobie.implicits.* +import org.postgresql.util.PSQLException import zio.* import zio.interop.catz.* +import java.time.OffsetDateTime import java.util.UUID case class JdbcAuthenticationRepository(xa: Transactor[Task]) extends AuthenticationRepository { import AuthenticationRepositorySql.* + import AuthenticationRepositoryError.* override def insert( entityId: UUID, - authenticationMethodType: AuthenticationMethodType, + amt: AuthenticationMethodType, secret: String - ): IO[AuthenticationRepositoryError, UUID] = { - val authenticationMethod = AuthenticationMethod(UUID.randomUUID(), authenticationMethodType, entityId, secret) + ): IO[AuthenticationRepositoryError, Unit] = { + val authenticationMethod = AuthenticationMethod(amt, entityId, secret) AuthenticationRepositorySql .insert(authenticationMethod) .transact(xa) + .map(_ => ()) .logError( - s"insert failed for entityId: $entityId, authenticationMethod: $authenticationMethod, and secret: $secret" + s"insert failed for entityId: $entityId, authenticationMethodType: $amt, and secret: $secret" ) - .mapError(AuthenticationRepositoryError.StorageError.apply) + .mapError { + case sqlException: PSQLException + if sqlException.getMessage + .contains("ERROR: duplicate key value violates unique constraint \"unique_type_secret_constraint\"") => + AuthenticationCompromised(entityId, amt, secret) + case otherSqlException: PSQLException => + StorageError(otherSqlException) + case unexpected: Throwable => + UnexpectedError(unexpected) + } + .catchSome { case AuthenticationCompromised(eId, amt, s) => + ensureThatTheApiKeyIsNotCompromised(eId, amt, s) + } + } + + private def ensureThatTheApiKeyIsNotCompromised( + entityId: UUID, + authenticationMethodType: AuthenticationMethodType, + secret: String + ): IO[AuthenticationRepositoryError, Unit] = { + val ac = AuthenticationCompromised(entityId, authenticationMethodType, secret) + val acZIO: IO[AuthenticationRepositoryError, Unit] = ZIO.fail(ac) + + for { + authRecordOpt <- findAuthenticationMethodByTypeAndSecret(authenticationMethodType, secret) + authRecord <- ZIO.fromOption(authRecordOpt).mapError(_ => ac) + compromisedEntityId = authRecord.entityId + isTheSameEntityId = authRecord.entityId == entityId + isNotDeleted = authRecord.deletedAt.isEmpty + result <- + if (isTheSameEntityId && isNotDeleted) + ZIO.unit + else if (isNotDeleted) + delete(compromisedEntityId, authenticationMethodType, secret) *> acZIO + else + acZIO + } yield result } override def getEntityIdByMethodAndSecret( - method: AuthenticationMethodType, + amt: AuthenticationMethodType, secret: String ): IO[AuthenticationRepositoryError, UUID] = { AuthenticationRepositorySql - .getEntityIdByMethodAndSecret(method, secret) + .getEntityIdByMethodAndSecret(amt, secret) .transact(xa) - .logError(s"getEntityIdByMethodAndSecret failed for method: $method and secret: $secret") + .logError(s"getEntityIdByMethodAndSecret failed for method: $amt and secret: $secret") .mapError(AuthenticationRepositoryError.StorageError.apply) .flatMap( - _.headOption.fold(ZIO.fail(AuthenticationRepositoryError.AuthenticationNotFound(method, secret)))(entityId => + _.headOption.fold(ZIO.fail(AuthenticationRepositoryError.AuthenticationNotFound(amt, secret)))(entityId => ZIO.succeed(entityId) ) ) } - override def deleteById(id: UUID): IO[AuthenticationRepositoryError, Unit] = { + override def findAuthenticationMethodByTypeAndSecret( + amt: AuthenticationMethodType, + secret: String + ): IO[AuthenticationRepositoryError, Option[AuthenticationMethod]] = { AuthenticationRepositorySql - .deleteById(id) + .filterByTypeAndSecret(amt, secret) .transact(xa) - .logError(s"deleteById failed for id: $id") + .logError(s"findAuthenticationMethodBySecret failed for secret:$secret") + .map(_.headOption) .mapError(AuthenticationRepositoryError.StorageError.apply) - .map(_ => ()) } override def deleteByMethodAndEntityId( - method: AuthenticationMethodType, - entityId: UUID + entityId: UUID, + amt: AuthenticationMethodType ): IO[AuthenticationRepositoryError, Unit] = { AuthenticationRepositorySql - .deleteByMethodAndEntityId(method, entityId) + .softDeleteByEntityIdAndType(entityId, amt, Some(OffsetDateTime.now())) .transact(xa) - .logError(s"deleteByMethodAndEntityId failed for method: $method and entityId: $entityId") + .logError(s"deleteByMethodAndEntityId failed for method: $amt and entityId: $entityId") .mapError(AuthenticationRepositoryError.StorageError.apply) .map(_ => ()) } - override def deleteByEntityIdAndSecret(id: UUID, secret: String): IO[AuthenticationRepositoryError, Unit] = { + override def delete( + entityId: UUID, + amt: AuthenticationMethodType, + secret: String + ): IO[AuthenticationRepositoryError, Unit] = { AuthenticationRepositorySql - .deleteByEntityIdAndSecret(id, secret) + .softDeleteBy(entityId, amt, secret, Some(OffsetDateTime.now())) .transact(xa) - .logError(s"deleteByEntityIdAndSecret failed for id: $id and secret: $secret") + .logError(s"deleteByEntityIdAndSecret failed for id: $entityId and secret: $secret") .mapError(AuthenticationRepositoryError.StorageError.apply) .map(_ => ()) } + + def checkDeleted(method: AuthenticationMethodType, secret: String) = { + AuthenticationRepositorySql + .getEntityIdByMethodAndSecret(method, secret) + .transact(xa) + .logError(s"getEntityIdByMethodAndSecret failed for method: $method and secret: $secret") + .mapError(AuthenticationRepositoryError.StorageError.apply) + .flatMap( + _.headOption.fold(ZIO.fail(AuthenticationRepositoryError.AuthenticationNotFound(method, secret)))(entityId => + ZIO.succeed(entityId) + ) + ) + } } object JdbcAuthenticationRepository { diff --git a/prism-agent/service/server/src/main/scala/io/iohk/atala/iam/entity/http/model/ApiKeyAuthenticationRequest.scala b/prism-agent/service/server/src/main/scala/io/iohk/atala/iam/entity/http/model/ApiKeyAuthenticationRequest.scala index e22bbe0e99..7be26f004a 100644 --- a/prism-agent/service/server/src/main/scala/io/iohk/atala/iam/entity/http/model/ApiKeyAuthenticationRequest.scala +++ b/prism-agent/service/server/src/main/scala/io/iohk/atala/iam/entity/http/model/ApiKeyAuthenticationRequest.scala @@ -2,7 +2,7 @@ package io.iohk.atala.iam.entity.http.model import io.iohk.atala.api.http.Annotation import io.iohk.atala.iam.entity.http.model.ApiKeyAuthenticationRequest.annotations -import sttp.tapir.Schema +import sttp.tapir.{Schema, Validator} import sttp.tapir.Schema.annotations.{description, encodedExample, validate, validateEach} import sttp.tapir.Validator.* import zio.json.{DeriveJsonDecoder, DeriveJsonEncoder, JsonDecoder, JsonEncoder} @@ -15,7 +15,7 @@ case class ApiKeyAuthenticationRequest( entityId: UUID, @description(annotations.apikey.description) @encodedExample(annotations.apikey.example) - @validate(nonEmptyString) + @validate(all(minLength(16), maxLength(128))) apiKey: String ) diff --git a/prism-agent/service/server/src/test/scala/io/iohk/atala/iam/authentication/apikey/ApiKeyAuthenticatorSpec.scala b/prism-agent/service/server/src/test/scala/io/iohk/atala/iam/authentication/apikey/ApiKeyAuthenticatorSpec.scala index 145bf8a1b7..c566c9db6b 100644 --- a/prism-agent/service/server/src/test/scala/io/iohk/atala/iam/authentication/apikey/ApiKeyAuthenticatorSpec.scala +++ b/prism-agent/service/server/src/test/scala/io/iohk/atala/iam/authentication/apikey/ApiKeyAuthenticatorSpec.scala @@ -15,6 +15,7 @@ import io.iohk.atala.iam.authentication.AuthenticationError import io.iohk.atala.iam.authentication.AuthenticationError.InvalidCredentials import io.iohk.atala.shared.models.WalletId import io.iohk.atala.shared.test.containers.PostgresTestContainerSupport +import zio.Runtime.removeDefaultLoggers import zio.test.Assertion.* import zio.test.TestAspect.sequential import zio.test.{Spec, TestEnvironment, ZIOSpecDefault, assert, *} @@ -67,7 +68,9 @@ object ApiKeyAuthenticatorSpec extends ZIOSpecDefault, PostgresTestContainerSupp paths = "classpath:sql/agent" ) - testSuite.provideSomeLayerShared(testEnvironmentLayer) + testSuite + .provideSomeLayerShared(testEnvironmentLayer) + .provide(removeDefaultLoggers) } val failWhenTheHeaderIsAnEmptyStringTest = test("should fail when the header is empty string")( diff --git a/prism-agent/service/server/src/test/scala/io/iohk/atala/iam/authentication/apikey/JdbcAuthenticationRepositorySpec.scala b/prism-agent/service/server/src/test/scala/io/iohk/atala/iam/authentication/apikey/JdbcAuthenticationRepositorySpec.scala index 778ddf8a3b..d697683b68 100644 --- a/prism-agent/service/server/src/test/scala/io/iohk/atala/iam/authentication/apikey/JdbcAuthenticationRepositorySpec.scala +++ b/prism-agent/service/server/src/test/scala/io/iohk/atala/iam/authentication/apikey/JdbcAuthenticationRepositorySpec.scala @@ -2,15 +2,20 @@ package io.iohk.atala.iam.authentication.apikey import io.iohk.atala.shared.test.containers.PostgresTestContainerSupport import zio.test.{TestAspect, ZIOSpecDefault} - import zio.ZIO import zio.test.* import zio.test.TestAspect.* import zio.test.Assertion.* import io.iohk.atala.container.util.MigrationAspects.migrate +import io.iohk.atala.iam.authentication.apikey.AuthenticationMethodType.ApiKey +import zio.Runtime.removeDefaultLoggers object JdbcAuthenticationRepositorySpec extends ZIOSpecDefault, PostgresTestContainerSupport { + val entityIdGen = Gen.uuid + val secretGen = Gen.alphaNumericStringBounded(256, 256) + val entityIdAndSecretGen = entityIdGen <*> secretGen + override def spec = { val testSuite = suite("JdbcAuthenticationRepositorySpec")( @@ -20,23 +25,68 @@ object JdbcAuthenticationRepositorySpec extends ZIOSpecDefault, PostgresTestCont paths = "classpath:sql/agent" ) - testSuite.provideSomeLayerShared( - pgContainerLayer >+> systemTransactorLayer >+> JdbcAuthenticationRepository.layer - ) + testSuite + .provideSomeLayerShared( + pgContainerLayer >+> systemTransactorLayer >+> JdbcAuthenticationRepository.layer + ) + .provide(removeDefaultLoggers) } private val crudSpec = suite("CRUD operations on the AuthenticationRepository")( test("create, read, update, delete") { - check(Gen.uuid <*> Gen.alphaNumericStringBounded(256, 256)) { case (entityId, secret) => + check(entityIdAndSecretGen) { case (entityId, secret) => for { repository <- ZIO.service[AuthenticationRepository] recordId <- repository.insert(entityId, AuthenticationMethodType.ApiKey, secret) fetchedEntityId <- repository.getEntityIdByMethodAndSecret(AuthenticationMethodType.ApiKey, secret) - _ <- repository.deleteByMethodAndEntityId(AuthenticationMethodType.ApiKey, entityId) + _ <- repository.deleteByMethodAndEntityId(entityId, AuthenticationMethodType.ApiKey) notFoundEntityId <- repository.getEntityIdByMethodAndSecret(AuthenticationMethodType.ApiKey, secret).flip } yield assert(entityId)(equalTo(fetchedEntityId)) && assert(notFoundEntityId)(isSubtype[AuthenticationRepositoryError.AuthenticationNotFound](anything)) } + }, + test("insert a similar secret for a different tenant must fail") { + check(entityIdAndSecretGen <*> entityIdGen) { case (entityId, secret, anotherEntityId) => + for { + repository <- ZIO.service[AuthenticationRepository] + _ <- repository.insert(entityId, AuthenticationMethodType.ApiKey, secret) + insertSameSecret <- repository.insert(anotherEntityId, AuthenticationMethodType.ApiKey, secret).flip + authenticationMethod <- repository + .findAuthenticationMethodByTypeAndSecret(AuthenticationMethodType.ApiKey, secret) + } yield assert(insertSameSecret)( + isSubtype[AuthenticationRepositoryError.AuthenticationCompromised](anything) + ) && + assert(authenticationMethod)(isSome(anything)) && + assert(authenticationMethod.flatMap(_.deletedAt))(isSome(anything)) + } + }, + test("insert a similar secret for the same tenant must succeed") { + check(entityIdAndSecretGen) { case (entityId, secret) => + for { + repository <- ZIO.service[AuthenticationRepository] + _ <- repository.insert(entityId, AuthenticationMethodType.ApiKey, secret) + _ <- repository.insert(entityId, AuthenticationMethodType.ApiKey, secret) + authenticationMethod <- repository + .findAuthenticationMethodByTypeAndSecret(AuthenticationMethodType.ApiKey, secret) + } yield assert(authenticationMethod)(isSome(anything)) && + assert(authenticationMethod.flatMap(_.deletedAt))(isNone) + } + }, + test("insert a similar secret after deletion for the same tenant must fail") { + check(entityIdAndSecretGen) { case (entityId, secret) => + for { + repository <- ZIO.service[AuthenticationRepository] + _ <- repository.insert(entityId, ApiKey, secret) + _ <- repository.delete(entityId, ApiKey, secret) + insertSameSecret <- repository.insert(entityId, ApiKey, secret).flip + authenticationMethod <- repository + .findAuthenticationMethodByTypeAndSecret(ApiKey, secret) + } yield assert(insertSameSecret)( + isSubtype[AuthenticationRepositoryError.AuthenticationCompromised](anything) + ) && + assert(authenticationMethod)(isSome(anything)) && + assert(authenticationMethod.flatMap(_.deletedAt))(isSome(anything)) + } } - ) @@ samples(100) @@ nondeterministic + ) @@ samples(10) @@ nondeterministic } diff --git a/prism-agent/service/wallet-api/src/main/resources/sql/agent/V13__apikey_authentication_improvements.sql b/prism-agent/service/wallet-api/src/main/resources/sql/agent/V13__apikey_authentication_improvements.sql new file mode 100644 index 0000000000..96b494f653 --- /dev/null +++ b/prism-agent/service/wallet-api/src/main/resources/sql/agent/V13__apikey_authentication_improvements.sql @@ -0,0 +1,11 @@ +ALTER TABLE public.authentication_method + DROP COLUMN id; + +ALTER TABLE public.authentication_method + ADD COLUMN created_at TIMESTAMPTZ NOT NULL DEFAULT now(); + +ALTER TABLE public.authentication_method + ADD COLUMN deleted_at TIMESTAMPTZ DEFAULT NULL; + +ALTER TABLE public.authentication_method + ADD CONSTRAINT unique_type_secret_constraint UNIQUE (type, secret); \ No newline at end of file diff --git a/tests/e2e-tests/src/test/kotlin/common/Environments.kt b/tests/e2e-tests/src/test/kotlin/common/Environments.kt index a902394086..eebbe6db36 100644 --- a/tests/e2e-tests/src/test/kotlin/common/Environments.kt +++ b/tests/e2e-tests/src/test/kotlin/common/Environments.kt @@ -3,7 +3,7 @@ package common object Environments { val AGENT_AUTH_REQUIRED: Boolean = (System.getenv("AGENT_AUTH_REQUIRED") ?: "true").toBoolean() val AGENT_AUTH_HEADER = System.getenv("AGENT_AUTH_HEADER") ?: "apikey" - val ACME_AUTH_KEY = System.getenv("ACME_AUTH_KEY") ?: "ACME_AUTH_KEY" + val ACME_AUTH_KEY = System.getenv("ACME_AUTH_KEY") ?: "SECURE_ACME_AUTH_KEY_GREATER_16_SYMBOLS" val ACME_AGENT_URL = System.getenv("ACME_AGENT_URL") ?: "http://localhost:8080/prism-agent" val ACME_AGENT_WEBHOOK_HOST = System.getenv("ACME_AGENT_WEBHOOK_HOST") ?: "host.docker.internal" val ACME_AGENT_WEBHOOK_PORT = (System.getenv("ACME_AGENT_WEBHOOK_PORT") ?: "9955").toInt() @@ -14,7 +14,7 @@ object Environments { val BOB_AGENT_WEBHOOK_PORT = (System.getenv("BOB_AGENT_WEBHOOK_PORT") ?: "9956").toInt() val BOB_AGENT_WEBHOOK_URL = "http://$BOB_AGENT_WEBHOOK_HOST:$BOB_AGENT_WEBHOOK_PORT" val FABER_AGENT_URL = System.getenv("FABER_AGENT_URL") ?: "http://localhost:8080/prism-agent" - val FABER_AUTH_KEY = System.getenv("FABER_AUTH_KEY") ?: "FABER_AUTH_KEY" + val FABER_AUTH_KEY = System.getenv("FABER_AUTH_KEY") ?: "SECURE_FABER_AUTH_KEY_GREATER_16_SYMBOLS" val FABER_AGENT_WEBHOOK_HOST = System.getenv("FABER_AGENT_WEBHOOK_HOST") ?: "host.docker.internal" val FABER_AGENT_WEBHOOK_PORT = (System.getenv("FABER_AGENT_WEBHOOK_PORT") ?: "9957").toInt() val FABER_AGENT_WEBHOOK_URL = "http://$FABER_AGENT_WEBHOOK_HOST:$FABER_AGENT_WEBHOOK_PORT" diff --git a/tests/e2e-tests/src/test/kotlin/features/multitenancy/EntitySteps.kt b/tests/e2e-tests/src/test/kotlin/features/multitenancy/EntitySteps.kt index f2bc915428..3e45e7d4f3 100644 --- a/tests/e2e-tests/src/test/kotlin/features/multitenancy/EntitySteps.kt +++ b/tests/e2e-tests/src/test/kotlin/features/multitenancy/EntitySteps.kt @@ -2,9 +2,12 @@ package features.multitenancy import api_models.CreateEntityRequest import api_models.AddApiKeyRequest +import common.Ensure import common.Utils import interactions.Post +import net.serenitybdd.rest.SerenityRest import net.serenitybdd.screenplay.Actor +import org.apache.http.HttpStatus.SC_CREATED import java.util.* class EntitySteps { @@ -26,6 +29,9 @@ class EntitySteps { ) }, ) + actor.attemptsTo( + Ensure.that(SerenityRest.lastResponse().statusCode).isEqualTo(SC_CREATED) + ) return Utils.lastResponseObject("id", String::class) } @@ -41,5 +47,8 @@ class EntitySteps { ) }, ) + actor.attemptsTo( + Ensure.that(SerenityRest.lastResponse().statusCode).isEqualTo(SC_CREATED) + ) } }