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)
This commit is contained in:
dalbodeule 2024-08-11 15:39:38 +09:00
parent dd628738f7
commit 9c047d3d87
No known key found for this signature in database
GPG Key ID: EFA860D069C9FA65
2 changed files with 132 additions and 4 deletions

View File

@ -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<OAuthAccessTokenResponse.OAuth2>()
val session = call.sessions.get<UserSession>()
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<String>
)
@Serializable
@ -158,3 +210,65 @@ data class NaverMeAPI(
@Serializable
data class NaverAPI<T>(val resultcode: String, val message: String, val response: T?)
@Serializable
data class DiscordMeAPI(
val application: DiscordApplicationAPI,
val scopes: List<String>,
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<String>,
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<DiscordMeAPI?>()
}
suspend fun getUserGuilds(accessToken: String): List<DiscordGuildListAPI> {
val response = applicationHttpClient.get("https://discord.com/api/users/@me/") {
headers {
append(HttpHeaders.Authorization, "Bearer $accessToken")
}
}
return response.body<List<DiscordGuildListAPI>>()
}

View File

@ -52,6 +52,20 @@ fun Route.apiDiscordRoutes() {
call.respond(HttpStatusCode.OK)
}
get {
val session = call.sessions.get<UserSession>()
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
}
}
}