Last active
May 25, 2024 08:38
-
-
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/a4850b1def4e911eff172a9a6f7b649faf634cf8
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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.4.2" | |
//> 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