Skip to content

Commit

Permalink
Moving some business logic around
Browse files Browse the repository at this point in the history
  • Loading branch information
NovaFox161 committed Sep 3, 2023
1 parent 75bc434 commit ab93eec
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 60 deletions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
package org.dreamexposure.discal.cam.endpoints.v1

import discord4j.common.util.Snowflake
import org.dreamexposure.discal.cam.google.GoogleAuth
import org.dreamexposure.discal.cam.managers.CalendarAuthManager
import org.dreamexposure.discal.core.annotations.Authentication
import org.dreamexposure.discal.core.business.CalendarService
import org.dreamexposure.discal.core.enums.calendar.CalendarHost
import org.dreamexposure.discal.core.`object`.network.discal.CredentialData
import org.springframework.web.bind.annotation.GetMapping
Expand All @@ -14,23 +13,11 @@ import org.springframework.web.bind.annotation.RestController
@RestController
@RequestMapping("/v1/")
class GetEndpoint(
private val calendarService: CalendarService,
private val googleAuth: GoogleAuth,
private val calendarAuthManager: CalendarAuthManager,
) {
@Authentication(access = Authentication.AccessLevel.ADMIN)
@GetMapping("token", produces = ["application/json"])
suspend fun get(@RequestParam host: CalendarHost, @RequestParam id: Int, @RequestParam guild: Snowflake?): CredentialData? {
return when (host) {
CalendarHost.GOOGLE -> {
if (guild == null) {
// Internal (owned by DisCal, should never go bad)
googleAuth.requestNewAccessToken(id)
} else {
// External (owned by user)
val calendar = calendarService.getCalendar(guild, id) ?: return null
googleAuth.requestNewAccessToken(calendar)
}
}
}
return calendarAuthManager.getCredentialData(host, id, guild)
}
}
Original file line number Diff line number Diff line change
@@ -1,73 +1,34 @@
package org.dreamexposure.discal.cam.endpoints.v1.oauth2

import org.dreamexposure.discal.cam.business.OauthStateService
import org.dreamexposure.discal.cam.discord.DiscordOauthHandler
import org.dreamexposure.discal.cam.json.discal.LoginResponse
import org.dreamexposure.discal.cam.json.discal.TokenRequest
import org.dreamexposure.discal.cam.json.discal.TokenResponse
import org.dreamexposure.discal.cam.managers.DiscordOauthManager
import org.dreamexposure.discal.core.annotations.Authentication
import org.dreamexposure.discal.core.business.SessionService
import org.dreamexposure.discal.core.config.Config
import org.dreamexposure.discal.core.crypto.KeyGenerator
import org.dreamexposure.discal.core.`object`.WebSession
import org.dreamexposure.discal.core.utils.GlobalVal.discordApiUrl
import org.springframework.http.HttpStatus
import org.springframework.web.bind.annotation.*
import org.springframework.web.server.ResponseStatusException
import java.net.URLEncoder
import java.nio.charset.Charset.defaultCharset

@RestController
@RequestMapping("/oauth2/discord/")
class DiscordOauthEndpoint(
private val sessionService: SessionService,
private val oauthStateService: OauthStateService,
private val discordOauthHandler: DiscordOauthHandler,
private val discordOauthManager: DiscordOauthManager,
) {
private val redirectUrl = Config.URL_DISCORD_REDIRECT.getString()
private val clientId = Config.DISCORD_APP_ID.getString()

private final val scopes = URLEncoder.encode("identify guilds", defaultCharset())
private final val encodedRedirectUrl = URLEncoder.encode(redirectUrl, defaultCharset())
private final val oauthLinkWithoutState = "$discordApiUrl/oauth2/authorize?client_id=$clientId&redirect_uri=$encodedRedirectUrl&response_type=code&scope=$scopes&prompt=none"

@GetMapping("login")
@Authentication(access = Authentication.AccessLevel.PUBLIC)
suspend fun login(): LoginResponse {
val state = oauthStateService.generateState()

val link = "$oauthLinkWithoutState&state=$state"

val link = discordOauthManager.getOauthLinkForLogin()
return LoginResponse(link)
}

@GetMapping("logout")
@Authentication(access = Authentication.AccessLevel.WRITE)
suspend fun logout(@RequestHeader("Authorization") token: String) {
sessionService.deleteSession(token)
discordOauthManager.handleLogout(token)
}

@PostMapping("code")
@Authentication(access = Authentication.AccessLevel.PUBLIC)
suspend fun token(@RequestBody body: TokenRequest): TokenResponse {
// Validate state
if (!oauthStateService.validateState(body.state)) {
// State invalid - 400
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid state")
}

val dTokens = discordOauthHandler.doTokenExchange(body.code)
val authInfo = discordOauthHandler.getOauthInfo(dTokens.accessToken)
val apiToken = KeyGenerator.csRandomAlphaNumericString(64)
val session = WebSession(
apiToken,
authInfo.user!!.id,
accessToken = dTokens.accessToken,
refreshToken = dTokens.refreshToken
)

sessionService.removeAndInsertSession(session)

return TokenResponse(session.token, session.expiresAt, authInfo.user)
return discordOauthManager.handleCodeExchange(body.state, body.code)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.dreamexposure.discal.cam.managers

import discord4j.common.util.Snowflake
import org.dreamexposure.discal.cam.google.GoogleAuth
import org.dreamexposure.discal.core.business.CalendarService
import org.dreamexposure.discal.core.enums.calendar.CalendarHost
import org.dreamexposure.discal.core.`object`.network.discal.CredentialData
import org.springframework.stereotype.Component

@Component
class CalendarAuthManager(
private val calendarService: CalendarService,
private val googleAuth: GoogleAuth,
) {
suspend fun getCredentialData(host: CalendarHost, id: Int, guild: Snowflake?): CredentialData? {
return when (host) {
CalendarHost.GOOGLE -> {
if (guild == null) {
// Internal (owned by DisCal, should never go bad)
googleAuth.requestNewAccessToken(id)
} else {
// External (owned by user)
val calendar = calendarService.getCalendar(guild, id) ?: return null
googleAuth.requestNewAccessToken(calendar)
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package org.dreamexposure.discal.cam.managers

import org.dreamexposure.discal.cam.business.OauthStateService
import org.dreamexposure.discal.cam.discord.DiscordOauthHandler
import org.dreamexposure.discal.cam.json.discal.TokenResponse
import org.dreamexposure.discal.core.business.SessionService
import org.dreamexposure.discal.core.config.Config
import org.dreamexposure.discal.core.crypto.KeyGenerator
import org.dreamexposure.discal.core.`object`.WebSession
import org.dreamexposure.discal.core.utils.GlobalVal.discordApiUrl
import org.springframework.http.HttpStatus
import org.springframework.stereotype.Component
import org.springframework.web.server.ResponseStatusException
import java.net.URLEncoder
import java.nio.charset.Charset.defaultCharset

@Component
class DiscordOauthManager(
private val sessionService: SessionService,
private val oauthStateService: OauthStateService,
private val discordOauthHandler: DiscordOauthHandler,
) {
private final val redirectUrl = Config.URL_DISCORD_REDIRECT.getString()
private final val clientId = Config.DISCORD_APP_ID.getString()

private final val scopes = URLEncoder.encode("identify guilds", defaultCharset())
private final val encodedRedirectUrl = URLEncoder.encode(redirectUrl, defaultCharset())
private final val oauthLinkWithoutState = "$discordApiUrl/oauth2/authorize?client_id=$clientId&redirect_uri=$encodedRedirectUrl&response_type=code&scope=$scopes&prompt=none"

suspend fun getOauthLinkForLogin(): String {
val state = oauthStateService.generateState()

return "$oauthLinkWithoutState&state=$state"
}

suspend fun handleLogout(token: String) {
sessionService.deleteSession(token)
}

suspend fun handleCodeExchange(state: String, code: String): TokenResponse {
// Validate state
if (!oauthStateService.validateState(state)) {
// State invalid - 400
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid state")
}

val dTokens = discordOauthHandler.doTokenExchange(code)
val authInfo = discordOauthHandler.getOauthInfo(dTokens.accessToken)
val apiToken = KeyGenerator.csRandomAlphaNumericString(64)
val session = WebSession(
apiToken,
authInfo.user!!.id,
accessToken = dTokens.accessToken,
refreshToken = dTokens.refreshToken
)

sessionService.removeAndInsertSession(session)

return TokenResponse(session.token, session.expiresAt, authInfo.user)

TODO()
}
}

0 comments on commit ab93eec

Please sign in to comment.