All Downloads are FREE. Search and download functionalities are using the official Maven repository.

sttp.tapir.TapirAuth.scala Maven / Gradle / Ivy

There is a newer version: 1.11.11
Show newest version
package sttp.tapir

import sttp.model.HeaderNames
import sttp.model.headers.{AuthenticationScheme, WWWAuthenticateChallenge}
import sttp.tapir.CodecFormat.TextPlain
import sttp.tapir.EndpointInput.Auth

import scala.collection.immutable.ListMap

object TapirAuth {

  /** Reads authorization data from the given `input`. */
  def apiKey[T](
      input: EndpointInput.Single[T],
      challenge: WWWAuthenticateChallenge = WWWAuthenticateChallenge("ApiKey")
  ): EndpointInput.Auth[T, EndpointInput.AuthType.ApiKey] =
    EndpointInput.Auth(input, challenge, EndpointInput.AuthType.ApiKey(), EndpointInput.AuthInfo.Empty)

  /** Reads authorization data from the `Authorization` headers starting with `Basic `, removing the prefix. To parse the data as a
    * base64-encoded username/password combination, use: `basic[UsernamePassword]`
    * @see
    *   UsernamePassword
    */
  def basic[T: Codec[List[String], *, CodecFormat.TextPlain]](
      challenge: WWWAuthenticateChallenge = WWWAuthenticateChallenge.basic
  ): EndpointInput.Auth[T, EndpointInput.AuthType.Http] = http(AuthenticationScheme.Basic.name, challenge)

  /** Reads authorization data from the `Authorization` headers starting with `Bearer `, removing the prefix. */
  def bearer[T: Codec[List[String], *, CodecFormat.TextPlain]](
      challenge: WWWAuthenticateChallenge = WWWAuthenticateChallenge.bearer
  ): EndpointInput.Auth[T, EndpointInput.AuthType.Http] = http(AuthenticationScheme.Bearer.name, challenge)

  def http[T: Codec[List[String], *, CodecFormat.TextPlain]](
      authScheme: String,
      challenge: WWWAuthenticateChallenge
  ): EndpointInput.Auth[T, EndpointInput.AuthType.Http] = {
    val codec = implicitly[Codec[List[String], T, CodecFormat.TextPlain]]

    def filterHeaders[T: Codec[List[String], *, TextPlain]](headers: List[String]) =
      headers.filter(_.toLowerCase.startsWith(authScheme.toLowerCase))

    def stringPrefixWithSpace: Mapping[List[String], List[String]] =
      Mapping.stringPrefixCaseInsensitiveForList(authScheme + " ")

    val authCodec = Codec
      .id[List[String], CodecFormat.TextPlain](codec.format, Schema.binary)
      .map(filterHeaders(_))(identity)
      .map(stringPrefixWithSpace)
      .mapDecode(codec.decode)(codec.encode)
      .schema(codec.schema)

    EndpointInput.Auth(
      header[T](HeaderNames.Authorization)(authCodec),
      challenge,
      EndpointInput.AuthType.Http(authScheme),
      EndpointInput.AuthInfo.Empty
    )
  }

  object oauth2 {
    sealed trait OAuth2Flow
    object OAuth2Flow {
      case object AuthenticationCode extends OAuth2Flow
      case object ClientCredentials extends OAuth2Flow
      case object Implicit extends OAuth2Flow

      val Attribute: AttributeKey[OAuth2Flow] = new AttributeKey[OAuth2Flow]("sttp.tapir.TapirAuth.oauth2.OAuth2Flow")
    }

    @deprecated("Use insted authorizationCodeFlow, clientCredentialsFlow or implicitFlow", "")
    def authorizationCode(
        authorizationUrl: Option[String] = None,
        scopes: ListMap[String, String] = ListMap(),
        tokenUrl: Option[String] = None,
        refreshUrl: Option[String] = None,
        challenge: WWWAuthenticateChallenge = WWWAuthenticateChallenge.bearer
    ): Auth[String, EndpointInput.AuthType.OAuth2] = {
      EndpointInput.Auth(
        header[String](HeaderNames.Authorization).map(stringPrefixWithSpace(AuthenticationScheme.Bearer.name)),
        challenge,
        EndpointInput.AuthType.OAuth2(authorizationUrl, tokenUrl, scopes, refreshUrl),
        EndpointInput.AuthInfo.Empty
      )
    }

    private def buildInput(
        baseOAuth: EndpointInput.AuthType.OAuth2,
        challenge: WWWAuthenticateChallenge,
        flow: OAuth2Flow
    ): Auth[String, EndpointInput.AuthType.OAuth2] =
      EndpointInput
        .Auth(
          header[String](HeaderNames.Authorization).map(stringPrefixWithSpace(AuthenticationScheme.Bearer.name)),
          challenge,
          baseOAuth: EndpointInput.AuthType.OAuth2,
          EndpointInput.AuthInfo.Empty
        )
        .attribute(OAuth2Flow.Attribute, flow)

    def authorizationCodeFlow(
        authorizationUrl: String,
        tokenUrl: String,
        refreshUrl: Option[String] = None,
        scopes: ListMap[String, String] = ListMap(),
        challenge: WWWAuthenticateChallenge = WWWAuthenticateChallenge.bearer
    ): Auth[String, EndpointInput.AuthType.OAuth2] = buildInput(
      EndpointInput.AuthType.OAuth2(Some(authorizationUrl), Some(tokenUrl), scopes, refreshUrl),
      challenge,
      OAuth2Flow.AuthenticationCode
    )

    def clientCredentialsFlow(
        tokenUrl: String,
        refreshUrl: Option[String] = None,
        scopes: ListMap[String, String] = ListMap(),
        challenge: WWWAuthenticateChallenge = WWWAuthenticateChallenge.bearer
    ): Auth[String, EndpointInput.AuthType.OAuth2] =
      buildInput(
        EndpointInput.AuthType.OAuth2(None, Some(tokenUrl), scopes, refreshUrl),
        challenge,
        OAuth2Flow.ClientCredentials
      )

    def implicitFlow(
        authorizationUrl: String,
        refreshUrl: Option[String] = None,
        scopes: ListMap[String, String] = ListMap(),
        challenge: WWWAuthenticateChallenge = WWWAuthenticateChallenge.bearer
    ): Auth[String, EndpointInput.AuthType.OAuth2] =
      buildInput(EndpointInput.AuthType.OAuth2(Some(authorizationUrl), None, scopes, refreshUrl), challenge, OAuth2Flow.Implicit)

    private def stringPrefixWithSpace(prefix: String) = Mapping.stringPrefixCaseInsensitive(prefix + " ")
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy