Skip to content

Commit 23606bd

Browse files
luigi617luigi
andauthored
Service sdk authentication (#1321)
* service init, code to separate client and service * server can be ran * application in build.gradle.kts * detele non-finished code * ktlintformat * change style * ktlintformat * add abstraction for service * minor fix * minor fix * cbor serde * fix * formatting * comment * fix * fix * generate routing based on smithy file * ktlint format * service framework abstraction * ktlint foramt * logging * ktlint format * add ContentTypeGuard * ktlint * fix content type guard * ktlint * move some parameters, e.g. engine, port, log level to be chosen in runtime rather than compile * ktlint * fix * error handler * ktlint * fix logging * error handler message envelope * use error handler for content guard type * fix * fix * ktlint * ktlint * fix * fix and set up * authentication set up * fix * kitlint * error handler for auth * remove manual input * fix * abstraction of service framework and run type * simplify the code * fix * auth * fix * fix --------- Co-authored-by: luigi <[email protected]>
1 parent 1fcf6fc commit 23606bd

File tree

4 files changed

+102
-33
lines changed

4 files changed

+102
-33
lines changed

codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/KotlinDependency.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ data class KotlinDependency(
150150
val KTOR_SERVER_STATUS_PAGE = KotlinDependency(GradleConfiguration.Implementation, "io.ktor.server.plugins.statuspages", "io.ktor", "ktor-server-status-pages-jvm", KTOR_VERSION)
151151
val KOTLINX_CBOR_SERDE = KotlinDependency(GradleConfiguration.Implementation, "kotlinx.serialization", "org.jetbrains.kotlinx", "kotlinx-serialization-cbor", KOTLINX_VERSION)
152152
val KOTLINX_JSON_SERDE = KotlinDependency(GradleConfiguration.Implementation, "kotlinx.serialization.json", "org.jetbrains.kotlinx", "kotlinx-serialization-json", KOTLINX_VERSION)
153+
val KTOR_SERVER_AUTH = KotlinDependency(GradleConfiguration.Implementation, "io.ktor.server.auth", "io.ktor", "ktor-server-auth", KTOR_VERSION)
153154
}
154155

155156
override fun getDependencies(): List<SymbolDependency> {

codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -560,6 +560,13 @@ object RuntimeTypes {
560560
val exception = symbol("exception")
561561
}
562562

563+
object KtorServerAuth : RuntimeTypePackage(KotlinDependency.KTOR_SERVER_AUTH) {
564+
val Authentication = symbol("Authentication")
565+
val authenticate = symbol("authenticate")
566+
val Principal = symbol("Principal")
567+
val bearer = symbol("bearer")
568+
}
569+
563570
object KotlinxCborSerde : RuntimeTypePackage(KotlinDependency.KOTLINX_CBOR_SERDE) {
564571
val Serializable = symbol("Serializable")
565572
val Cbor = symbol("Cbor", "cbor")

codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/service/KtorStubGenerator.kt

Lines changed: 89 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ import software.amazon.smithy.kotlin.codegen.core.RuntimeTypes
99
import software.amazon.smithy.kotlin.codegen.core.withBlock
1010
import software.amazon.smithy.kotlin.codegen.core.withInlineBlock
1111
import software.amazon.smithy.kotlin.codegen.model.getTrait
12+
import software.amazon.smithy.model.shapes.OperationShape
13+
import software.amazon.smithy.model.traits.AuthTrait
14+
import software.amazon.smithy.model.traits.HttpBearerAuthTrait
1215
import software.amazon.smithy.model.traits.HttpTrait
1316
import software.amazon.smithy.utils.AbstractCodeWriter
1417

@@ -44,6 +47,7 @@ internal class KtorStubGenerator(
4447
) {
4548
write("#T()", ServiceTypes(pkgName).configureErrorHandling)
4649

50+
write("#T()", ServiceTypes(pkgName).configureAuthentication)
4751
write("#T()", ServiceTypes(pkgName).configureRouting)
4852
write("#T()", ServiceTypes(pkgName).configureLogging)
4953
}
@@ -123,6 +127,36 @@ internal class KtorStubGenerator(
123127

124128
// Generates `Authentication.kt` with Authenticator interface + configureSecurity().
125129
override fun renderAuthModule() {
130+
delegator.useFileWriter("UserPrincipal.kt", "${ctx.settings.pkg.name}.auth") { writer ->
131+
writer.withBlock("public data class UserPrincipal(", ")") {
132+
write("val user: String")
133+
}
134+
}
135+
136+
delegator.useFileWriter("Validation.kt", "${ctx.settings.pkg.name}.auth") { writer ->
137+
writer.withBlock("public fun bearerValidation(token: String): UserPrincipal? {", "}") {
138+
write("// TODO: implement me")
139+
write("if (true) return UserPrincipal(#S) else return null", "Authenticated User")
140+
}
141+
}
142+
143+
delegator.useFileWriter("Authentication.kt", "${ctx.settings.pkg.name}.auth") { writer ->
144+
writer.withBlock("internal fun #T.configureAuthentication() {", "}", RuntimeTypes.KtorServerCore.Application) {
145+
write("")
146+
withBlock(
147+
"#T(#T) {",
148+
"}",
149+
RuntimeTypes.KtorServerCore.install,
150+
RuntimeTypes.KtorServerAuth.Authentication,
151+
) {
152+
withBlock("#T(#S) {", "}", RuntimeTypes.KtorServerAuth.bearer, "auth-bearer") {
153+
write("realm = #S", "Access to API")
154+
write("authenticate { cred -> bearerValidation(cred.token) }")
155+
}
156+
write("provider(#S) { authenticate { ctx -> ctx.principal(Unit) } }", "no-auth")
157+
}
158+
}
159+
}
126160
}
127161

128162
// For every operation request structure, create a `validate()` function file.
@@ -148,6 +182,7 @@ internal class KtorStubGenerator(
148182
withBlock("#T(#S) {", "}", RuntimeTypes.KtorServerRouting.get, "/") {
149183
write(" #T.#T(#S)", RuntimeTypes.KtorServerCore.applicationCall, RuntimeTypes.KtorServerRouting.responseText, "hello world")
150184
}
185+
151186
operations.filter { it.hasTrait(HttpTrait.ID) }
152187
.forEach { shape ->
153188
val httpTrait = shape.getTrait<HttpTrait>()!!
@@ -172,43 +207,48 @@ internal class KtorStubGenerator(
172207

173208
withBlock("#T (#S) {", "}", RuntimeTypes.KtorServerRouting.route, uri) {
174209
write("#T(#T) { $contentTypeGuard }", RuntimeTypes.KtorServerCore.install, ServiceTypes(pkgName).contentTypeGuard)
175-
176-
withBlock("#T {", "}", method) {
177-
withInlineBlock("try {", "}") {
178-
write(
179-
"val request = #T.#T<ByteArray>()",
180-
RuntimeTypes.KtorServerCore.applicationCall,
181-
RuntimeTypes.KtorServerRouting.requestReceive,
182-
)
183-
write("val deserializer = ${shape.id.name}OperationDeserializer()")
184-
withBlock(
185-
"val requestObj = try { deserializer.deserialize(#T(), request) } catch (ex: Exception) {",
186-
"}",
187-
RuntimeTypes.Core.ExecutionContext,
188-
) {
210+
withBlock(
211+
"#W",
212+
"}",
213+
{ w: KotlinWriter -> renderRoutingAuth(w, shape) },
214+
) {
215+
withBlock("#T {", "}", method) {
216+
withInlineBlock("try {", "}") {
189217
write(
190-
"throw #T(#S, ex)",
191-
RuntimeTypes.KtorServerCore.BadRequestException,
192-
"Malformed CBOR input",
218+
"val request = #T.#T<ByteArray>()",
219+
RuntimeTypes.KtorServerCore.applicationCall,
220+
RuntimeTypes.KtorServerRouting.requestReceive,
193221
)
222+
write("val deserializer = ${shape.id.name}OperationDeserializer()")
223+
withBlock(
224+
"val requestObj = try { deserializer.deserialize(#T(), request) } catch (ex: Exception) {",
225+
"}",
226+
RuntimeTypes.Core.ExecutionContext,
227+
) {
228+
write(
229+
"throw #T(#S, ex)",
230+
RuntimeTypes.KtorServerCore.BadRequestException,
231+
"Malformed CBOR input",
232+
)
233+
}
234+
write("val responseObj = handle${shape.id.name}Request(requestObj)")
235+
write("val serializer = ${shape.id.name}OperationSerializer()")
236+
withBlock(
237+
"val response = try { serializer.serialize(#T(), responseObj) } catch (ex: Exception) {",
238+
"}",
239+
RuntimeTypes.Core.ExecutionContext,
240+
) {
241+
write(
242+
"throw #T(#S, ex)",
243+
RuntimeTypes.KtorServerCore.BadRequestException,
244+
"Malformed CBOR output",
245+
)
246+
}
247+
.call { renderResponseCall(writer, contentType, successCode) }
194248
}
195-
write("val responseObj = handle${shape.id.name}Request(requestObj)")
196-
write("val serializer = ${shape.id.name}OperationSerializer()")
197-
withBlock(
198-
"val response = try { serializer.serialize(#T(), responseObj) } catch (ex: Exception) {",
199-
"}",
200-
RuntimeTypes.Core.ExecutionContext,
201-
) {
202-
write(
203-
"throw #T(#S, ex)",
204-
RuntimeTypes.KtorServerCore.BadRequestException,
205-
"Malformed CBOR output",
206-
)
249+
withBlock(" catch (t: Throwable) {", "}") {
250+
write("throw t")
207251
}
208-
.call { renderResponseCall(writer, contentType, successCode) }
209-
}
210-
withBlock(" catch (t: Throwable) {", "}") {
211-
write("throw t")
212252
}
213253
}
214254
}
@@ -218,6 +258,22 @@ internal class KtorStubGenerator(
218258
}
219259
}
220260

261+
private fun renderRoutingAuth(w: KotlinWriter, shape: OperationShape) {
262+
val hasServiceHttpBearerAuthTrait = serviceShape.hasTrait(HttpBearerAuthTrait.ID)
263+
val authTrait = shape.getTrait<AuthTrait>()
264+
val hasOperationBearerAuthTrait = authTrait?.valueSet?.contains(HttpBearerAuthTrait.ID) ?: true
265+
266+
if (hasServiceHttpBearerAuthTrait && hasOperationBearerAuthTrait) {
267+
w.write(
268+
"#T(#S) {",
269+
RuntimeTypes.KtorServerAuth.authenticate,
270+
"auth-bearer",
271+
)
272+
} else {
273+
w.write("#T(#S) {", RuntimeTypes.KtorServerAuth.authenticate, "no-auth")
274+
}
275+
}
276+
221277
private fun renderResponseCall(
222278
w: KotlinWriter,
223279
contentType: ContentType,

codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/service/ServiceTypes.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,9 @@ class ServiceTypes(val pkgName: String) {
5252
name = "ContentTypeGuard"
5353
namespace = "$pkgName.plugins"
5454
}
55+
56+
val configureAuthentication = buildSymbol {
57+
name = "configureAuthentication"
58+
namespace = "$pkgName.auth"
59+
}
5560
}

0 commit comments

Comments
 (0)