From 9c047d3d87bfb929b9b83fb8db564d84556f1409 Mon Sep 17 00:00:00 2001 From: dalbodeule <11470513+dalbodeule@users.noreply.github.com> Date: Sun, 11 Aug 2024 15:39:38 +0900 Subject: [PATCH] add discord login - add discord login logics - add discord api classes, functions. - add current user's discord guilds. (if session has discordGuilds, return it else return null lists) --- .../space/mori/chzzk_bot/webserver/Main.kt | 122 +++++++++++++++++- .../webserver/routes/ApiDiscordRoutes.kt | 14 ++ 2 files changed, 132 insertions(+), 4 deletions(-) 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 cbbd00c..85df122 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 @@ -4,6 +4,7 @@ import applicationHttpClient import io.github.cdimascio.dotenv.dotenv import io.ktor.client.call.* import io.ktor.client.request.* +import io.ktor.client.statement.* import io.ktor.http.* import io.ktor.serialization.kotlinx.* import io.ktor.serialization.kotlinx.json.* @@ -20,6 +21,9 @@ 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.models.User +import space.mori.chzzk_bot.common.services.UserService +import space.mori.chzzk_bot.common.utils.getUserInfo import space.mori.chzzk_bot.webserver.routes.* import java.time.Duration @@ -70,6 +74,29 @@ val server = embeddedServer(Netty, port = 8080, ) { )} client = applicationHttpClient } + oauth("auth-oauth-discord") { + urlProvider = { "${dotenv["HOST"]}/auth/discord/callback" } + providerLookup = { OAuthServerSettings.OAuth2ServerSettings( + name = "discord", + authorizeUrl = "https://discord.com/oauth2/authorize", + accessTokenUrl = "https://discord.com/api/oauth2/token", + clientId = dotenv["DISCORD_CLIENT_ID"], + clientSecret = dotenv["DISCORD_CLIENT_SECRET"], + defaultScopes = listOf(), + extraAuthParameters = listOf( + Pair("permissions", "826781355072"), + Pair("response_type", "code"), + Pair("integration_type", "0"), + Pair("scope", "guilds+bot") + ), + onStateCreated = { call, state -> + call.request.queryParameters["redirectUrl"]?.let { + redirects[state] = it + } + } + )} + client = applicationHttpClient + } } routing { route("/auth") { @@ -88,7 +115,7 @@ val server = embeddedServer(Netty, port = 8080, ) { }.body() call.sessions.set(userInfo.response?.let { profile -> - UserSession(state, profile.id) + UserSession(state, profile.id, listOf()) }) redirects[state]?.let { redirect -> @@ -97,7 +124,28 @@ val server = embeddedServer(Netty, port = 8080, ) { } } } - call.respondRedirect("${ if(dotenv["FRONTEND_HTTPS"].toBoolean()) "https://" else "http://" }${dotenv["FRONTEND"]}") + call.respondRedirect(getFrontendURL("")) + } + get("/discord/callback") { + val principal = call.principal() + val session = call.sessions.get() + val user = session?.id?.let { UserService.getUserWithNaverId(it)} + + if(principal != null && session != null && user != null) { + val accessToken = principal.accessToken + val userInfo = getDiscordUser(accessToken) + val guilds = getUserGuilds(accessToken) + + userInfo?.user?.id?.toLong()?.let { id -> UserService.updateUser(user, id) } + + call.sessions.set(UserSession( + session.state, + session.id, + guilds.map { it.id } + )) + } else { + call.respondRedirect(getFrontendURL("")) + } } } get("/logout") { @@ -145,10 +193,14 @@ fun stop() { server.stop() } +fun getFrontendURL(path: String) + = "${if(dotenv["FRONTEND_HTTPS"].toBoolean()) "https://" else "http://" }${dotenv["FRONTEND"]}${path}"; + @Serializable data class UserSession( val state: String, - val id: String + val id: String, + val discordGuildList: List ) @Serializable @@ -157,4 +209,66 @@ data class NaverMeAPI( ) @Serializable -data class NaverAPI(val resultcode: String, val message: String, val response: T?) \ No newline at end of file +data class NaverAPI(val resultcode: String, val message: String, val response: T?) + +@Serializable +data class DiscordMeAPI( + val application: DiscordApplicationAPI, + val scopes: List, + val user: DiscordUserAPI +) + +@Serializable +data class DiscordApplicationAPI( + val id: String, + val name: String, + val icon: String, + val description: String, + val hook: Boolean, + val bot_public: Boolean, + val bot_require_code_grant: Boolean, + val verify_key: String +) + +@Serializable +data class DiscordUserAPI( + val id: String, + val username: String, + val avatar: String, + val discriminator: String, + val global_name: String, + val public_flags: Int +) + +@Serializable +data class DiscordGuildListAPI( + val id: String, + val name: String, + val icon: String, + val banner: String, + val owner: Boolean, + val permissions: Int, + val features: List, + val approximate_member_count: Int, + val approximate_presence_count: Int, +) + +suspend fun getDiscordUser(accessToken: String): DiscordMeAPI? { + val response: HttpResponse = applicationHttpClient.get("https://discord.com/api/oauth2/@me") { + headers { + append(HttpHeaders.Authorization, "Bearer $accessToken") + } + } + + return response.body() +} + +suspend fun getUserGuilds(accessToken: String): List { + val response = applicationHttpClient.get("https://discord.com/api/users/@me/") { + headers { + append(HttpHeaders.Authorization, "Bearer $accessToken") + } + } + + return response.body>() +} \ No newline at end of file diff --git a/webserver/src/main/kotlin/space/mori/chzzk_bot/webserver/routes/ApiDiscordRoutes.kt b/webserver/src/main/kotlin/space/mori/chzzk_bot/webserver/routes/ApiDiscordRoutes.kt index ab82bfb..d84c717 100644 --- a/webserver/src/main/kotlin/space/mori/chzzk_bot/webserver/routes/ApiDiscordRoutes.kt +++ b/webserver/src/main/kotlin/space/mori/chzzk_bot/webserver/routes/ApiDiscordRoutes.kt @@ -52,6 +52,20 @@ fun Route.apiDiscordRoutes() { call.respond(HttpStatusCode.OK) } + get { + val session = call.sessions.get() + if(session == null) { + call.respond(HttpStatusCode.BadRequest, "Session is required") + return@get + } + val user = UserService.getUserWithNaverId(session.id) + if(user == null) { + call.respond(HttpStatusCode.BadRequest, "User does not exist") + return@get + } + call.respond(HttpStatusCode.OK, session.discordGuildList) + return@get + } } }