Skip to content

Instantly share code, notes, and snippets.

@dacr
Last active August 18, 2023 07:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dacr/968953125248d47746092191c980d2b0 to your computer and use it in GitHub Desktop.
Save dacr/968953125248d47746092191c980d2b0 to your computer and use it in GitHub Desktop.
securing API with tapir - basic auth - output openapi doc / published by https://github.com/dacr/code-examples-manager #d5994634-17f4-4e9d-9a03-5a9706c98097/e13c015aad3fdc8cff45147b4386bd70ba4ea5bb
// summary : securing API with tapir - basic auth - output openapi doc
// keywords : scala, zio, tapir, http, zhttp, endpoints, auth, basicauth, swagger, openapi, secured, @testable, @exclusive
// publish : gist
// authors : David Crosson
// license : Apache NON-AI License Version 2.0 (https://raw.githubusercontent.com/non-ai-licenses/non-ai-licenses/main/NON-AI-APACHE2)
// id : d5994634-17f4-4e9d-9a03-5a9706c98097
// created-on : 2023-05-12T18:51:27+02:00
// managed-by : https://github.com/dacr/code-examples-manager
// run-with : scala-cli $file
// test-with : curl -v -u admin:admin http://127.0.0.1:8080/hello
// ---------------------
//> using scala "3.3.0"
//> using dep "com.softwaremill.sttp.tapir::tapir-core:1.6.0"
//> using dep "com.softwaremill.sttp.tapir::tapir-zio-http-server:1.6.0"
//> using dep "com.softwaremill.sttp.tapir::tapir-json-zio:1.6.0"
//> using dep "com.softwaremill.sttp.tapir::tapir-swagger-ui-bundle:1.6.0"
//> using dep "com.softwaremill.sttp.tapir::tapir-openapi-docs:1.6.0"
//> using dep "com.softwaremill.sttp.apispec::apispec-model:0.5.3" // to just generate yaml / json openapi documentation
//> using dep "fr.janalyse::zio-worksheet:2.0.15.0"
// ---------------------
import sttp.tapir.ztapir.*
import sttp.tapir.server.ziohttp.ZioHttpInterpreter
import zio.*, zio.worksheet.*, zio.http.Server, zio.json.*
import sttp.model.headers.WWWAuthenticateChallenge
import sttp.tapir.model.UsernamePassword
import sttp.tapir.json.zio.*
import sttp.tapir.generic.auto.*
import sttp.model.StatusCode
import sttp.tapir.swagger.bundle.SwaggerInterpreter
import sttp.tapir.docs.openapi.OpenAPIDocsInterpreter
import sttp.apispec.openapi.Info
import io.circe.Printer
import io.circe.syntax.*
import sttp.apispec.openapi.circe.*
/*
curl -u admin:x http://127.0.0.1:8080/hello
curl -u admin:admin http://127.0.0.1:8080/hello
curl -u admin:x http://127.0.0.1:8080/docs
*/
case class User(userName: String)
case class PermissionDenied(message: String) derives JsonCodec
case class BackendIssue(message: String) derives JsonCodec
def checkUsernamePassword(usernamePassword: UsernamePassword): ZIO[Any, PermissionDenied, User] =
usernamePassword match {
case UsernamePassword(username @ "admin", Some("admin")) => ZIO.succeed(User(username))
case UsernamePassword(username, _) => ZIO.fail(PermissionDenied("Invalid username or password"))
}
val statusForPermissionDenied = oneOfVariant(StatusCode.Forbidden, jsonBody[PermissionDenied].description("Permission denied"))
val statusForBackendIssue = oneOfVariant(StatusCode.InternalServerError, jsonBody[BackendIssue].description("Something went wrong on backend side"))
val challengeBasic = WWWAuthenticateChallenge.basic("basicAuth")
val securedBasicEndpoint =
endpoint.get
.tag("greetings")
.securityIn(auth.basic[UsernamePassword](challengeBasic))
.errorOutVariant(statusForPermissionDenied)
.zServerSecurityLogic[Any, User](usernamePassword => checkUsernamePassword(usernamePassword))
val helloEndPoint =
securedBasicEndpoint
.in("hello")
.out(stringBody)
.errorOutVariantPrepend(statusForBackendIssue)
.serverLogic[Any](user => hello(user))
def hello(user: User): Unit => ZIO[Any, BackendIssue, String] = _ => ZIO.succeed(s"Hello ${user.userName}")
val apiRoutes = List(helloEndPoint)
val apiInfo = Info(title = "Greeting API", version = "1.0", description = Some("Everything required to be polite"))
val swaggerEndpoints = SwaggerInterpreter().fromServerEndpoints(apiRoutes, apiInfo)
val apiDocs = OpenAPIDocsInterpreter().toOpenAPI(apiRoutes.map(_.endpoint), apiInfo)
val helloHttp = ZioHttpInterpreter().toHttp(helloEndPoint :: swaggerEndpoints)
val app = Console.printLine(apiDocs.asJson.toString) *> Server.serve(helloHttp.withDefaultErrorResponse)
app.provide(Server.default).unsafeRun
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment