membership-attribute-service/app/services/IdentityAuthService.scala (68 lines of code) (raw):

package services import _root_.play.api.mvc.RequestHeader import cats.effect.IO import cats.implicits._ import com.gu.identity.auth._ import com.gu.identity.play.IdentityPlayAuthService import com.gu.identity.play.IdentityPlayAuthService.UserCredentialsMissingError import com.gu.monitoring.SafeLogging import models.{ApiError, ApiErrors, UserFromToken} import services.AuthenticationFailure.{Forbidden, Unauthorised} import scala.concurrent.{ExecutionContext, Future} class IdentityAuthService(identityPlayAuthService: IdentityPlayAuthService)(implicit ec: ExecutionContext) extends AuthenticationService with SafeLogging { def user(requiredScopes: List[AccessScope])(implicit requestHeader: RequestHeader): Future[Either[AuthenticationFailure, UserFromToken]] = { getUser(requestHeader, requiredScopes).attempt .map { case Left(UserCredentialsMissingError(_)) => // IdentityPlayAuthService throws an error if there is no SC_GU_U cookie or crypto auth token // frontend decides to make a request based on the existence of a GU_U cookie, so this case is expected. Left(Unauthorised) case Left(OktaValidationException(validationError: ValidationError)) => validationError match { case MissingRequiredScope(_) => logger.warnNoPrefix(s"could not validate okta token - $validationError") Left(Forbidden) case OktaValidationError(originalException) => logger.warnNoPrefix( s"could not validate okta token - $validationError. Path: ${requestHeader.path}. User-Agent: ${requestHeader.headers.get("User-Agent")}", originalException, ) Left(Unauthorised) case _ => logger.warnNoPrefix(s"could not validate okta token - $validationError") Left(Unauthorised) } case Left(err) => logger.warnNoPrefix(s"valid request but expired token or cookie so user must log in again - $err") Left(Unauthorised) case Right(Some(user)) => Right(user) case Right(None) => Left(Unauthorised) } } /** If given request has valid credentials, returns a [[UserAndCredentials]]. This tells us the access claims and their source. Otherwise it returns * an [[ApiError]]. * * @param requestHeader * Request to extract claims from. * @param requiredScopes * Permissions that token needs to access an endpoint, if it's an Okta token. This is ignored for Idapi credentials. */ def userAndCredentials(requestHeader: RequestHeader, requiredScopes: List[AccessScope]): Future[Either[ApiError, UserAndCredentials]] = identityPlayAuthService .validateCredentialsFromRequest[UserFromToken](requestHeader, requiredScopes) .attempt .flatMap { // Request has Okta token but it's invalid case Left(OktaValidationException(error)) => IO.pure(Left(ApiError(message = error.message, details = "", statusCode = error.suggestedHttpResponseCode))) // Request has invalid Idapi credentials case Left(UserCredentialsMissingError(_)) => IO.pure(Left(ApiErrors.unauthorized)) // Something unexpected case Left(other) => IO.raiseError(other) // Request has valid Okta token case Right((credentials: OktaUserCredentials, user)) => IO.pure(Right(UserAndCredentials(user, credentials))) // Request has valid Idapi credentials case Right((credentials: IdapiUserCredentials, user)) => IO.pure(Right(UserAndCredentials(user, credentials))) } .unsafeToFuture() private def getUser(requestHeader: RequestHeader, requiredScopes: List[AccessScope]): Future[Option[UserFromToken]] = identityPlayAuthService .validateCredentialsFromRequest[UserFromToken](requestHeader, requiredScopes) .map { case (_: OktaUserCredentials, claims) => Some(claims) case (_: IdapiUserCredentials, claims) => import claims.logPrefix logger.warn("Authorised by Idapi token") Some(claims) } .unsafeToFuture() } /** A set of access claims and the source of the claims. */ case class UserAndCredentials(user: UserFromToken, credentials: UserCredentials)