diff --git a/cam/src/main/kotlin/org/dreamexposure/discal/cam/google/GoogleAuth.kt b/cam/src/main/kotlin/org/dreamexposure/discal/cam/google/GoogleAuth.kt index 870a04bd..aef5b6ae 100644 --- a/cam/src/main/kotlin/org/dreamexposure/discal/cam/google/GoogleAuth.kt +++ b/cam/src/main/kotlin/org/dreamexposure/discal/cam/google/GoogleAuth.kt @@ -33,7 +33,6 @@ class GoogleAuth( private val calendarService: CalendarService, private val objectMapper: ObjectMapper, ) { - private final val aes: AESEncryption = AESEncryption(Config.SECRET_GOOGLE_CREDENTIAL_KEY.getString()) suspend fun requestNewAccessToken(calendar: Calendar): CredentialData? { val aes = AESEncryption(calendar.secrets.privateKey) @@ -57,16 +56,13 @@ class GoogleAuth( suspend fun requestNewAccessToken(credentialId: Int): CredentialData { val credential = credentialService.getCredential(credentialId) ?: throw NotFoundException() - if (!credential.expiresAt.isExpiredTtl()) { - val accessToken = aes.decrypt(credential.encryptedAccessToken).awaitSingle() - return CredentialData(accessToken, credential.expiresAt) - } + if (!credential.expiresAt.isExpiredTtl()) return CredentialData(credential.accessToken, credential.expiresAt) + LOGGER.debug("Refreshing access token | credentialId:$credentialId") - val refreshToken = aes.decrypt(credential.encryptedRefreshToken).awaitSingle() - val refreshedCredentialData = doAccessTokenRequest(refreshToken) ?: throw EmptyNotAllowedException() - credential.encryptedAccessToken = aes.encrypt(refreshedCredentialData.accessToken).awaitSingle() + val refreshedCredentialData = doAccessTokenRequest(credential.refreshToken) ?: throw EmptyNotAllowedException() + credential.accessToken = refreshedCredentialData.accessToken credential.expiresAt = refreshedCredentialData.validUntil.minusSeconds(60) // Add a minute of wiggle room credentialService.updateCredential(credential) diff --git a/client/src/main/kotlin/org/dreamexposure/discal/client/service/AnnouncementService.kt b/client/src/main/kotlin/org/dreamexposure/discal/client/service/AnnouncementService.kt index e0307ff8..be94ae97 100644 --- a/client/src/main/kotlin/org/dreamexposure/discal/client/service/AnnouncementService.kt +++ b/client/src/main/kotlin/org/dreamexposure/discal/client/service/AnnouncementService.kt @@ -204,12 +204,14 @@ class AnnouncementService( private fun getEvents(guild: Guild, announcement: Announcement): Flux { val cached = getCached(announcement.guildId) + if (cached.events.contains(announcement.calendarNumber)) + return Flux.fromIterable(cached.events[announcement.calendarNumber]!!) - return if (!cached.events.contains(announcement.calendarNumber)) { - getCalendar(guild, announcement).flatMapMany { - it.getUpcomingEvents(20).cache() - } - } else cached.events[announcement.calendarNumber]!! + return getCalendar(guild, announcement).flatMapMany { + it.getUpcomingEvents(20) + }.collectList() + .doOnNext { cached.events[announcement.calendarNumber] = it } + .flatMapIterable { it } } private fun getCached(guildId: Snowflake): AnnouncementCache { diff --git a/core/src/main/kotlin/org/dreamexposure/discal/core/crypto/AESEncryption.kt b/core/src/main/kotlin/org/dreamexposure/discal/core/crypto/AESEncryption.kt index 9e07bbfa..cd7a5488 100644 --- a/core/src/main/kotlin/org/dreamexposure/discal/core/crypto/AESEncryption.kt +++ b/core/src/main/kotlin/org/dreamexposure/discal/core/crypto/AESEncryption.kt @@ -26,6 +26,7 @@ class AESEncryption(privateKey: String) { } } + @Deprecated("Use #decryptFixed(string) instead") fun encrypt(data: String): Mono { return Mono.fromCallable { this.cipher?.init(Cipher.ENCRYPT_MODE, this.secretKeySpec, this.ivParameterSpec) @@ -39,6 +40,20 @@ class AESEncryption(privateKey: String) { }.subscribeOn(Schedulers.single()).switchIfEmpty(Mono.error(EmptyNotAllowedException())) } + + fun encryptFixed(data: String): String { + return try { + this.cipher?.init(Cipher.ENCRYPT_MODE, this.secretKeySpec, this.ivParameterSpec) + val encrypted = this.cipher?.doFinal(data.toByteArray(StandardCharsets.UTF_8)) + + Base64.encodeBase64String(encrypted) + } catch (ex: Exception) { + LOGGER.error("Encrypt failure", ex) + throw IllegalStateException("Encrypt Failure", ex) + } + } + + @Deprecated("Use #decryptFixed(string) instead") fun decrypt(data: String): Mono { return Mono.fromCallable { this.cipher?.init(Cipher.DECRYPT_MODE, this.secretKeySpec, this.ivParameterSpec) @@ -51,4 +66,16 @@ class AESEncryption(privateKey: String) { Mono.error(IllegalStateException("Decrypt Failure", it)) }.subscribeOn(Schedulers.single()).switchIfEmpty(Mono.error(EmptyNotAllowedException())) } + + fun decryptFixed(data: String): String { + return try { + this.cipher?.init(Cipher.DECRYPT_MODE, this.secretKeySpec, this.ivParameterSpec) + val decrypted = this.cipher?.doFinal(Base64.decodeBase64(data)) + + String(decrypted!!, StandardCharsets.UTF_8) + } catch (ex: Exception) { + LOGGER.error("Decrypt failure", ex) + throw IllegalStateException("Decrypt Failure", ex) + } + } } diff --git a/core/src/main/kotlin/org/dreamexposure/discal/core/object/announcement/AnnouncementCache.kt b/core/src/main/kotlin/org/dreamexposure/discal/core/object/announcement/AnnouncementCache.kt index 2ad07c3d..145314ba 100644 --- a/core/src/main/kotlin/org/dreamexposure/discal/core/object/announcement/AnnouncementCache.kt +++ b/core/src/main/kotlin/org/dreamexposure/discal/core/object/announcement/AnnouncementCache.kt @@ -3,11 +3,10 @@ package org.dreamexposure.discal.core.`object`.announcement import discord4j.common.util.Snowflake import org.dreamexposure.discal.core.entities.Calendar import org.dreamexposure.discal.core.entities.Event -import reactor.core.publisher.Flux import java.util.concurrent.ConcurrentHashMap data class AnnouncementCache( val id: Snowflake, val calendars: ConcurrentHashMap = ConcurrentHashMap(), - val events: ConcurrentHashMap> = ConcurrentHashMap(), + val events: ConcurrentHashMap> = ConcurrentHashMap(), ) diff --git a/core/src/main/kotlin/org/dreamexposure/discal/core/object/new/Calendar.kt b/core/src/main/kotlin/org/dreamexposure/discal/core/object/new/Calendar.kt index 0205cba5..ed7c45e1 100644 --- a/core/src/main/kotlin/org/dreamexposure/discal/core/object/new/Calendar.kt +++ b/core/src/main/kotlin/org/dreamexposure/discal/core/object/new/Calendar.kt @@ -35,8 +35,8 @@ data class Calendar( data class Secrets( val credentialId: Int, val privateKey: String, - val encryptedRefreshToken: String, - var encryptedAccessToken: String, + val encryptedRefreshToken: String, // TODO: Secrets should be unencrypted immediately before/after Db write/read respectively + var encryptedAccessToken: String, // TODO: Secrets should be unencrypted immediately before/after Db write/read respectively var expiresAt: Instant, ) } diff --git a/core/src/main/kotlin/org/dreamexposure/discal/core/object/new/Credential.kt b/core/src/main/kotlin/org/dreamexposure/discal/core/object/new/Credential.kt index 272aa524..cd54c3cf 100644 --- a/core/src/main/kotlin/org/dreamexposure/discal/core/object/new/Credential.kt +++ b/core/src/main/kotlin/org/dreamexposure/discal/core/object/new/Credential.kt @@ -1,19 +1,37 @@ package org.dreamexposure.discal.core.`object`.new +import org.dreamexposure.discal.core.config.Config +import org.dreamexposure.discal.core.crypto.AESEncryption import org.dreamexposure.discal.core.database.CredentialData import org.dreamexposure.discal.core.extensions.asInstantMilli import java.time.Instant data class Credential( val credentialNumber: Int, - var encryptedRefreshToken: String, - var encryptedAccessToken: String, var expiresAt: Instant, + var refreshToken: String, + var accessToken: String, ) { + /** + * Returns the encrypted refresh token, note that this will run the encryption on every access. + */ + val encryptedRefreshToken: String + get() = aes.encryptFixed(refreshToken) + + /** + * Returns the encrypted access token, note that this will run the encryption on every access. + */ + val encryptedAccessToken: String + get() = aes.encryptFixed(accessToken) + constructor(data: CredentialData) : this( credentialNumber = data.credentialNumber, - encryptedRefreshToken = data.refreshToken, - encryptedAccessToken = data.accessToken, expiresAt = data.expiresAt.asInstantMilli(), + refreshToken = aes.decryptFixed(data.refreshToken), + accessToken = aes.decryptFixed(data.accessToken), ) + + companion object { + private val aes = AESEncryption(Config.SECRET_GOOGLE_CREDENTIAL_KEY.getString()) + } }