init security scheme (#29)
This commit is contained in:
40
kompendium-auth/build.gradle.kts
Normal file
40
kompendium-auth/build.gradle.kts
Normal file
@ -0,0 +1,40 @@
|
||||
plugins {
|
||||
`java-library`
|
||||
`maven-publish`
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(platform("org.jetbrains.kotlin:kotlin-bom"))
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
||||
implementation(libs.bundles.ktor)
|
||||
implementation(libs.bundles.ktorAuth)
|
||||
implementation(projects.kompendiumCore)
|
||||
|
||||
testImplementation("org.jetbrains.kotlin:kotlin-test")
|
||||
testImplementation("org.jetbrains.kotlin:kotlin-test-junit")
|
||||
testImplementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.12.0")
|
||||
testImplementation("io.ktor:ktor-server-test-host:1.5.3")
|
||||
}
|
||||
|
||||
java {
|
||||
withSourcesJar()
|
||||
}
|
||||
|
||||
publishing {
|
||||
repositories {
|
||||
maven {
|
||||
name = "GithubPackages"
|
||||
url = uri("https://maven.pkg.github.com/lg-backbone/kompendium")
|
||||
credentials {
|
||||
username = System.getenv("GITHUB_ACTOR")
|
||||
password = System.getenv("GITHUB_TOKEN")
|
||||
}
|
||||
}
|
||||
}
|
||||
publications {
|
||||
create<MavenPublication>("kompendium") {
|
||||
from(components["kotlin"])
|
||||
artifact(tasks.sourcesJar)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package org.leafygreens.kompendium.auth
|
||||
|
||||
import io.ktor.auth.AuthenticationRouteSelector
|
||||
import io.ktor.routing.Route
|
||||
import org.leafygreens.kompendium.path.CorePathCalculator
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
class AuthPathCalculator : CorePathCalculator() {
|
||||
|
||||
private val logger = LoggerFactory.getLogger(javaClass)
|
||||
|
||||
override fun handleCustomSelectors(route: Route, tail: String): String = when (route.selector) {
|
||||
is AuthenticationRouteSelector -> {
|
||||
logger.debug("Found authentication route selector ${route.selector}")
|
||||
super.calculate(route.parent, tail)
|
||||
}
|
||||
else -> super.handleCustomSelectors(route, tail)
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
package org.leafygreens.kompendium.auth
|
||||
|
||||
import io.ktor.auth.Authentication
|
||||
import io.ktor.auth.basic
|
||||
import io.ktor.auth.BasicAuthenticationProvider
|
||||
import io.ktor.auth.jwt.jwt
|
||||
import io.ktor.auth.jwt.JWTAuthenticationProvider
|
||||
import org.leafygreens.kompendium.Kompendium
|
||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecSchemaSecurity
|
||||
|
||||
object KompendiumAuth {
|
||||
|
||||
init {
|
||||
Kompendium.pathCalculator = AuthPathCalculator()
|
||||
}
|
||||
|
||||
fun Authentication.Configuration.notarizedBasic(
|
||||
name: String? = null,
|
||||
configure: BasicAuthenticationProvider.Configuration.() -> Unit
|
||||
) {
|
||||
Kompendium.openApiSpec.components.securitySchemes[name ?: "default"] = OpenApiSpecSchemaSecurity(
|
||||
type = "http",
|
||||
scheme = "basic"
|
||||
)
|
||||
basic(name, configure)
|
||||
}
|
||||
|
||||
fun Authentication.Configuration.notarizedJwt(
|
||||
name: String? = null,
|
||||
header: String? = null,
|
||||
scheme: String? = null,
|
||||
configure: JWTAuthenticationProvider.Configuration.() -> Unit
|
||||
) {
|
||||
if (header == null || header == "Authorization") {
|
||||
Kompendium.openApiSpec.components.securitySchemes[name ?: "default"] = OpenApiSpecSchemaSecurity(
|
||||
type = "http",
|
||||
scheme = scheme ?: "bearer"
|
||||
)
|
||||
} else {
|
||||
Kompendium.openApiSpec.components.securitySchemes[name ?: "default"] = OpenApiSpecSchemaSecurity(
|
||||
type = "apiKey",
|
||||
name = header,
|
||||
`in` = "header"
|
||||
)
|
||||
}
|
||||
jwt(name, configure)
|
||||
}
|
||||
|
||||
// TODO support other authentication providers (e.g., oAuth)?
|
||||
}
|
@ -0,0 +1,197 @@
|
||||
package org.leafygreens.kompendium.auth
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude
|
||||
import com.fasterxml.jackson.databind.SerializationFeature
|
||||
import io.ktor.application.*
|
||||
import io.ktor.auth.*
|
||||
import io.ktor.features.*
|
||||
import io.ktor.http.*
|
||||
import io.ktor.jackson.*
|
||||
import io.ktor.response.*
|
||||
import io.ktor.routing.*
|
||||
import io.ktor.server.testing.*
|
||||
import org.junit.Test
|
||||
import org.leafygreens.kompendium.Kompendium
|
||||
import org.leafygreens.kompendium.Kompendium.notarizedGet
|
||||
import org.leafygreens.kompendium.auth.KompendiumAuth.notarizedBasic
|
||||
import org.leafygreens.kompendium.auth.KompendiumAuth.notarizedJwt
|
||||
import org.leafygreens.kompendium.auth.util.TestData
|
||||
import org.leafygreens.kompendium.auth.util.TestParams
|
||||
import org.leafygreens.kompendium.auth.util.TestResponse
|
||||
import org.leafygreens.kompendium.models.meta.MethodInfo
|
||||
import org.leafygreens.kompendium.models.meta.ResponseInfo
|
||||
import org.leafygreens.kompendium.models.oas.OpenApiSpec
|
||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecInfo
|
||||
import org.leafygreens.kompendium.routes.openApi
|
||||
import org.leafygreens.kompendium.routes.redoc
|
||||
import org.leafygreens.kompendium.util.KompendiumHttpCodes
|
||||
import kotlin.test.AfterTest
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
internal class KompendiumAuthTest {
|
||||
|
||||
@AfterTest
|
||||
fun `reset kompendium`() {
|
||||
Kompendium.openApiSpec = OpenApiSpec(
|
||||
info = OpenApiSpecInfo(),
|
||||
servers = mutableListOf(),
|
||||
paths = mutableMapOf()
|
||||
)
|
||||
Kompendium.cache = emptyMap()
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun `Notarized Get with basic authentication records all expected information`() {
|
||||
withTestApplication({
|
||||
configModule()
|
||||
configBasicAuth()
|
||||
docs()
|
||||
notarizedAuthenticatedGetModule(TestData.AuthConfigName.Basic)
|
||||
}) {
|
||||
// do
|
||||
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
|
||||
|
||||
// expect
|
||||
val expected = TestData.getFileSnapshot("notarized_basic_authenticated_get.json").trim()
|
||||
assertEquals(expected, json, "The received json spec should match the expected content")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Notarized Get with jwt authentication records all expected information`() {
|
||||
withTestApplication({
|
||||
configModule()
|
||||
configJwtAuth()
|
||||
docs()
|
||||
notarizedAuthenticatedGetModule(TestData.AuthConfigName.JWT)
|
||||
}) {
|
||||
// do
|
||||
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
|
||||
|
||||
// expect
|
||||
val expected = TestData.getFileSnapshot("notarized_jwt_authenticated_get.json").trim()
|
||||
assertEquals(expected, json, "The received json spec should match the expected content")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Notarized Get with jwt authentication and custom scheme records all expected information`() {
|
||||
withTestApplication({
|
||||
configModule()
|
||||
configJwtAuth(scheme = "oauth")
|
||||
docs()
|
||||
notarizedAuthenticatedGetModule(TestData.AuthConfigName.JWT)
|
||||
}) {
|
||||
// do
|
||||
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
|
||||
|
||||
// expect
|
||||
val expected = TestData.getFileSnapshot("notarized_jwt_custom_scheme_authenticated_get.json").trim()
|
||||
assertEquals(expected, json, "The received json spec should match the expected content")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Notarized Get with jwt authentication and custom header records all expected information`() {
|
||||
withTestApplication({
|
||||
configModule()
|
||||
configJwtAuth(header = "x-api-key")
|
||||
docs()
|
||||
notarizedAuthenticatedGetModule(TestData.AuthConfigName.JWT)
|
||||
}) {
|
||||
// do
|
||||
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
|
||||
|
||||
// expect
|
||||
val expected = TestData.getFileSnapshot("notarized_jwt_custom_header_authenticated_get.json").trim()
|
||||
assertEquals(expected, json, "The received json spec should match the expected content")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Notarized Get with multiple jwt schemes records all expected information`() {
|
||||
withTestApplication({
|
||||
configModule()
|
||||
install(Authentication) {
|
||||
notarizedJwt("jwt1", header = "x-api-key-1") {
|
||||
realm = "Ktor server"
|
||||
}
|
||||
notarizedJwt("jwt2", header = "x-api-key-2") {
|
||||
realm = "Ktor server"
|
||||
}
|
||||
}
|
||||
docs()
|
||||
notarizedAuthenticatedGetModule("jwt1", "jwt2")
|
||||
}) {
|
||||
// do
|
||||
val json = handleRequest(HttpMethod.Get, "/openapi.json").response.content
|
||||
|
||||
// expect
|
||||
val expected = TestData.getFileSnapshot("notarized_multiple_jwt_authenticated_get.json").trim()
|
||||
assertEquals(expected, json, "The received json spec should match the expected content")
|
||||
}
|
||||
}
|
||||
|
||||
private fun Application.configModule() {
|
||||
install(ContentNegotiation) {
|
||||
jackson(ContentType.Application.Json) {
|
||||
enable(SerializationFeature.INDENT_OUTPUT)
|
||||
setSerializationInclusion(JsonInclude.Include.NON_NULL)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Application.configBasicAuth() {
|
||||
install(Authentication) {
|
||||
notarizedBasic(TestData.AuthConfigName.Basic) {
|
||||
realm = "Ktor Server"
|
||||
validate { credentials ->
|
||||
if (credentials.name == credentials.password) {
|
||||
UserIdPrincipal(credentials.name)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Application.configJwtAuth(
|
||||
header: String? = null,
|
||||
scheme: String? = null
|
||||
) {
|
||||
install(Authentication) {
|
||||
notarizedJwt(TestData.AuthConfigName.JWT, header, scheme) {
|
||||
realm = "Ktor server"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Application.notarizedAuthenticatedGetModule(vararg authenticationConfigName: String) {
|
||||
routing {
|
||||
authenticate(*authenticationConfigName) {
|
||||
route(TestData.getRoutePath) {
|
||||
notarizedGet<TestParams, TestResponse>(testGetInfo(*authenticationConfigName)) {
|
||||
call.respondText { "hey dude ‼️ congratz on the get request" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val oas = Kompendium.openApiSpec.copy()
|
||||
|
||||
private fun Application.docs() {
|
||||
routing {
|
||||
openApi(oas)
|
||||
redoc(oas)
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
val testGetResponse = ResponseInfo(KompendiumHttpCodes.OK, "A Successful Endeavor")
|
||||
fun testGetInfo(vararg security: String) =
|
||||
MethodInfo("Another get test", "testing more", testGetResponse, securitySchemes = security.toSet())
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package org.leafygreens.kompendium.auth.util
|
||||
|
||||
import java.io.File
|
||||
|
||||
object TestData {
|
||||
object AuthConfigName {
|
||||
val Basic = "basic"
|
||||
val JWT = "jwt"
|
||||
}
|
||||
|
||||
val getRoutePath = "/test"
|
||||
|
||||
fun getFileSnapshot(fileName: String): String {
|
||||
val snapshotPath = "src/test/resources"
|
||||
val file = File("$snapshotPath/$fileName")
|
||||
return file.readText()
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package org.leafygreens.kompendium.auth.util
|
||||
|
||||
import org.leafygreens.kompendium.annotations.KompendiumField
|
||||
import org.leafygreens.kompendium.annotations.PathParam
|
||||
import org.leafygreens.kompendium.annotations.QueryParam
|
||||
|
||||
data class TestParams(
|
||||
@PathParam val a: String,
|
||||
@QueryParam val aa: Int
|
||||
)
|
||||
|
||||
data class TestRequest(
|
||||
@KompendiumField(name = "field_name")
|
||||
val b: Double,
|
||||
val aaa: List<Long>
|
||||
)
|
||||
|
||||
data class TestResponse(val c: String)
|
||||
|
@ -0,0 +1,74 @@
|
||||
{
|
||||
"openapi" : "3.0.3",
|
||||
"info" : { },
|
||||
"servers" : [ ],
|
||||
"paths" : {
|
||||
"/test" : {
|
||||
"get" : {
|
||||
"tags" : [ ],
|
||||
"summary" : "Another get test",
|
||||
"description" : "testing more",
|
||||
"parameters" : [ {
|
||||
"name" : "a",
|
||||
"in" : "path",
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/String"
|
||||
},
|
||||
"required" : true,
|
||||
"deprecated" : false
|
||||
}, {
|
||||
"name" : "aa",
|
||||
"in" : "query",
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/Int"
|
||||
},
|
||||
"required" : true,
|
||||
"deprecated" : false
|
||||
} ],
|
||||
"responses" : {
|
||||
"200" : {
|
||||
"description" : "A Successful Endeavor",
|
||||
"content" : {
|
||||
"application/json" : {
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/TestResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"deprecated" : false,
|
||||
"security" : [ {
|
||||
"basic" : [ ]
|
||||
} ]
|
||||
}
|
||||
}
|
||||
},
|
||||
"components" : {
|
||||
"schemas" : {
|
||||
"String" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"TestResponse" : {
|
||||
"properties" : {
|
||||
"c" : {
|
||||
"$ref" : "#/components/schemas/String"
|
||||
}
|
||||
},
|
||||
"type" : "object"
|
||||
},
|
||||
"Int" : {
|
||||
"format" : "int32",
|
||||
"type" : "integer"
|
||||
}
|
||||
},
|
||||
"securitySchemes" : {
|
||||
"basic" : {
|
||||
"type" : "http",
|
||||
"scheme" : "basic"
|
||||
}
|
||||
}
|
||||
},
|
||||
"security" : [ ],
|
||||
"tags" : [ ]
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
{
|
||||
"openapi" : "3.0.3",
|
||||
"info" : { },
|
||||
"servers" : [ ],
|
||||
"paths" : {
|
||||
"/test" : {
|
||||
"get" : {
|
||||
"tags" : [ ],
|
||||
"summary" : "Another get test",
|
||||
"description" : "testing more",
|
||||
"parameters" : [ {
|
||||
"name" : "a",
|
||||
"in" : "path",
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/String"
|
||||
},
|
||||
"required" : true,
|
||||
"deprecated" : false
|
||||
}, {
|
||||
"name" : "aa",
|
||||
"in" : "query",
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/Int"
|
||||
},
|
||||
"required" : true,
|
||||
"deprecated" : false
|
||||
} ],
|
||||
"responses" : {
|
||||
"200" : {
|
||||
"description" : "A Successful Endeavor",
|
||||
"content" : {
|
||||
"application/json" : {
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/TestResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"deprecated" : false,
|
||||
"security" : [ {
|
||||
"jwt" : [ ]
|
||||
} ]
|
||||
}
|
||||
}
|
||||
},
|
||||
"components" : {
|
||||
"schemas" : {
|
||||
"String" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"TestResponse" : {
|
||||
"properties" : {
|
||||
"c" : {
|
||||
"$ref" : "#/components/schemas/String"
|
||||
}
|
||||
},
|
||||
"type" : "object"
|
||||
},
|
||||
"Int" : {
|
||||
"format" : "int32",
|
||||
"type" : "integer"
|
||||
}
|
||||
},
|
||||
"securitySchemes" : {
|
||||
"jwt" : {
|
||||
"type" : "http",
|
||||
"scheme" : "bearer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"security" : [ ],
|
||||
"tags" : [ ]
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
{
|
||||
"openapi" : "3.0.3",
|
||||
"info" : { },
|
||||
"servers" : [ ],
|
||||
"paths" : {
|
||||
"/test" : {
|
||||
"get" : {
|
||||
"tags" : [ ],
|
||||
"summary" : "Another get test",
|
||||
"description" : "testing more",
|
||||
"parameters" : [ {
|
||||
"name" : "a",
|
||||
"in" : "path",
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/String"
|
||||
},
|
||||
"required" : true,
|
||||
"deprecated" : false
|
||||
}, {
|
||||
"name" : "aa",
|
||||
"in" : "query",
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/Int"
|
||||
},
|
||||
"required" : true,
|
||||
"deprecated" : false
|
||||
} ],
|
||||
"responses" : {
|
||||
"200" : {
|
||||
"description" : "A Successful Endeavor",
|
||||
"content" : {
|
||||
"application/json" : {
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/TestResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"deprecated" : false,
|
||||
"security" : [ {
|
||||
"jwt" : [ ]
|
||||
} ]
|
||||
}
|
||||
}
|
||||
},
|
||||
"components" : {
|
||||
"schemas" : {
|
||||
"String" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"TestResponse" : {
|
||||
"properties" : {
|
||||
"c" : {
|
||||
"$ref" : "#/components/schemas/String"
|
||||
}
|
||||
},
|
||||
"type" : "object"
|
||||
},
|
||||
"Int" : {
|
||||
"format" : "int32",
|
||||
"type" : "integer"
|
||||
}
|
||||
},
|
||||
"securitySchemes" : {
|
||||
"jwt" : {
|
||||
"type" : "apiKey",
|
||||
"name" : "x-api-key",
|
||||
"in" : "header"
|
||||
}
|
||||
}
|
||||
},
|
||||
"security" : [ ],
|
||||
"tags" : [ ]
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
{
|
||||
"openapi" : "3.0.3",
|
||||
"info" : { },
|
||||
"servers" : [ ],
|
||||
"paths" : {
|
||||
"/test" : {
|
||||
"get" : {
|
||||
"tags" : [ ],
|
||||
"summary" : "Another get test",
|
||||
"description" : "testing more",
|
||||
"parameters" : [ {
|
||||
"name" : "a",
|
||||
"in" : "path",
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/String"
|
||||
},
|
||||
"required" : true,
|
||||
"deprecated" : false
|
||||
}, {
|
||||
"name" : "aa",
|
||||
"in" : "query",
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/Int"
|
||||
},
|
||||
"required" : true,
|
||||
"deprecated" : false
|
||||
} ],
|
||||
"responses" : {
|
||||
"200" : {
|
||||
"description" : "A Successful Endeavor",
|
||||
"content" : {
|
||||
"application/json" : {
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/TestResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"deprecated" : false,
|
||||
"security" : [ {
|
||||
"jwt" : [ ]
|
||||
} ]
|
||||
}
|
||||
}
|
||||
},
|
||||
"components" : {
|
||||
"schemas" : {
|
||||
"String" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"TestResponse" : {
|
||||
"properties" : {
|
||||
"c" : {
|
||||
"$ref" : "#/components/schemas/String"
|
||||
}
|
||||
},
|
||||
"type" : "object"
|
||||
},
|
||||
"Int" : {
|
||||
"format" : "int32",
|
||||
"type" : "integer"
|
||||
}
|
||||
},
|
||||
"securitySchemes" : {
|
||||
"jwt" : {
|
||||
"type" : "http",
|
||||
"scheme" : "oauth"
|
||||
}
|
||||
}
|
||||
},
|
||||
"security" : [ ],
|
||||
"tags" : [ ]
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
{
|
||||
"openapi" : "3.0.3",
|
||||
"info" : { },
|
||||
"servers" : [ ],
|
||||
"paths" : {
|
||||
"/test" : {
|
||||
"get" : {
|
||||
"tags" : [ ],
|
||||
"summary" : "Another get test",
|
||||
"description" : "testing more",
|
||||
"parameters" : [ {
|
||||
"name" : "a",
|
||||
"in" : "path",
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/String"
|
||||
},
|
||||
"required" : true,
|
||||
"deprecated" : false
|
||||
}, {
|
||||
"name" : "aa",
|
||||
"in" : "query",
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/Int"
|
||||
},
|
||||
"required" : true,
|
||||
"deprecated" : false
|
||||
} ],
|
||||
"responses" : {
|
||||
"200" : {
|
||||
"description" : "A Successful Endeavor",
|
||||
"content" : {
|
||||
"application/json" : {
|
||||
"schema" : {
|
||||
"$ref" : "#/components/schemas/TestResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"deprecated" : false,
|
||||
"security" : [ {
|
||||
"jwt1" : [ ],
|
||||
"jwt2" : [ ]
|
||||
} ]
|
||||
}
|
||||
}
|
||||
},
|
||||
"components" : {
|
||||
"schemas" : {
|
||||
"String" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"TestResponse" : {
|
||||
"properties" : {
|
||||
"c" : {
|
||||
"$ref" : "#/components/schemas/String"
|
||||
}
|
||||
},
|
||||
"type" : "object"
|
||||
},
|
||||
"Int" : {
|
||||
"format" : "int32",
|
||||
"type" : "integer"
|
||||
}
|
||||
},
|
||||
"securitySchemes" : {
|
||||
"jwt1" : {
|
||||
"type" : "apiKey",
|
||||
"name" : "x-api-key-1",
|
||||
"in" : "header"
|
||||
},
|
||||
"jwt2" : {
|
||||
"type" : "apiKey",
|
||||
"name" : "x-api-key-2",
|
||||
"in" : "header"
|
||||
}
|
||||
}
|
||||
},
|
||||
"security" : [ ],
|
||||
"tags" : [ ]
|
||||
}
|
Reference in New Issue
Block a user