Skip to content

Commit

Permalink
[WIP] Working on removing the DatabaseManager usage within the securi…
Browse files Browse the repository at this point in the history
…ty API
  • Loading branch information
NovaFox161 committed Aug 28, 2023
1 parent ad43c3e commit 2d292bf
Show file tree
Hide file tree
Showing 22 changed files with 297 additions and 400 deletions.
7 changes: 0 additions & 7 deletions cam/src/main/kotlin/org/dreamexposure/discal/cam/Cam.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package org.dreamexposure.discal.cam

import jakarta.annotation.PreDestroy
import org.dreamexposure.discal.Application
import org.dreamexposure.discal.cam.google.GoogleInternalAuthHandler
import org.dreamexposure.discal.core.config.Config
import org.dreamexposure.discal.core.database.DatabaseManager
import org.dreamexposure.discal.core.logger.LOGGER
Expand All @@ -25,12 +24,6 @@ class Cam {
fun main(args: Array<String>) {
Config.init()

//Handle generating new google auth credentials for discal accounts
if (args.size > 1 && args[0].equals("-forceNewGoogleAuth", true)) {
//This will automatically kill this instance once finished
GoogleInternalAuthHandler.requestCode(args[1].toInt()).subscribe()
}

//Start up spring
try {
SpringApplicationBuilder(Application::class.java)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.dreamexposure.discal.cam.business.cronjob

import kotlinx.coroutines.reactor.mono
import org.dreamexposure.discal.core.business.SessionService
import org.dreamexposure.discal.core.logger.LOGGER
import org.dreamexposure.discal.core.utils.GlobalVal
import org.springframework.boot.ApplicationArguments
import org.springframework.boot.ApplicationRunner
import org.springframework.stereotype.Component
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import java.time.Duration

@Component
class SessionCronJob(
val sessionService: SessionService,
) : ApplicationRunner {
override fun run(args: ApplicationArguments?) {
Flux.interval(Duration.ofHours(1))
.flatMap { justDoIt() }.doOnError {
LOGGER.error(GlobalVal.DEFAULT, "Session cronjob error", it)
}.onErrorResume {
Mono.empty()
}.subscribe()
}

private fun justDoIt() = mono {
LOGGER.debug("Running expired session purge job")

sessionService.deleteExpiredSessions()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ package org.dreamexposure.discal.cam.endpoints.v1

import discord4j.common.util.Snowflake
import org.dreamexposure.discal.cam.google.GoogleAuth
import org.dreamexposure.discal.core.`object`.network.discal.CredentialData
import org.dreamexposure.discal.core.annotations.Authentication
import org.dreamexposure.discal.core.database.DatabaseManager
import org.dreamexposure.discal.core.enums.calendar.CalendarHost
import org.dreamexposure.discal.core.`object`.network.discal.CredentialData
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
Expand All @@ -14,7 +14,9 @@ import reactor.core.publisher.Mono

@RestController
@RequestMapping("/v1/")
class GetEndpoint {
class GetEndpoint(
private val googleAuth: GoogleAuth,
) {

@Authentication(access = Authentication.AccessLevel.ADMIN)
@GetMapping("token", produces = ["application/json"])
Expand All @@ -24,10 +26,10 @@ class GetEndpoint {
CalendarHost.GOOGLE -> {
if (guild == null) {
// Internal (owned by DisCal, should never go bad)
GoogleAuth.requestNewAccessToken(id)
googleAuth.requestNewAccessToken(id)
} else {
// External (owned by user)
DatabaseManager.getCalendar(guild, id).flatMap(GoogleAuth::requestNewAccessToken)
DatabaseManager.getCalendar(guild, id).flatMap(googleAuth::requestNewAccessToken) //TODO: Replace this
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,28 @@
package org.dreamexposure.discal.cam.endpoints.v1.oauth2

import kotlinx.coroutines.reactor.awaitSingle
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.service.StateService
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.database.DatabaseManager
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 reactor.core.publisher.Mono
import java.net.URLEncoder
import java.nio.charset.Charset.defaultCharset

@RestController
@RequestMapping("/oauth2/discord/")
class DiscordOauthEndpoint(
private val stateService: StateService,
private val sessionService: SessionService,
private val discordOauthHandler: DiscordOauthHandler,
) {
private val redirectUrl = Config.URL_DISCORD_REDIRECT.getString()
Expand All @@ -33,45 +34,41 @@ class DiscordOauthEndpoint(

@GetMapping("login")
@Authentication(access = Authentication.AccessLevel.PUBLIC)
fun login(): Mono<LoginResponse> {
fun login(): LoginResponse {
val state = stateService.generateState()

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

return Mono.just(LoginResponse(link))
return LoginResponse(link)
}

@GetMapping("logout")
@Authentication(access = Authentication.AccessLevel.WRITE)
fun logout(@RequestHeader("Authorization") token: String): Mono<Void> {
return DatabaseManager.deleteSession(token).then()
suspend fun logout(@RequestHeader("Authorization") token: String) {
sessionService.deleteSession(token)
}

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

return discordOauthHandler.doTokenExchange(body.code).flatMap { dTokens ->
// request current user info
discordOauthHandler.getOauthInfo(dTokens.accessToken).flatMap { authInfo ->
val apiToken = KeyGenerator.csRandomAlphaNumericString(64)
val dTokens = discordOauthHandler.doTokenExchange(body.code).awaitSingle()
val authInfo = discordOauthHandler.getOauthInfo(dTokens.accessToken).awaitSingle()
val apiToken = KeyGenerator.csRandomAlphaNumericString(64)
val session = WebSession(
apiToken,
authInfo.user!!.id,
accessToken = dTokens.accessToken,
refreshToken = dTokens.refreshToken
)

val session = WebSession(
apiToken,
authInfo.user!!.id,
accessToken = dTokens.accessToken,
refreshToken = dTokens.refreshToken
)
sessionService.removeAndInsertSession(session)

// Save session data then return response
DatabaseManager.removeAndInsertSessionData(session)
.thenReturn(TokenResponse(session.token, session.expiresAt, authInfo.user))
}
}
return TokenResponse(session.token, session.expiresAt, authInfo.user)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ package org.dreamexposure.discal.cam.google

import com.google.api.client.http.HttpStatusCodes.STATUS_CODE_BAD_REQUEST
import com.google.api.client.http.HttpStatusCodes.STATUS_CODE_OK
import kotlinx.coroutines.reactor.mono
import okhttp3.FormBody
import okhttp3.Request
import org.dreamexposure.discal.cam.json.google.ErrorData
import org.dreamexposure.discal.cam.json.google.RefreshData
import org.dreamexposure.discal.core.business.CredentialService
import org.dreamexposure.discal.core.config.Config
import org.dreamexposure.discal.core.crypto.AESEncryption
import org.dreamexposure.discal.core.database.DatabaseManager
Expand All @@ -19,20 +21,24 @@ import org.dreamexposure.discal.core.`object`.network.discal.CredentialData
import org.dreamexposure.discal.core.utils.GlobalVal.DEFAULT
import org.dreamexposure.discal.core.utils.GlobalVal.HTTP_CLIENT
import org.dreamexposure.discal.core.utils.GlobalVal.JSON_FORMAT
import org.springframework.stereotype.Component
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import reactor.core.scheduler.Schedulers
import java.time.Instant
import kotlin.system.exitProcess

@Suppress("BlockingMethodInNonBlockingContext")
object GoogleAuth {
private val CREDENTIALS: Flux<DisCalGoogleCredential>
@Component
class GoogleAuth(
private val credentialService: CredentialService,
) {
private final val CREDENTIALS: Flux<DisCalGoogleCredential>

init {
val credCount = Config.SECRET_GOOGLE_CREDENTIAL_COUNT.getInt()

CREDENTIALS = Flux.range(0, credCount)
.flatMap(DatabaseManager::getCredentialData)
.flatMap { mono { credentialService.getCredential(it) } }
.map(::DisCalGoogleCredential)
.doOnError { exitProcess(1) }
.cache()
Expand All @@ -53,27 +59,27 @@ object GoogleAuth {

aes.encrypt(data.accessToken)
.doOnNext { calendarData.encryptedAccessToken = it }
.then(DatabaseManager.updateCalendar(calendarData).thenReturn(data))
.then(DatabaseManager.updateCalendar(calendarData).thenReturn(data))//TODO: Replace this
}
}
}

fun requestNewAccessToken(credentialId: Int): Mono<CredentialData> {
return CREDENTIALS
.filter { it.credentialData.credentialNumber == credentialId }
.filter { it.credential.credentialNumber == credentialId }
.next()
.switchIfEmpty(Mono.error(NotFoundException()))
.flatMap { credential ->
if (!credential.expired()) {
return@flatMap credential.getAccessToken()
.map { CredentialData(it, credential.credentialData.expiresAt) }
.map { CredentialData(it, credential.credential.expiresAt) }
}

credential.getRefreshToken()
.flatMap(this::doAccessTokenRequest)
.flatMap { credential.setAccessToken(it.accessToken).thenReturn(it) }
.doOnNext { credential.credentialData.expiresAt = it.validUntil }
.flatMap { DatabaseManager.updateCredentialData(credential.credentialData).thenReturn(it) }
.doOnNext { credential.credential.expiresAt = it.validUntil }
.flatMap { DatabaseManager.updateCredentialData(credential.credentialData).thenReturn(it) }//TODO: Replace this
}.switchIfEmpty(Mono.error(EmptyNotAllowedException()))

}
Expand Down

This file was deleted.

Loading

0 comments on commit 2d292bf

Please sign in to comment.