From a828e23767e80b5c979a728ff54e3f7227ac11db Mon Sep 17 00:00:00 2001 From: dalbodeule <11470513+dalbodeule@users.noreply.github.com> Date: Thu, 8 Aug 2024 14:30:26 +0900 Subject: [PATCH] add OAuth flows - add naver OAuth flow --- inc.env | 4 + webserver/build.gradle.kts | 10 +- .../space/mori/chzzk_bot/webserver/Main.kt | 98 ++++++++++++++++++- .../mori/chzzk_bot/webserver/ktorClient.kt | 10 ++ 4 files changed, 119 insertions(+), 3 deletions(-) create mode 100644 webserver/src/main/kotlin/space/mori/chzzk_bot/webserver/ktorClient.kt diff --git a/inc.env b/inc.env index e05be4e..caaa4b4 100644 --- a/inc.env +++ b/inc.env @@ -6,5 +6,9 @@ DB_PASS=chzzk RUN_AGENT=false YOUTUBE_API_KEY= RAPID_KEY= +HOST=http://localhost:8080 +FRONTEND=http://localhost:3000 +NAVER_CLIENT_ID= +NAVER_CLIENT_SECRET= NID_AUT= NID_SES= \ No newline at end of file diff --git a/webserver/build.gradle.kts b/webserver/build.gradle.kts index ff34b6c..23b8007 100644 --- a/webserver/build.gradle.kts +++ b/webserver/build.gradle.kts @@ -18,9 +18,14 @@ dependencies { implementation("io.ktor:ktor-server-websockets:$ktorVersion") implementation("io.ktor:ktor-server-swagger:$ktorVersion") implementation("io.ktor:ktor-server-content-negotiation:$ktorVersion") - implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion") implementation("io.ktor:ktor-server-cors:$ktorVersion") implementation("io.ktor:ktor-server-swagger:$ktorVersion") + implementation("io.ktor:ktor-server-auth:$ktorVersion") + + implementation("io.ktor:ktor-client-core:$ktorVersion") + implementation("io.ktor:ktor-client-cio:$ktorVersion") + implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion") + implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion") implementation("io.swagger.codegen.v3:swagger-codegen-generators:1.0.50") @@ -35,6 +40,9 @@ dependencies { // https://mvnrepository.com/artifact/ch.qos.logback/logback-classic implementation("ch.qos.logback:logback-classic:1.5.6") + // https://mvnrepository.com/artifact/io.github.cdimascio/dotenv-kotlin + implementation("io.github.cdimascio:dotenv-kotlin:6.4.1") + implementation(project(":common")) testImplementation(kotlin("test")) diff --git a/webserver/src/main/kotlin/space/mori/chzzk_bot/webserver/Main.kt b/webserver/src/main/kotlin/space/mori/chzzk_bot/webserver/Main.kt index 4d56bda..155e6e0 100644 --- a/webserver/src/main/kotlin/space/mori/chzzk_bot/webserver/Main.kt +++ b/webserver/src/main/kotlin/space/mori/chzzk_bot/webserver/Main.kt @@ -1,21 +1,38 @@ package space.mori.chzzk_bot.webserver +import applicationHttpClient +import io.github.cdimascio.dotenv.dotenv +import io.ktor.client.call.* +import io.ktor.client.request.* import io.ktor.http.* import io.ktor.serialization.kotlinx.* import io.ktor.serialization.kotlinx.json.* import io.ktor.server.application.* +import io.ktor.server.auth.* import io.ktor.server.engine.* import io.ktor.server.netty.* import io.ktor.server.plugins.contentnegotiation.* import io.ktor.server.plugins.cors.routing.* import io.ktor.server.plugins.swagger.* +import io.ktor.server.response.* import io.ktor.server.routing.* +import io.ktor.server.sessions.* import io.ktor.server.websocket.* +import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json +import space.mori.chzzk_bot.common.dotenv import space.mori.chzzk_bot.webserver.routes.* import java.time.Duration -val server = embeddedServer(Netty, port = 8080) { +val dotenv = dotenv { + ignoreIfMissing = true +} + +const val naverMeAPIURL = "https://openapi.naver.com/v1/nid/me" + +val redirects = mutableMapOf() + +val server = embeddedServer(Netty, port = 8080, ) { install(WebSockets) { pingPeriod = Duration.ofSeconds(15) timeout = Duration.ofSeconds(15) @@ -34,7 +51,66 @@ val server = embeddedServer(Netty, port = 8080) { anyHost() allowHeader(HttpHeaders.ContentType) } + install(Sessions) { + cookie("user_session", storage = SessionStorageMemory()) {} + } + install(Authentication) { + oauth("auth-oauth-naver") { + urlProvider = { "${dotenv["HOST"]}/auth/callback" } + providerLookup = { OAuthServerSettings.OAuth2ServerSettings( + name = "naver", + authorizeUrl = "https://nid.naver.com/oauth2.0/authorize", + accessTokenUrl = "https://nid.naver.com/oauth2.0/token", + requestMethod = HttpMethod.Post, + clientId = dotenv["NAVER_CLIENT_ID"], + clientSecret = dotenv["NAVER_CLIENT_SECRET"], + defaultScopes = listOf(""), + extraAuthParameters = listOf(), + onStateCreated = { call, state -> + //saves new state with redirect url value + call.request.queryParameters["redirectUrl"]?.let { + redirects[state] = it + } + } + )} + client = applicationHttpClient + } + } routing { + route("/auth") { + authenticate("auth-oauth-naver") { + get("/login") { + + } + get("/callback") { + val currentPrincipal = call.principal() + currentPrincipal?.let { principal -> + principal.state?.let { state -> + val userInfo: NaverAPI = applicationHttpClient.get(naverMeAPIURL) { + headers{ + append(HttpHeaders.Authorization, "Bearer ${principal.accessToken}") + } + }.body() + + call.sessions.set(userInfo.response?.let { it1 -> + UserSession(state, + it1.id, it1.nickname, it1.profile_image) + }) + + redirects[state]?.let { redirect -> + call.respondRedirect(redirect) + return@get + } + } + } + call.respondRedirect(dotenv["FRONTEND"]) + } + } + get("/logout") { + call.sessions.clear() + } + } + apiRoutes() apiSongRoutes() wsTimerRoutes() @@ -54,4 +130,22 @@ fun start() { fun stop() { server.stop() -} \ No newline at end of file +} + +@Serializable +data class UserSession( + val state: String, + val id: String, + val nickname: String, + val profileImage: String +) + +@Serializable +data class NaverMeAPI( + val id: String, + val nickname: String, + val profile_image: String +) + +@Serializable +data class NaverAPI(val resultcode: String, val message: String, val response: T?) \ No newline at end of file diff --git a/webserver/src/main/kotlin/space/mori/chzzk_bot/webserver/ktorClient.kt b/webserver/src/main/kotlin/space/mori/chzzk_bot/webserver/ktorClient.kt new file mode 100644 index 0000000..44c0812 --- /dev/null +++ b/webserver/src/main/kotlin/space/mori/chzzk_bot/webserver/ktorClient.kt @@ -0,0 +1,10 @@ +import io.ktor.client.* +import io.ktor.client.engine.cio.* +import io.ktor.client.plugins.contentnegotiation.* +import io.ktor.serialization.kotlinx.json.* + +val applicationHttpClient = HttpClient(CIO) { + install(ContentNegotiation) { + json() + } +} \ No newline at end of file