Skip to content

Commit

Permalink
chore: refactor authentication repository - remove obsolete repo errors
Browse files Browse the repository at this point in the history
Signed-off-by: Benjamin Voiturier <benjamin.voiturier@iohk.io>
  • Loading branch information
bvoiturier committed Jun 20, 2024
1 parent 8f57008 commit 056a840
Show file tree
Hide file tree
Showing 3 changed files with 29 additions and 52 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import org.hyperledger.identus.iam.authentication.AuthenticationError
import org.hyperledger.identus.iam.authentication.AuthenticationError.*
import org.hyperledger.identus.shared.crypto.Sha256Hash
import org.hyperledger.identus.shared.models.{WalletAdministrationContext, WalletId}
import zio.{IO, URLayer, ZIO, ZLayer}
import zio.{IO, UIO, URLayer, ZIO, ZLayer}

import java.util.UUID
import scala.language.implicitConversions
Expand All @@ -31,17 +31,6 @@ case class ApiKeyAuthenticatorImpl(
case AuthenticationError.InvalidCredentials(message) if apiKeyConfig.autoProvisioning =>
provisionNewEntity(apiKey)
}
.mapError {
case AuthenticationRepositoryError.StorageError(cause) =>
UnexpectedError("Internal error")
case AuthenticationRepositoryError.UnexpectedError(cause) =>
UnexpectedError("Internal error")
case AuthenticationRepositoryError.ServiceError(message) =>
UnexpectedError("Internal error")
case AuthenticationRepositoryError.AuthenticationCompromised(entityId, amt, secret) =>
InvalidCredentials("API key is compromised")
case e: AuthenticationError => e
}
}
} else {
ZIO.fail(
Expand All @@ -50,7 +39,7 @@ case class ApiKeyAuthenticatorImpl(
}
}

protected[apikey] def provisionNewEntity(apiKey: String): IO[AuthenticationRepositoryError, Entity] = synchronized {
protected[apikey] def provisionNewEntity(apiKey: String): UIO[Entity] = synchronized {
for {
wallet <- walletManagementService
.createWallet(Wallet("Auto provisioned wallet", WalletId.random))
Expand All @@ -59,7 +48,6 @@ case class ApiKeyAuthenticatorImpl(
entityToCreate = Entity(name = "Auto provisioned entity", walletId = wallet.id.toUUID)
entity <- entityService.create(entityToCreate).orDieAsUnmanagedFailure
_ <- add(entity.id, apiKey)
.mapError(are => AuthenticationRepositoryError.ServiceError(are.userFacingMessage))
} yield entity
}

Expand All @@ -76,17 +64,15 @@ case class ApiKeyAuthenticatorImpl(
} yield entity
}

override def add(entityId: UUID, apiKey: String): IO[AuthenticationError, Unit] = {
override def add(entityId: UUID, apiKey: String): UIO[Unit] = {
for {
saltAndApiKey <- ZIO.succeed(apiKeyConfig.salt + apiKey)
secret <- ZIO
.fromTry(Try(Sha256Hash.compute(saltAndApiKey.getBytes).hexEncoded))
.logError("Failed to compute SHA256 hash")
.mapError(cause => AuthenticationError.UnexpectedError(cause.getMessage))
.orDie
_ <- repository
.insert(entityId, AuthenticationMethodType.ApiKey, secret)
.logError(s"Insert operation failed for entityId: $entityId")
.mapError(are => AuthenticationError.UnexpectedError(are.message))
.orDieAsUnmanagedFailure
} yield ()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package org.hyperledger.identus.iam.authentication.apikey
import io.getquill.*
import io.getquill.context.json.PostgresJsonExtensions
import io.getquill.doobie.DoobieContext
import org.hyperledger.identus.iam.authentication.apikey.AuthenticationRepositoryError.AuthenticationCompromised
import org.hyperledger.identus.shared.models.{Failure, StatusCode}
import zio.{IO, *}
import zio.interop.catz.*

Expand Down Expand Up @@ -34,7 +36,7 @@ trait AuthenticationRepository {
entityId: UUID,
amt: AuthenticationMethodType,
secret: String
): zio.IO[AuthenticationRepositoryError, Unit]
): zio.IO[AuthenticationCompromised, Unit]

def findEntityIdByMethodAndSecret(
amt: AuthenticationMethodType,
Expand All @@ -59,31 +61,25 @@ trait AuthenticationRepository {
}

//TODO: reconsider the hierarchy of the service and dal layers
sealed trait AuthenticationRepositoryError {
def message: String
sealed trait AuthenticationRepositoryError(
val statusCode: StatusCode,
val userFacingMessage: String
) extends Failure {
override val namespace: String = "AuthenticationRepositoryError"
}

object AuthenticationRepositoryError {

def hide(secret: String) = secret.take(8) + "****"
private def hide(secret: String) = secret.take(8) + "****"

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
case class StorageError(cause: Throwable) extends AuthenticationRepositoryError {
def message = cause.getMessage
}

case class UnexpectedError(cause: Throwable) extends AuthenticationRepositoryError {
def message = cause.getMessage
}
) extends AuthenticationRepositoryError(
StatusCode.Unauthorized,
s"Authentication method is compromised for entityId:$entityId, type:${authenticationMethodType.value}, and secret:${hide(secret)}"
)
}

object AuthenticationRepositorySql extends DoobieContext.Postgres(SnakeCase) with PostgresJsonExtensions {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package org.hyperledger.identus.iam.authentication.apikey

import doobie.*
import doobie.implicits.*
import org.hyperledger.identus.shared.db.Errors
import org.hyperledger.identus.shared.db.Implicits.ensureOneAffectedRowOrDie
import org.postgresql.util.PSQLException
import zio.*
Expand All @@ -18,37 +19,31 @@ case class JdbcAuthenticationRepository(xa: Transactor[Task]) extends Authentica
entityId: UUID,
amt: AuthenticationMethodType,
secret: String
): IO[AuthenticationRepositoryError, Unit] = {
): IO[AuthenticationCompromised, Unit] = {
val authenticationMethod = AuthenticationMethod(amt, entityId, secret)
AuthenticationRepositorySql
.insert(authenticationMethod)
.transact(xa)
.map(_ => ())
.logError(
s"insert failed for entityId: $entityId, authenticationMethodType: $amt, and secret: $secret"
)
.mapError {
.flatMap {
case 1 => ZIO.unit
case count => ZIO.die(Errors.UnexpectedAffectedRow(count))
}
.catchAll {
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)
ensureThatTheApiKeyIsNotCompromised(entityId, amt, secret)
case e => ZIO.die(e)
}
}

private def ensureThatTheApiKeyIsNotCompromised(
entityId: UUID,
authenticationMethodType: AuthenticationMethodType,
secret: String
): IO[AuthenticationRepositoryError, Unit] = {
): IO[AuthenticationCompromised, Unit] = {
val ac = AuthenticationCompromised(entityId, authenticationMethodType, secret)
val acZIO: IO[AuthenticationRepositoryError, Unit] = ZIO.fail(ac)
val acZIO: IO[AuthenticationCompromised, Unit] = ZIO.fail(ac)

for {
authRecordOpt <- findAuthenticationMethodByTypeAndSecret(authenticationMethodType, secret)
Expand Down

0 comments on commit 056a840

Please sign in to comment.