Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package co.nilin.opex.auth.data

data class ActionCache(
val actionType: ActionType,
val remainingAttempts: Int,
)

enum class ActionType {
REGISTER, FORGET, LOGIN
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ data class PasswordFlowTokenRequest(
val otp: String?,
val rememberMe: Boolean = true,
val captchaType: CaptchaType? = CaptchaType.INTERNAL,
val captchaCode: String,
val captchaCode: String?,
)

data class RefreshTokenRequest(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ data class RegisterUserRequest(
val firstName: String? = null,
val lastName: String? = null,
val captchaType: CaptchaType? = CaptchaType.INTERNAL,
val captchaCode: String,
val captchaCode: String?,
)

data class VerifyOTPRequest(
Expand Down Expand Up @@ -59,5 +59,5 @@ data class ConfirmForgetRequest(
data class ForgotPasswordRequest(
val username: String,
val captchaType: CaptchaType? = CaptchaType.INTERNAL,
val captchaCode: String,
val captchaCode: String?,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package co.nilin.opex.auth.service

import co.nilin.opex.auth.data.ActionCache
import co.nilin.opex.auth.data.ActionType
import co.nilin.opex.auth.model.CaptchaType
import co.nilin.opex.auth.proxy.CaptchaProxy
import co.nilin.opex.auth.utils.CacheManager
import co.nilin.opex.common.OpexError
import org.springframework.stereotype.Service
import java.util.concurrent.TimeUnit

@Service
class CaptchaHandler(
private val cacheManager: CacheManager<String, ActionCache>,
private val captchaProxy: CaptchaProxy
) {
suspend fun validateCaptchaWithActionCache(
username: String,
captchaCode: String?,
captchaType: CaptchaType?,
action: ActionType,
maxAttempts: Int = 3,
expireTimeMinutes: Long = 10
) {
val cache = cacheManager.get(username)

if (cache == null || cache.actionType != action || cache.remainingAttempts <= 0) {
captchaProxy.validateCaptcha(captchaCode ?: throw OpexError.CaptchaRequired.exception(), captchaType ?: CaptchaType.INTERNAL)
cacheManager.put(username, ActionCache(action, maxAttempts), expireTimeMinutes, TimeUnit.MINUTES)
return
}
cacheManager.put(
username,
cache.copy(remainingAttempts = cache.remainingAttempts - 1),
expireTimeMinutes,
TimeUnit.MINUTES
)
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package co.nilin.opex.auth.service

import co.nilin.opex.auth.data.ActionType
import co.nilin.opex.auth.model.*
import co.nilin.opex.auth.proxy.CaptchaProxy
import co.nilin.opex.auth.proxy.GoogleProxy
import co.nilin.opex.auth.proxy.KeycloakProxy
import co.nilin.opex.auth.proxy.OTPProxy
Expand All @@ -13,11 +13,16 @@ class TokenService(
private val otpProxy: OTPProxy,
private val keycloakProxy: KeycloakProxy,
private val googleProxy: GoogleProxy,
private val captchaProxy: CaptchaProxy,
private val captchaHandler: CaptchaHandler,
) {

suspend fun getToken(request: PasswordFlowTokenRequest): TokenResponse {
captchaProxy.validateCaptcha(request.captchaCode, request.captchaType ?: CaptchaType.INTERNAL)
captchaHandler.validateCaptchaWithActionCache(
username = request.username,
captchaCode = request.captchaCode,
captchaType = request.captchaType,
action = ActionType.LOGIN
)
val username = Username.create(request.username)
val user = keycloakProxy.findUserByUsername(username) ?: throw OpexError.UserNotFound.exception()

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package co.nilin.opex.auth.service

import co.nilin.opex.auth.data.ActionType
import co.nilin.opex.auth.data.ActiveSession
import co.nilin.opex.auth.data.UserCreatedEvent
import co.nilin.opex.auth.kafka.AuthEventProducer
import co.nilin.opex.auth.model.*
import co.nilin.opex.auth.proxy.CaptchaProxy
import co.nilin.opex.auth.proxy.GoogleProxy
import co.nilin.opex.auth.proxy.KeycloakProxy
import co.nilin.opex.auth.proxy.OTPProxy
Expand All @@ -26,15 +26,20 @@ class UserService(
private val googleProxy: GoogleProxy,
private val privateKey: PrivateKey,
private val publicKey: PublicKey,
private val captchaProxy: CaptchaProxy,
private val authProducer: AuthEventProducer
private val authProducer: AuthEventProducer,
private val captchaHandler: CaptchaHandler
) {

private val logger by LoggerDelegate()

//TODO IMPORTANT: remove in production
suspend fun registerUser(request: RegisterUserRequest): String {
captchaProxy.validateCaptcha(request.captchaCode, request.captchaType ?: CaptchaType.INTERNAL)
captchaHandler.validateCaptchaWithActionCache(
username = request.username,
captchaCode = request.captchaCode,
captchaType = request.captchaType,
action = ActionType.REGISTER
)
val username = Username.create(request.username)
val userStatus = isUserDuplicate(username)

Expand Down Expand Up @@ -106,7 +111,12 @@ class UserService(
}

suspend fun forgetPassword(request: ForgotPasswordRequest): String {
captchaProxy.validateCaptcha(request.captchaCode, request.captchaType ?: CaptchaType.INTERNAL)
captchaHandler.validateCaptchaWithActionCache(
username = request.username,
captchaCode = request.captchaCode,
captchaType = request.captchaType,
action = ActionType.FORGET
)
val uName = Username.create(request.username)
val user = keycloakProxy.findUserByUsername(uName) ?: return null ?: ""
//TODO IMPORTANT: remove in production
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package co.nilin.opex.auth.utils

import org.springframework.stereotype.Component
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.TimeUnit

@Component
class CacheManager<K, V> {

private val cacheMap = ConcurrentHashMap<K, CacheEntry<V>>()

data class CacheEntry<T>(
val value: T,
val timestamp: Long
)

fun put(key: K, value: V, expirationTime: Long, timeUnit: TimeUnit) {
val expirationMillis = timeUnit.toMillis(expirationTime)
cacheMap[key] = CacheEntry(value, System.currentTimeMillis() + expirationMillis)
}

fun get(key: K): V? {
val entry = cacheMap[key]
return if (entry != null && !isExpired(entry)) {
entry.value
} else {
cacheMap.remove(key)
null
}
}

private fun isExpired(entry: CacheEntry<V>): Boolean {
return System.currentTimeMillis() > entry.timestamp
}

fun remove(key: K) {
cacheMap.remove(key)
}

fun clear() {
cacheMap.clear()
}
}
1 change: 1 addition & 0 deletions common/src/main/kotlin/co/nilin/opex/common/OpexError.kt
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ enum class OpexError(val code: Int, val message: String?, val status: HttpStatus
ExpiredOTP(5017, "OTP is expired", HttpStatus.BAD_REQUEST),
InvalidToken(5018, "Invalid token", HttpStatus.BAD_REQUEST),
InternalIdGenerateFailed(5019, "Internal id generate failed", HttpStatus.INTERNAL_SERVER_ERROR),
CaptchaRequired(5020, "Captcha required", HttpStatus.BAD_REQUEST),



Expand Down
Loading