Automatic login with JWT and Logout
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
parent
304508fa8c
commit
b575f6e157
@ -0,0 +1,17 @@
|
||||
package fr.iut.alldev.allin.di
|
||||
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import fr.iut.alldev.allin.keystore.AllInKeystoreManager
|
||||
import fr.iut.alldev.allin.keystore.impl.AllInKeystoreManagerImpl
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
abstract class KeystoreModule {
|
||||
@Singleton
|
||||
@Binds
|
||||
abstract fun provideKeystoreManager(allInKeystoreManagerImpl: AllInKeystoreManagerImpl): AllInKeystoreManager
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package fr.iut.alldev.allin.keystore
|
||||
|
||||
import androidx.security.crypto.MasterKeys
|
||||
|
||||
abstract class AllInKeystoreManager {
|
||||
val masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC)
|
||||
|
||||
abstract fun createKeystore()
|
||||
abstract fun putToken(token: String)
|
||||
abstract fun getToken(): String?
|
||||
abstract fun deleteToken()
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package fr.iut.alldev.allin.keystore.impl
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.security.crypto.EncryptedSharedPreferences
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import fr.iut.alldev.allin.keystore.AllInKeystoreManager
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val AUTH_TOKEN_KEY = "auth_token"
|
||||
private const val PREFS_FILE_NAME = "secured_shared_prefs"
|
||||
|
||||
class AllInKeystoreManagerImpl @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
) : AllInKeystoreManager() {
|
||||
|
||||
private var sharedPreferences: SharedPreferences? = null
|
||||
override fun createKeystore() {
|
||||
if (sharedPreferences == null) {
|
||||
sharedPreferences = EncryptedSharedPreferences.create(
|
||||
PREFS_FILE_NAME,
|
||||
masterKeyAlias,
|
||||
context,
|
||||
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
|
||||
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun putToken(token: String) {
|
||||
sharedPreferences?.edit()?.putString(AUTH_TOKEN_KEY, token)?.apply()
|
||||
}
|
||||
|
||||
|
||||
override fun getToken(): String? {
|
||||
return sharedPreferences?.getString(AUTH_TOKEN_KEY, null)
|
||||
}
|
||||
|
||||
override fun deleteToken() {
|
||||
sharedPreferences?.edit()?.putString(AUTH_TOKEN_KEY, null)?.apply()
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package fr.iut.alldev.allin.ui.welcome
|
||||
|
||||
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import fr.iut.alldev.allin.data.repository.UserRepository
|
||||
import fr.iut.alldev.allin.keystore.AllInKeystoreManager
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class WelcomeScreenViewModel @Inject constructor(
|
||||
private val keystoreManager: AllInKeystoreManager,
|
||||
private val userRepository: UserRepository
|
||||
) : ViewModel() {
|
||||
|
||||
var loading = mutableStateOf(false)
|
||||
|
||||
fun tryAutoLogin(onSuccess: () -> Unit) {
|
||||
viewModelScope.launch {
|
||||
loading.value = true
|
||||
keystoreManager.createKeystore()
|
||||
keystoreManager.getToken()?.let { token ->
|
||||
runCatching {
|
||||
userRepository
|
||||
.login(token)
|
||||
?.let { newToken ->
|
||||
keystoreManager.putToken(newToken)
|
||||
}
|
||||
onSuccess()
|
||||
}
|
||||
}
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package fr.iut.alldev.allin.data.api.model
|
||||
|
||||
import androidx.annotation.Keep
|
||||
import fr.iut.alldev.allin.data.model.bet.Bet
|
||||
import fr.iut.alldev.allin.data.model.bet.BetStatus
|
||||
import fr.iut.alldev.allin.data.model.bet.CustomBet
|
||||
import fr.iut.alldev.allin.data.model.bet.YesNoBet
|
||||
import fr.iut.alldev.allin.data.serialization.ZonedDateTimeSerializer
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.time.ZonedDateTime
|
||||
|
||||
@Keep
|
||||
@Serializable
|
||||
data class ResponseBet(
|
||||
val id: Int?,
|
||||
val theme: String,
|
||||
val sentenceBet: String,
|
||||
@Serializable(ZonedDateTimeSerializer::class) val endRegistration: ZonedDateTime,
|
||||
@Serializable(ZonedDateTimeSerializer::class) var endBet: ZonedDateTime,
|
||||
var isPrivate: Boolean,
|
||||
var response: List<String>,
|
||||
val createdBy: String,
|
||||
) {
|
||||
fun toBet(): Bet {
|
||||
if (response.toSet() == setOf("Yes", "No")) {
|
||||
return YesNoBet(
|
||||
theme = theme,
|
||||
phrase = sentenceBet,
|
||||
endRegisterDate = endRegistration,
|
||||
endBetDate = endBet,
|
||||
isPublic = !isPrivate,
|
||||
betStatus = BetStatus.Waiting,
|
||||
creator = createdBy
|
||||
)
|
||||
} else {
|
||||
return CustomBet(
|
||||
theme = theme,
|
||||
phrase = sentenceBet,
|
||||
endRegisterDate = endRegistration,
|
||||
endBetDate = endBet,
|
||||
isPublic = !isPrivate,
|
||||
betStatus = BetStatus.Waiting,
|
||||
creator = createdBy,
|
||||
possibleAnswers = response.toSet()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,12 +1,29 @@
|
||||
package fr.iut.alldev.allin.data.model.bet
|
||||
|
||||
import fr.iut.alldev.allin.data.api.model.ResponseBet
|
||||
import java.time.ZonedDateTime
|
||||
|
||||
abstract class Bet(
|
||||
open val id: Int? = null,
|
||||
open val creator: String,
|
||||
open val theme: String,
|
||||
open val phrase: String,
|
||||
open val endRegisterDate: ZonedDateTime,
|
||||
open val endBetDate: ZonedDateTime,
|
||||
open val isPublic: Boolean,
|
||||
open val betStatus: BetStatus,
|
||||
) {
|
||||
abstract fun getResponses(): List<String>
|
||||
fun toResponseBet(): ResponseBet {
|
||||
return ResponseBet(
|
||||
id = id,
|
||||
theme = theme,
|
||||
sentenceBet = phrase,
|
||||
endRegistration = endRegisterDate,
|
||||
endBet = endBetDate,
|
||||
isPrivate = !isPublic,
|
||||
response = getResponses(),
|
||||
createdBy = creator
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package fr.iut.alldev.allin.data.serialization
|
||||
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.descriptors.PrimitiveKind
|
||||
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import java.time.Instant
|
||||
import java.time.ZoneId
|
||||
import java.time.ZonedDateTime
|
||||
|
||||
object ZonedDateTimeSerializer : KSerializer<ZonedDateTime> {
|
||||
override val descriptor: SerialDescriptor =
|
||||
PrimitiveSerialDescriptor("ZonedDateTime", PrimitiveKind.LONG)
|
||||
|
||||
override fun serialize(encoder: Encoder, value: ZonedDateTime) {
|
||||
encoder.encodeLong(value.toEpochSecond())
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder): ZonedDateTime {
|
||||
val epoch = decoder.decodeLong()
|
||||
return ZonedDateTime.ofInstant(Instant.ofEpochSecond(epoch), ZoneId.systemDefault())
|
||||
}
|
||||
}
|
Loading…
Reference in new issue