Skip to content

Commit

Permalink
Its a whole new auth world
Browse files Browse the repository at this point in the history
I redid the new auth system I added a little while back,
 this time it's not spaghetti and should be maintainable
  • Loading branch information
NovaFox161 committed Oct 22, 2023
1 parent adaea8d commit 2d475e0
Show file tree
Hide file tree
Showing 49 changed files with 332 additions and 395 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,24 @@ import org.dreamexposure.discal.core.config.Config
import org.dreamexposure.discal.core.extensions.isExpiredTtl
import org.dreamexposure.discal.core.`object`.new.security.Scope
import org.dreamexposure.discal.core.`object`.new.security.TokenType
import org.springframework.http.HttpStatus
import org.springframework.stereotype.Component

@Component
class SecurityService(
private val sessionService: SessionService,
private val apiKeyService: ApiKeyService,
) {
suspend fun authenticateAndAuthorizeToken(token: String, schemas: List<TokenType>, scopes: List<Scope>): Pair<HttpStatus, String> {
if (!authenticateToken(token)) return Pair(HttpStatus.UNAUTHORIZED, "Unauthenticated")

if (!validateTokenSchema(token, schemas)) return Pair(HttpStatus.UNAUTHORIZED, "Unsupported schema")

if (!authorizeToken(token, scopes)) return Pair(HttpStatus.FORBIDDEN, "Access denied")

return Pair(HttpStatus.OK, "Authorized")
}

suspend fun authenticateToken(token: String): Boolean {
val schema = getSchema(token)
val tokenStr = token.removePrefix(schema.schema)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package org.dreamexposure.discal.cam.business.cronjob

import com.fasterxml.jackson.databind.ObjectMapper
import kotlinx.coroutines.reactor.mono
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
Expand All @@ -12,7 +13,6 @@ import org.dreamexposure.discal.core.`object`.network.discal.InstanceData
import org.dreamexposure.discal.core.`object`.rest.HeartbeatRequest
import org.dreamexposure.discal.core.`object`.rest.HeartbeatType
import org.dreamexposure.discal.core.utils.GlobalVal
import org.dreamexposure.discal.core.utils.GlobalVal.HTTP_CLIENT
import org.dreamexposure.discal.core.utils.GlobalVal.JSON
import org.springframework.boot.ApplicationArguments
import org.springframework.boot.ApplicationRunner
Expand All @@ -23,6 +23,7 @@ import reactor.core.scheduler.Schedulers

@Component
class HeartbeatCronJob(
private val httpClient: OkHttpClient,
private val objectMapper: ObjectMapper,
): ApplicationRunner {
private final val apiUrl = Config.URL_API.getString()
Expand All @@ -39,13 +40,13 @@ class HeartbeatCronJob(
val requestBody = HeartbeatRequest(HeartbeatType.CAM, instanceData = InstanceData())

val request = Request.Builder()
.url("$apiUrl/v2/status/heartbeat")
.url("$apiUrl/v3/status/heartbeat")
.post(objectMapper.writeValueAsString(requestBody).toRequestBody(JSON))
.header("Authorization", Config.SECRET_DISCAL_API_KEY.getString())
.header("Authorization", "Int ${Config.SECRET_DISCAL_API_KEY.getString()}")
.header("Content-Type", "application/json")
.build()

Mono.fromCallable(HTTP_CLIENT.newCall(request)::execute)
Mono.fromCallable(httpClient.newCall(request)::execute)
.map(Response::close)
.subscribeOn(Schedulers.boundedElastic())
.doOnError { LOGGER.error(GlobalVal.DEFAULT, "[Heartbeat] Failed to heartbeat", it) }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.dreamexposure.discal.cam.controllers.v1

import org.dreamexposure.discal.cam.business.SecurityService
import org.dreamexposure.discal.core.annotations.SecurityRequirement
import org.dreamexposure.discal.core.`object`.new.security.Scope.INTERNAL_CAM_VALIDATE_TOKEN
import org.dreamexposure.discal.core.`object`.new.security.TokenType.INTERNAL
import org.dreamexposure.discal.core.`object`.rest.v1.security.ValidateRequest
import org.dreamexposure.discal.core.`object`.rest.v1.security.ValidateResponse
import org.springframework.http.HttpStatus
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController

@RestController
@RequestMapping("/v1/security")
class SecurityController(
private val securityService: SecurityService,
) {
@SecurityRequirement(schemas = [INTERNAL], scopes = [INTERNAL_CAM_VALIDATE_TOKEN])
@PostMapping("/validate", produces = ["application/json"])
suspend fun validate(@RequestBody request: ValidateRequest): ValidateResponse {
val result = securityService.authenticateAndAuthorizeToken(
request.token,
request.schemas,
request.scopes,
)

return ValidateResponse(result.first == HttpStatus.OK, result.first, result.second)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,26 +61,15 @@ class SecurityWebFilter(
return
}

if (!securityService.authenticateToken(authHeader)) {
exchange.response.statusCode = HttpStatus.UNAUTHORIZED
exchange.response.writeJsonString(
objectMapper.writeValueAsString(ErrorResponse("Unauthenticated"))
).awaitFirstOrNull()
return
}

if (!securityService.validateTokenSchema(authHeader, authAnnotation.schemas.toList())) {
exchange.response.statusCode = HttpStatus.UNAUTHORIZED
exchange.response.writeJsonString(
objectMapper.writeValueAsString(ErrorResponse("Unsupported schema"))
).awaitFirstOrNull()
return
}

if (!securityService.authorizeToken(authHeader, authAnnotation.scopes.toList())) {
exchange.response.statusCode = HttpStatus.FORBIDDEN
val result = securityService.authenticateAndAuthorizeToken(
authHeader,
authAnnotation.schemas.toList(),
authAnnotation.scopes.toList()
)
if (result.first != HttpStatus.OK) {
exchange.response.statusCode = result.first
exchange.response.writeJsonString(
objectMapper.writeValueAsString(ErrorResponse("Access denied"))
objectMapper.writeValueAsString(ErrorResponse(result.second))
).awaitFirstOrNull()
return
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package org.dreamexposure.discal.client.business.cronjob

import com.fasterxml.jackson.databind.ObjectMapper
import discord4j.core.GatewayDiscordClient
import kotlinx.coroutines.reactor.awaitSingle
import kotlinx.coroutines.reactor.mono
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import org.dreamexposure.discal.core.config.Config
import org.dreamexposure.discal.core.extensions.asSeconds
import org.dreamexposure.discal.core.logger.LOGGER
import org.dreamexposure.discal.core.`object`.network.discal.BotInstanceData
import org.dreamexposure.discal.core.`object`.rest.HeartbeatRequest
import org.dreamexposure.discal.core.`object`.rest.HeartbeatType
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 reactor.core.scheduler.Schedulers

@Component
class HeartbeatCronJob(
private val discordClient: GatewayDiscordClient,
private val httpClient: OkHttpClient,
private val objectMapper: ObjectMapper,
): ApplicationRunner {
private final val apiUrl = Config.URL_API.getString()

override fun run(args: ApplicationArguments?) {
Flux.interval(Config.HEARTBEAT_INTERVAL.getLong().asSeconds())
.flatMap { heartbeat() }
.doOnError { LOGGER.error(GlobalVal.DEFAULT, "[Heartbeat] Failed to heartbeat", it) }
.onErrorResume { Mono.empty() }
.subscribe()
}

private fun heartbeat() = mono {
val data = BotInstanceData.load(discordClient).awaitSingle()

val requestBody = HeartbeatRequest(HeartbeatType.BOT, botInstanceData = data)
val request = Request.Builder()
.url("$apiUrl/v3/status/heartbeat")
.post(objectMapper.writeValueAsString(requestBody).toRequestBody(GlobalVal.JSON))
.header("Authorization", "Int ${Config.SECRET_DISCAL_API_KEY.getString()}")
.header("Content-Type", "application/json")
.build()

Mono.fromCallable(httpClient.newCall(request)::execute)
.map(Response::close)
.subscribeOn(Schedulers.boundedElastic())
.doOnError { LOGGER.error(GlobalVal.DEFAULT, "[Heartbeat] Failed to heartbeat", it) }
.onErrorResume { Mono.empty() }
.subscribe()
}
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
import discord4j.common.JacksonResources
import okhttp3.OkHttpClient
import org.dreamexposure.discal.core.serializers.DurationMapper
import org.dreamexposure.discal.core.serializers.SnowflakeMapper
import org.springframework.context.annotation.Bean
Expand All @@ -28,4 +29,9 @@ class BeanConfig {
fun handlerMapping(): RequestMappingHandlerMapping {
return RequestMappingHandlerMapping()
}

@Bean
fun httpClient(): OkHttpClient {
return OkHttpClient()
}
}

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ enum class Scope {
CALENDAR_TOKEN_READ,

OAUTH2_DISCORD,

INTERNAL_CAM_VALIDATE_TOKEN,
INTERNAL_HEARTBEAT,
;

companion object {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.dreamexposure.discal.core.`object`.rest.v1.security

import org.dreamexposure.discal.core.`object`.new.security.Scope
import org.dreamexposure.discal.core.`object`.new.security.TokenType

data class ValidateRequest(
val token: String,
val schemas: List<TokenType>,
val scopes: List<Scope>,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.dreamexposure.discal.core.`object`.rest.v1.security

import org.springframework.http.HttpStatus

data class ValidateResponse(
val valid: Boolean,
val code: HttpStatus,
val message: String,
)
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import kotlinx.serialization.encodeToString
import org.dreamexposure.discal.core.exceptions.AccessRevokedException
import org.dreamexposure.discal.core.exceptions.AuthenticationException
import org.dreamexposure.discal.core.exceptions.NotFoundException
import org.dreamexposure.discal.core.exceptions.TeaPotException
import org.dreamexposure.discal.core.logger.LOGGER
import org.dreamexposure.discal.core.`object`.rest.RestError
import org.dreamexposure.discal.core.utils.GlobalVal
Expand Down Expand Up @@ -44,10 +43,6 @@ class GlobalErrorHandler : ErrorWebExceptionHandler {
}
}
}
is TeaPotException -> {
exchange.response.statusCode = HttpStatus.I_AM_A_TEAPOT
RestError.TEAPOT
}
is AuthenticationException -> {
exchange.response.statusCode = HttpStatus.UNAUTHORIZED
RestError.UNAUTHORIZED
Expand Down
Loading

0 comments on commit 2d475e0

Please sign in to comment.