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