diff --git a/app/src/main/java/ru/myitschool/work/data/dto/AuthRequestDto.kt b/app/src/main/java/ru/myitschool/work/data/dto/AuthRequestDto.kt new file mode 100644 index 0000000..8c877e9 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/data/dto/AuthRequestDto.kt @@ -0,0 +1,20 @@ +package ru.myitschool.work.data.dto + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class AuthRequestDto( + @SerialName("login") + val login: String, + @SerialName("password") + val password: String, +) + +@Serializable +data class AuthResponseDto( + @SerialName("token") + val token: String, + @SerialName("expired") + val expired: Int, +) \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/data/repo/AuthRepository.kt b/app/src/main/java/ru/myitschool/work/data/repo/AuthRepository.kt index e4126dd..f6240b2 100644 --- a/app/src/main/java/ru/myitschool/work/data/repo/AuthRepository.kt +++ b/app/src/main/java/ru/myitschool/work/data/repo/AuthRepository.kt @@ -8,41 +8,42 @@ import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.preferencesDataStore import kotlinx.coroutines.flow.firstOrNull import ru.myitschool.work.App +import ru.myitschool.work.data.dto.AuthRequestDto +import ru.myitschool.work.data.dto.AuthResponseDto import ru.myitschool.work.data.source.NetworkDataSource object AuthRepository { private const val STORE = "AUTH-STORE" - private const val CODE_KEY = "CODE" + private const val TOKEN_KEY = "TOKEN" - private var codeCache: String? = null + private var tokenCache: String? = null - suspend fun checkAndSave(text: String): Result { - return NetworkDataSource.checkAuth(text).onSuccess { success -> - if (success) { - codeCache = text - App.context.userDataStore.edit { preferences -> - val prefKey = stringPreferencesKey(CODE_KEY) - preferences[prefKey] = text - } + suspend fun checkAndSave(login: String, password: String): Result { + val data = AuthRequestDto(login=login, password=password) + return NetworkDataSource.checkAuth(data).onSuccess { success -> + tokenCache = success.token + App.context.userDataStore.edit { preferences -> + val prefKey = stringPreferencesKey(TOKEN_KEY) + preferences[prefKey] = success.token } } } - suspend fun getCode(): String? { - if (codeCache == null) { - codeCache = App.context.userDataStore.data + suspend fun getToken(): String? { + if (tokenCache == null) { + tokenCache = App.context.userDataStore.data .firstOrNull() ?.let { preferences -> - preferences[stringPreferencesKey(CODE_KEY)] + preferences[stringPreferencesKey(TOKEN_KEY)] } } - return codeCache + return tokenCache } suspend fun logout() { - codeCache = null + tokenCache = null App.context.userDataStore.edit { preferences -> - val prefKey = stringPreferencesKey(CODE_KEY) + val prefKey = stringPreferencesKey(TOKEN_KEY) preferences.remove(prefKey) } } diff --git a/app/src/main/java/ru/myitschool/work/data/repo/BookRepository.kt b/app/src/main/java/ru/myitschool/work/data/repo/BookRepository.kt index ea1c581..f60d556 100644 --- a/app/src/main/java/ru/myitschool/work/data/repo/BookRepository.kt +++ b/app/src/main/java/ru/myitschool/work/data/repo/BookRepository.kt @@ -10,8 +10,8 @@ class BookRepository( private val authRepository: AuthRepository ) { suspend fun getInfo(): Result { - val code = authRepository.getCode() ?: return getNoAuthResult() - return NetworkDataSource.getInfo(code).mapCatching { dto -> + val token = authRepository.getToken() ?: return getNoAuthResult() + return NetworkDataSource.getInfo(token).mapCatching { dto -> MainInfoEntity( name = dto.name ?: error("Name is null"), photoUrl = dto.photoUrl ?: error("Photo url is null"), @@ -26,8 +26,8 @@ class BookRepository( } suspend fun getBookingInfo(): Result> { - val code = authRepository.getCode() ?: return getNoAuthResult() - return NetworkDataSource.getBooking(code).mapCatching { dto -> + val token = authRepository.getToken() ?: return getNoAuthResult() + return NetworkDataSource.getBooking(token).mapCatching { dto -> dto?.map { (date, places) -> BookingData( date = date, @@ -43,9 +43,9 @@ class BookRepository( } suspend fun sendBook(data: BookRequestData): Result { - val code = authRepository.getCode() ?: return getNoAuthResult() + val token = authRepository.getToken() ?: return getNoAuthResult() val dto = BookRequestDto(data.date, data.placeId) - return NetworkDataSource.addBook(code, dto) + return NetworkDataSource.addBook(token, dto) } private fun getNoAuthResult() = Result.failure( IllegalStateException("No auth") diff --git a/app/src/main/java/ru/myitschool/work/data/source/NetworkDataSource.kt b/app/src/main/java/ru/myitschool/work/data/source/NetworkDataSource.kt index 85387ac..d78e716 100644 --- a/app/src/main/java/ru/myitschool/work/data/source/NetworkDataSource.kt +++ b/app/src/main/java/ru/myitschool/work/data/source/NetworkDataSource.kt @@ -5,6 +5,7 @@ import io.ktor.client.call.body import io.ktor.client.engine.cio.CIO import io.ktor.client.plugins.contentnegotiation.ContentNegotiation import io.ktor.client.request.get +import io.ktor.client.request.headers import io.ktor.client.request.post import io.ktor.client.request.setBody import io.ktor.client.statement.bodyAsText @@ -16,6 +17,8 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import kotlinx.serialization.json.Json import ru.myitschool.work.core.Constants +import ru.myitschool.work.data.dto.AuthRequestDto +import ru.myitschool.work.data.dto.AuthResponseDto import ru.myitschool.work.data.dto.PlaceDto import ru.myitschool.work.data.dto.BookRequestDto import ru.myitschool.work.data.dto.UserDto @@ -36,20 +39,27 @@ object NetworkDataSource { } } - suspend fun checkAuth(code: String): Result = withContext(Dispatchers.IO) { + suspend fun checkAuth(data: AuthRequestDto): Result = withContext(Dispatchers.IO) { return@withContext runCatching { - val response = client.get(getUrl(code, Constants.AUTH_URL)) + val response = client.post(getUrl(Constants.BOOK_URL)) { + contentType(ContentType.Application.Json) + setBody(data) + } when (response.status) { - HttpStatusCode.OK -> true - else -> false + HttpStatusCode.OK -> response.body() + else -> error(response.bodyAsText()) } } } - suspend fun getInfo(code: String): Result = withContext(Dispatchers.IO) { + suspend fun getInfo(token: String): Result = withContext(Dispatchers.IO) { return@withContext runCatching { - println("!!!!!!!!!!!!!! getInfo $code") - val response = client.get(getUrl(code, Constants.INFO_URL)) + println("!!!!!!!!!!!!!! getInfo") + val response = client.get(getUrl(Constants.INFO_URL)) { + headers { + append("Authorization", "Bearer $token") + } + } if (response.status == HttpStatusCode.OK) { println("!!!!!!!!!!!!!! getInfo OK ${response.bodyAsText()}") response.body() @@ -60,9 +70,13 @@ object NetworkDataSource { } } - suspend fun getBooking(code: String): Result>?> = withContext(Dispatchers.IO) { + suspend fun getBooking(token: String): Result>?> = withContext(Dispatchers.IO) { return@withContext runCatching { - val response = client.get(getUrl(code, Constants.BOOKING_URL)) + val response = client.get(getUrl(Constants.BOOKING_URL)) { + headers { + append("Authorization", "Bearer $token") + } + } if (response.status == HttpStatusCode.OK) { response.body>>() } else { @@ -71,11 +85,14 @@ object NetworkDataSource { } } - suspend fun addBook(code: String, data: BookRequestDto): Result = withContext(Dispatchers.IO) { + suspend fun addBook(token: String, data: BookRequestDto): Result = withContext(Dispatchers.IO) { return@withContext runCatching { - val response = client.post(getUrl(code, Constants.BOOK_URL)) { + val response = client.post(getUrl(Constants.BOOK_URL)) { contentType(ContentType.Application.Json) setBody(data) + headers { + append("Authorization", "Bearer $token") + } } when (response.status) { HttpStatusCode.Created -> true @@ -85,5 +102,5 @@ object NetworkDataSource { } } - private fun getUrl(code: String, targetUrl: String) = "${Constants.HOST}/api/$code$targetUrl" + private fun getUrl(targetUrl: String) = "${Constants.HOST}/api/$targetUrl" } \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/domain/auth/CheckAndSaveAuthCodeUseCase.kt b/app/src/main/java/ru/myitschool/work/domain/auth/CheckAndSaveAuthCodeUseCase.kt index 012fb6f..d246c4f 100644 --- a/app/src/main/java/ru/myitschool/work/domain/auth/CheckAndSaveAuthCodeUseCase.kt +++ b/app/src/main/java/ru/myitschool/work/domain/auth/CheckAndSaveAuthCodeUseCase.kt @@ -1,15 +1,15 @@ package ru.myitschool.work.domain.auth +import ru.myitschool.work.data.dto.AuthResponseDto import ru.myitschool.work.data.repo.AuthRepository class CheckAndSaveAuthCodeUseCase( private val repository: AuthRepository ) { suspend operator fun invoke( - text: String - ): Result { - return repository.checkAndSave(text).mapCatching { success -> - if (!success) error("Code is incorrect") - } + login: String, + password: String + ): Result { + return repository.checkAndSave(login, password) } } \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/domain/auth/CheckCodeFormatUseCase.kt b/app/src/main/java/ru/myitschool/work/domain/auth/CheckCodeFormatUseCase.kt index fe291a0..c12813f 100644 --- a/app/src/main/java/ru/myitschool/work/domain/auth/CheckCodeFormatUseCase.kt +++ b/app/src/main/java/ru/myitschool/work/domain/auth/CheckCodeFormatUseCase.kt @@ -2,11 +2,18 @@ package ru.myitschool.work.domain.auth class CheckCodeFormatUseCase { operator fun invoke( - text: String + login: String, password: String ): Boolean { - return text.length == 4 && text.all { char -> - char.isLetterOrDigit() && - ((char >= 'A' && char <= 'Z') || (char >= 'a' && char <= 'z') || char.isDigit()) + val passwordList = password.toList() + var passwordCorrect = true + for (i in 1..(passwordList.size - 3)) { + val s = StringBuilder().append(passwordList[i]).append(passwordList[i+1]).append(passwordList[i+2]).toString() + if (login.contains(s)) { + passwordCorrect = false + } } + return login.isNotEmpty() && password.length >= 8 && passwordCorrect && login.all { char -> + char.isLetterOrDigit() && ((char >= 'A' && char <= 'Z') || (char >= 'a' && char <= 'z') || char.isDigit()) + } && password.all({ char -> password.count { it == char } < 3 }) && password.count { it == '!' } + password.count { it == '@' } + password.count { it == '#' } + password.count { it == '"' } + password.count { it == '№' } + password.count { it == ';' } + password.count { it == '$' } + password.count { it == '%' } + password.count { it == '^' } + password.count { it == ':' } + password.count { it == '&' } + password.count { it == '?' } + password.count { it == '*' } >= 1 } } \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/domain/auth/GetCodeUseCase.kt b/app/src/main/java/ru/myitschool/work/domain/auth/GetCodeUseCase.kt index a3c22b8..f3a3517 100644 --- a/app/src/main/java/ru/myitschool/work/domain/auth/GetCodeUseCase.kt +++ b/app/src/main/java/ru/myitschool/work/domain/auth/GetCodeUseCase.kt @@ -6,6 +6,6 @@ class GetCodeUseCase( private val repository: AuthRepository ) { suspend operator fun invoke(): String? { - return repository.getCode() + return repository.getToken() } } \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/ui/screen/auth/AuthIntent.kt b/app/src/main/java/ru/myitschool/work/ui/screen/auth/AuthIntent.kt index 74f200a..b4aec57 100644 --- a/app/src/main/java/ru/myitschool/work/ui/screen/auth/AuthIntent.kt +++ b/app/src/main/java/ru/myitschool/work/ui/screen/auth/AuthIntent.kt @@ -1,6 +1,6 @@ package ru.myitschool.work.ui.screen.auth sealed interface AuthIntent { - data class Send(val text: String): AuthIntent - data class TextInput(val text: String): AuthIntent + data class Send(val login: String, val password: String): AuthIntent + data class TextInput(val login: String, val password: String): AuthIntent } \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/ui/screen/auth/AuthViewModel.kt b/app/src/main/java/ru/myitschool/work/ui/screen/auth/AuthViewModel.kt index c28f5cd..8ec6f24 100644 --- a/app/src/main/java/ru/myitschool/work/ui/screen/auth/AuthViewModel.kt +++ b/app/src/main/java/ru/myitschool/work/ui/screen/auth/AuthViewModel.kt @@ -1,5 +1,6 @@ package ru.myitschool.work.ui.screen.auth +import android.os.CountDownTimer import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.flow.MutableSharedFlow @@ -28,28 +29,69 @@ class AuthViewModel : ViewModel() { private val _actionFlow: MutableSharedFlow = MutableSharedFlow() val actionFlow: SharedFlow = _actionFlow + private var authTries: Int = 0 + private var timerSecs: Int = 0 + fun onIntent(intent: AuthIntent) { when (intent) { is AuthIntent.Send -> { - viewModelScope.launch { - checkAndSaveAuthCodeUseCase.invoke(intent.text).fold( - onSuccess = { - _actionFlow.emit(AuthAction.Open(MainScreenDestination)) - }, - onFailure = { error -> + if (authTries >= 5) { + timerSecs = 60 + val timer = object : CountDownTimer((timerSecs * 1000).toLong(), 1000) { + override fun onTick(millisUntilFinished: Long) { + timerSecs -= 1 updateStateIfData { oldState -> oldState.copy( - error = error.message + error = "Слишком много попыток входа, попробуйте через $timerSecs секунд" ) } } - ) + + override fun onFinish() { + authTries = 0 + updateStateIfData { oldState -> + oldState.copy( + isEnabledSend = checkCodeFormatUseCase.invoke( + login = intent.login, + password = intent.password + ), + error = null + ) + } + } + } + timer.start() + updateStateIfData { oldState -> + oldState.copy( + error = "Слишком много попыток входа, попробуйте через $timerSecs секунд" + ) + } + } else { + viewModelScope.launch { + checkAndSaveAuthCodeUseCase.invoke(intent.login, intent.password).fold( + onSuccess = { + _actionFlow.emit(AuthAction.Open(MainScreenDestination)) + }, + onFailure = { error -> + authTries += 1 + updateStateIfData { oldState -> + oldState.copy( + error = error.message + ) + } + } + ) + } } } + is AuthIntent.TextInput -> { updateStateIfData { oldState -> oldState.copy( - isEnabledSend = checkCodeFormatUseCase.invoke(intent.text), + isEnabledSend = checkCodeFormatUseCase.invoke( + login = intent.login, + password = intent.password + ), error = null ) }