Skip to content

Commit

Permalink
feat: add configurable webserver for jvm and fix default HTTP status …
Browse files Browse the repository at this point in the history
…code. Fixes #65.

It is now possible to configure the JvmCodeAuthFlowFactory / PlatformCodeAuthFlow using a webserverProvider. The pre-defined SimpleKtorWebserver is configurable to send custom results and now sends a default HTML response.
  • Loading branch information
kalinjul committed Oct 11, 2024
1 parent 1e20fb7 commit 5837e20
Show file tree
Hide file tree
Showing 6 changed files with 82 additions and 42 deletions.
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package org.publicvalue.multiplatform.oidc.appsupport

import org.publicvalue.multiplatform.oidc.OpenIdConnectClient
import org.publicvalue.multiplatform.oidc.appsupport.webserver.SimpleKtorWebserver
import org.publicvalue.multiplatform.oidc.appsupport.webserver.Webserver

@Suppress("unused")
class JvmCodeAuthFlowFactory(
private val webserverProvider: () -> Webserver = { SimpleKtorWebserver() }
): CodeAuthFlowFactory {
override fun createAuthFlow(client: OpenIdConnectClient): PlatformCodeAuthFlow {
return PlatformCodeAuthFlow(client)
return PlatformCodeAuthFlow(client, webserverProvider = webserverProvider)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import kotlinx.coroutines.async
import kotlinx.coroutines.withContext
import org.publicvalue.multiplatform.oidc.OpenIdConnectClient
import org.publicvalue.multiplatform.oidc.OpenIdConnectException
import org.publicvalue.multiplatform.oidc.appsupport.webserver.SimpleKtorWebserver
import org.publicvalue.multiplatform.oidc.appsupport.webserver.Webserver
import org.publicvalue.multiplatform.oidc.flows.AuthCodeResponse
import org.publicvalue.multiplatform.oidc.flows.AuthCodeResult
import org.publicvalue.multiplatform.oidc.flows.CodeAuthFlow
import org.publicvalue.multiplatform.oidc.types.AuthCodeRequest
import java.awt.Desktop
Expand All @@ -18,7 +18,8 @@ import java.net.NetworkInterface
import java.net.SocketException

actual class PlatformCodeAuthFlow(
client: OpenIdConnectClient
client: OpenIdConnectClient,
private val webserverProvider: () -> Webserver = { SimpleKtorWebserver() }
) : CodeAuthFlow(client) {
companion object {
var PORT = 8080
Expand All @@ -31,7 +32,7 @@ actual class PlatformCodeAuthFlow(
throw OpenIdConnectException.AuthenticationFailure("JVM implementation can only handle redirect uris using localhost port 8080! Redirect uri was: $redirectUrl")
}

val webserver = Webserver()
val webserver = webserverProvider()

val response =
withContext(Dispatchers.IO) {
Expand All @@ -43,14 +44,8 @@ actual class PlatformCodeAuthFlow(
}.await()
}

val authCode = response?.queryParameters?.get("code")
val state = response?.queryParameters?.get("state")

return AuthCodeResponse.success(
AuthCodeResult(
code = authCode,
state = state
)
response
)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package org.publicvalue.multiplatform.oidc.appsupport.webserver

import io.ktor.http.ContentType
import io.ktor.http.HttpStatusCode
import io.ktor.server.application.ApplicationCall
import io.ktor.server.application.call
import io.ktor.server.cio.CIO
import io.ktor.server.cio.CIOApplicationEngine
import io.ktor.server.engine.embeddedServer
import io.ktor.server.request.ApplicationRequest
import io.ktor.server.response.respondText
import io.ktor.server.routing.get
import io.ktor.server.routing.routing
import org.publicvalue.multiplatform.oidc.flows.AuthCodeResult

class SimpleKtorWebserver(
val createResponse: suspend io.ktor.util.pipeline.PipelineContext<Unit, ApplicationCall>.() -> Unit = {
call.respondText(
status = HttpStatusCode.OK,
text = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Authorization redirect successful</title>
</head>
<body>
<h1>You may now close this page and return to your app.</h1>
</body>
</html>
""".trimIndent(),
contentType = ContentType.parse("text/html")
)
}
): Webserver {
private var server: CIOApplicationEngine? = null

override suspend fun startAndWaitForRedirect(port: Int, redirectPath: String): AuthCodeResult {
var call: ApplicationRequest? = null
server?.stop()
embeddedServer(CIO, port = port) {
routing {
get(redirectPath) {
createResponse()
call = this.call.request
server?.stop()
}
}
}.apply {
server = this
start(wait = true)
}
val code = call?.queryParameters?.get("code")
val state = call?.queryParameters?.get("code")
return AuthCodeResult(code, state)
}

override suspend fun stop() {
server?.stop()
}
}
Original file line number Diff line number Diff line change
@@ -1,36 +1,17 @@
package org.publicvalue.multiplatform.oidc.appsupport.webserver

import io.ktor.http.HttpStatusCode
import io.ktor.server.application.*
import io.ktor.server.cio.CIO
import io.ktor.server.cio.CIOApplicationEngine
import io.ktor.server.engine.embeddedServer
import io.ktor.server.request.ApplicationRequest
import io.ktor.server.response.*
import io.ktor.server.routing.*
import org.publicvalue.multiplatform.oidc.flows.AuthCodeResult

class Webserver() {
private var server: CIOApplicationEngine? = null
interface Webserver {
/**
* Start a local Webserver on the given port, waiting for the redirectPath to be called.
*
* @return RedirectResponse containing authCode + state.
*/
suspend fun startAndWaitForRedirect(port: Int, redirectPath: String): AuthCodeResult

suspend fun startAndWaitForRedirect(port: Int, redirectPath: String): ApplicationRequest? {
var call: ApplicationRequest? = null
server?.stop()
embeddedServer(CIO, port = port) {
routing {
get(redirectPath) {
this.call.respond(status = HttpStatusCode.OK, Unit)
call = this.call.request
server?.stop()
}
}
}.apply {
server = this
start(wait = true)
}
return call
}

fun stop() {
server?.stop()
}
/**
* Stop the webserver.
*/
suspend fun stop()
}

0 comments on commit 5837e20

Please sign in to comment.