add UserInfoCache
This commit is contained in:
+4
-3
@@ -44,8 +44,8 @@ class RedisCacheStorage(
|
||||
return mutex.withLock {
|
||||
try {
|
||||
read(key, serializer)?.let { return@withLock it }
|
||||
factory().also {
|
||||
write(key, it, serializer, ttl)
|
||||
withContext(dispatcherProvider.io) {
|
||||
factory().also { write(key, it, serializer, ttl) }
|
||||
}
|
||||
} finally {
|
||||
locks.remove(key, mutex)
|
||||
@@ -55,10 +55,11 @@ class RedisCacheStorage(
|
||||
|
||||
private suspend fun <T> write(
|
||||
key: String,
|
||||
value: T,
|
||||
value: T?,
|
||||
serializer: KSerializer<T>,
|
||||
duration: Duration
|
||||
) {
|
||||
value ?: return
|
||||
val rawValue = json.encodeToString(serializer, value)
|
||||
val args = SetArgs().ex(duration.toJavaDuration())
|
||||
getCommands().set(key, rawValue, args)
|
||||
|
||||
+22
-15
@@ -30,7 +30,8 @@ class AuthTokenRepositoryFactory(
|
||||
private val loginVerifier: LoginVerifier,
|
||||
private val log: Log,
|
||||
private val stringFetcher: StringFetcher,
|
||||
private val envFetcher: EnvFetcher
|
||||
private val envFetcher: EnvFetcher,
|
||||
private val userInfoCache: UserInfoCache
|
||||
) {
|
||||
|
||||
fun create(userId: Long = -1): AuthTokenRepository {
|
||||
@@ -42,7 +43,8 @@ class AuthTokenRepositoryFactory(
|
||||
userId,
|
||||
log,
|
||||
stringFetcher,
|
||||
envFetcher
|
||||
envFetcher,
|
||||
userInfoCache
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -55,7 +57,8 @@ class BackendAuthTokenRepository(
|
||||
private val userId: Long,
|
||||
private val log: Log,
|
||||
private val stringFetcher: StringFetcher,
|
||||
private val envFetcher: EnvFetcher
|
||||
private val envFetcher: EnvFetcher,
|
||||
private val userInfoCache: UserInfoCache
|
||||
) : AuthTokenRepository {
|
||||
private val random = SecureRandom()
|
||||
|
||||
@@ -69,10 +72,13 @@ class BackendAuthTokenRepository(
|
||||
|
||||
private suspend fun registerInternal(loginInfo: LoginInfo): TokenInfo {
|
||||
return withContext(dispatcherProvider.io) {
|
||||
val user = db.usersQueries.selectUserByLogin(loginInfo.login).executeAsOneOrNull()
|
||||
val user = userInfoCache.query(loginInfo.login)
|
||||
if (user != null) throw BadRequestException(stringFetcher.get(StringResource.ALREADY_REGISTERED))
|
||||
db.usersQueries.addUser(
|
||||
loginInfo.login, loginInfo.passwordHash, System.currentTimeMillis(), null
|
||||
loginInfo.login,
|
||||
loginInfo.passwordHash,
|
||||
System.currentTimeMillis(),
|
||||
null
|
||||
)
|
||||
TokenInfo("")
|
||||
}
|
||||
@@ -80,9 +86,9 @@ class BackendAuthTokenRepository(
|
||||
|
||||
override suspend fun login(loginInfo: LoginInfo): TokenInfo =
|
||||
withContext(dispatcherProvider.io) {
|
||||
val userInfo = db.usersQueries.selectUserByLogin(loginInfo.login).executeAsOneOrNull()
|
||||
val userInfo = userInfoCache.query(loginInfo.login)
|
||||
?: throw BadRequestException(stringFetcher.get(StringResource.UNKNOWN_USER))
|
||||
if (loginInfo.passwordHash != userInfo.password_hash) {
|
||||
if (loginInfo.passwordHash != userInfo.passwordHash) {
|
||||
throw BadRequestException(stringFetcher.get(StringResource.UNKNOWN_USER))
|
||||
}
|
||||
create(loginInfo.login)
|
||||
@@ -92,7 +98,7 @@ class BackendAuthTokenRepository(
|
||||
val info = db.refresh_tokensQueries.selectByHash(refresh.token.toHash())
|
||||
.executeAsOneOrNull()
|
||||
?: throw BadRequestException("Token not found")
|
||||
val userInfo = db.usersQueries.selectUserByUserId(info.user_id).executeAsOneOrNull()
|
||||
val userInfo = userInfoCache.query(info.user_id)
|
||||
?: throw BadRequestException("User not found")
|
||||
val newTokens = create(userInfo.login)
|
||||
revokeInternal(refresh)
|
||||
@@ -126,34 +132,35 @@ class BackendAuthTokenRepository(
|
||||
|
||||
private suspend fun create(login: String): TokenInfo = withContext(dispatcherProvider.io) {
|
||||
loginVerifier.verify(login)
|
||||
val userInfo = db.usersQueries.selectUserByLogin(login).executeAsOneOrNull()
|
||||
val userInfo = userInfoCache.query(login)
|
||||
?: throw BadRequestException(stringFetcher.get(StringResource.UNKNOWN_USER))
|
||||
val rsp = jwtPreparer.prepare(login, userInfo.user_id)
|
||||
val rsp = jwtPreparer.prepare(login, userInfo.userId)
|
||||
val refresh = ByteArray(32)
|
||||
random.nextBytes(refresh)
|
||||
val refreshStr = refresh.toHexString()
|
||||
db.refresh_tokensQueries.insertToken(
|
||||
userInfo.user_id,
|
||||
userInfo.userId,
|
||||
refreshStr.toHash(),
|
||||
System.currentTimeMillis() + TimeUnit.DAYS.toMillis(30)
|
||||
).executeAsOneOrNull()
|
||||
log.d(
|
||||
"BackendAuthTokenRepository",
|
||||
"${refreshStr.toHash()} created for user ${userInfo.user_id}"
|
||||
"${refreshStr.toHash()} created for user ${userInfo.userId}"
|
||||
)
|
||||
TokenInfo(rsp, refreshStr)
|
||||
}
|
||||
|
||||
override suspend fun changePassword(request: ChangePasswordRequest): Unit =
|
||||
withContext(dispatcherProvider.io) {
|
||||
val user = db.usersQueries.selectUserByUserId(userId).executeAsOneOrNull()
|
||||
val user = userInfoCache.query(userId)
|
||||
?: throw BadRequestException(stringFetcher.get(StringResource.UNKNOWN_USER))
|
||||
if (user.password_hash != request.oldPasswordHash) {
|
||||
if (user.passwordHash != request.oldPasswordHash) {
|
||||
throw BadRequestException(stringFetcher.get(StringResource.INVALID_PASSWORD))
|
||||
} else if (user.password_hash == request.newPasswordHash) {
|
||||
} else if (user.passwordHash == request.newPasswordHash) {
|
||||
throw BadRequestException(stringFetcher.get(StringResource.NO_CHANGES_PASS))
|
||||
}
|
||||
db.usersQueries.updatePassword(request.newPasswordHash, userId).await()
|
||||
userInfoCache.delete(userId)
|
||||
}
|
||||
|
||||
private fun String.toHash(): String {
|
||||
|
||||
+3
-5
@@ -5,17 +5,15 @@ import ru.shadowsparky.backend.data.StringFetcher
|
||||
import ru.shadowsparky.backend.data.StringResource
|
||||
import ru.shadowsparky.backend.domain.LoginVerifier
|
||||
import ru.shadowsparky.backend.domain.VerifyTokenException
|
||||
import ru.shadowsparky.vbox.backend.AppDatabase
|
||||
import ru.shadowsparky.vbox.shared.domain.FLAG_USER_BLOCKED
|
||||
|
||||
@Factory
|
||||
class BackendLoginVerifier(
|
||||
private val db: AppDatabase,
|
||||
private val stringFetcher: StringFetcher
|
||||
private val stringFetcher: StringFetcher,
|
||||
private val userInfoCache: UserInfoCache
|
||||
) : LoginVerifier {
|
||||
override suspend fun verify(login: String) {
|
||||
val user = db.usersQueries.selectUserByLogin(login)
|
||||
.executeAsOneOrNull()
|
||||
val user = userInfoCache.query(login)
|
||||
?: throw VerifyTokenException(stringFetcher.get(StringResource.UNKNOWN_USER))
|
||||
user.flags?.let {
|
||||
if ((it and FLAG_USER_BLOCKED) != 0) {
|
||||
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
package ru.shadowsparky.vbox.backend.data.auth
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.koin.core.annotation.Factory
|
||||
import ru.shadowsparky.vbox.backend.AppDatabase
|
||||
import ru.shadowsparky.vbox.backend.domain.CacheStorage
|
||||
import ru.shadowsparky.vbox.backend.domain.getOrCreate
|
||||
import ru.shadowsparky.vbox.server.User
|
||||
import kotlin.time.Duration.Companion.days
|
||||
|
||||
@Factory
|
||||
class UserInfoCache(
|
||||
private val db: AppDatabase,
|
||||
private val cacheStorage: CacheStorage
|
||||
) {
|
||||
private val ttl = 3.days
|
||||
|
||||
suspend fun query(login: String): UserEntry? {
|
||||
return cacheStorage.getOrCreate("$PREFIX:login:$login", ttl) {
|
||||
db.usersQueries.selectUserByLogin(login)
|
||||
.executeAsOneOrNull()
|
||||
?.toUserEntry()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun query(userId: Long): UserEntry? {
|
||||
return cacheStorage.getOrCreate("$PREFIX:id:$userId", ttl) {
|
||||
db.usersQueries.selectUserByUserId(userId)
|
||||
.executeAsOneOrNull()
|
||||
?.toUserEntry()
|
||||
}
|
||||
}
|
||||
|
||||
private fun User.toUserEntry(): UserEntry {
|
||||
return UserEntry(login, user_id, password_hash, flags)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class UserEntry(
|
||||
val login: String,
|
||||
val userId: Long,
|
||||
val passwordHash: String,
|
||||
val flags: Int?
|
||||
)
|
||||
|
||||
suspend fun delete(userId: Long) {
|
||||
val login = query(userId)?.login
|
||||
cacheStorage.delete("$PREFIX:id:$userId")
|
||||
login?.let { cacheStorage.delete("$PREFIX:login:$login") }
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val PREFIX = "login_storage"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user