Skip to content
Snippets Groups Projects
Commit e406f60c authored by naufal's avatar naufal
Browse files

add background service for checking jwt expiry

parent d66f737b
Branches
Tags
No related merge requests found
...@@ -51,6 +51,7 @@ dependencies { ...@@ -51,6 +51,7 @@ dependencies {
implementation("com.squareup.retrofit2:retrofit:2.9.0") implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.retrofit2:converter-gson:2.9.0") implementation("com.squareup.retrofit2:converter-gson:2.9.0")
implementation("androidx.security:security-crypto:1.1.0-alpha06") implementation("androidx.security:security-crypto:1.1.0-alpha06")
implementation(libs.androidx.work.runtime.ktx)
testImplementation(libs.junit) testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core) androidTestImplementation(libs.androidx.espresso.core)
......
...@@ -9,10 +9,11 @@ import androidx.appcompat.app.AppCompatActivity ...@@ -9,10 +9,11 @@ import androidx.appcompat.app.AppCompatActivity
import com.example.myapplication.databinding.ActivityLoginBinding import com.example.myapplication.databinding.ActivityLoginBinding
import com.example.myapplication.repository.AuthRepository import com.example.myapplication.repository.AuthRepository
import com.example.myapplication.ui.login.UserViewModel import com.example.myapplication.ui.login.UserViewModel
import com.example.myapplication.util.EventBus
import com.example.myapplication.util.LoginListener import com.example.myapplication.util.LoginListener
import com.example.myapplication.util.SecretPreference import com.example.myapplication.util.SecretPreference
class LoginActivity : AppCompatActivity() , LoginListener { class LoginActivity : AppCompatActivity() {
private lateinit var binding: ActivityLoginBinding private lateinit var binding: ActivityLoginBinding
private var userViewModel : UserViewModel = UserViewModel() private var userViewModel : UserViewModel = UserViewModel()
...@@ -23,7 +24,7 @@ class LoginActivity : AppCompatActivity() , LoginListener { ...@@ -23,7 +24,7 @@ class LoginActivity : AppCompatActivity() , LoginListener {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
secretPreference = SecretPreference(this) secretPreference = SecretPreference(this)
authRepository = AuthRepository(this, secretPreference) authRepository = AuthRepository(secretPreference)
binding = ActivityLoginBinding.inflate(layoutInflater) binding = ActivityLoginBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
...@@ -38,9 +39,17 @@ class LoginActivity : AppCompatActivity() , LoginListener { ...@@ -38,9 +39,17 @@ class LoginActivity : AppCompatActivity() , LoginListener {
authRepository.loginRequest(binding.emailInput.text.toString(), binding.passwordInput.text.toString()) authRepository.loginRequest(binding.emailInput.text.toString(), binding.passwordInput.text.toString())
Log.d("Development", "Activity: Login request sent") Log.d("Development", "Activity: Login request sent")
} }
EventBus.subscribe("LOGIN_SUCCESS") {
onLoginSuccess()
}
EventBus.subscribe("LOGIN_FAIL") {
onLoginFailure()
}
} }
override fun onLoginSuccess() { fun onLoginSuccess() {
userViewModel.setUser(binding.emailInput.text.toString(), binding.passwordInput.text.toString()) userViewModel.setUser(binding.emailInput.text.toString(), binding.passwordInput.text.toString())
Log.d("Development", "Activity: Login success") Log.d("Development", "Activity: Login success")
val preference : SharedPreferences = getSharedPreferences("secret_shared_prefs", MODE_PRIVATE) val preference : SharedPreferences = getSharedPreferences("secret_shared_prefs", MODE_PRIVATE)
...@@ -48,7 +57,7 @@ class LoginActivity : AppCompatActivity() , LoginListener { ...@@ -48,7 +57,7 @@ class LoginActivity : AppCompatActivity() , LoginListener {
finish() finish()
} }
override fun onLoginFailure() { fun onLoginFailure() {
Log.d("Development", "Activity: Login failed") Log.d("Development", "Activity: Login failed")
} }
......
...@@ -4,7 +4,6 @@ import android.content.Context ...@@ -4,7 +4,6 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.ConnectivityManager import android.net.ConnectivityManager
import android.net.NetworkCapabilities import android.net.NetworkCapabilities
import android.net.NetworkInfo
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import com.google.android.material.bottomnavigation.BottomNavigationView import com.google.android.material.bottomnavigation.BottomNavigationView
...@@ -13,11 +12,18 @@ import androidx.navigation.findNavController ...@@ -13,11 +12,18 @@ import androidx.navigation.findNavController
import androidx.navigation.ui.AppBarConfiguration import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.setupActionBarWithNavController import androidx.navigation.ui.setupActionBarWithNavController
import androidx.navigation.ui.setupWithNavController import androidx.navigation.ui.setupWithNavController
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager
import com.example.myapplication.databinding.ActivityMainBinding import com.example.myapplication.databinding.ActivityMainBinding
import com.example.myapplication.service.TokenExpiryWorker
import com.example.myapplication.util.EventBus
import com.example.myapplication.util.SecretPreference
import java.util.concurrent.TimeUnit
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity(){
private lateinit var binding: ActivityMainBinding private lateinit var binding: ActivityMainBinding
private lateinit var secretPreference : SecretPreference
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
...@@ -44,10 +50,25 @@ class MainActivity : AppCompatActivity() { ...@@ -44,10 +50,25 @@ class MainActivity : AppCompatActivity() {
val loginIntent = Intent(this, LoginActivity::class.java) val loginIntent = Intent(this, LoginActivity::class.java)
startActivity(loginIntent) startActivity(loginIntent)
} }
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
// CHECK TOKEN for expiry
val backgroundWork = PeriodicWorkRequestBuilder<TokenExpiryWorker>(15, TimeUnit.MINUTES).build()
WorkManager.getInstance(this).enqueue(backgroundWork)
// event listener for token expiry
EventBus.subscribe("TOKEN_EXPIRED") {
Log.i("Development", "Token expired")
secretPreference = SecretPreference(this)
secretPreference.clearToken()
val loginIntent = Intent(this, LoginActivity::class.java)
startActivity(loginIntent)
}
} }
private fun isOnline(): Boolean { private fun isOnline(): Boolean {
...@@ -57,4 +78,5 @@ class MainActivity : AppCompatActivity() { ...@@ -57,4 +78,5 @@ class MainActivity : AppCompatActivity() {
return networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) && return networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) &&
networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
} }
} }
\ No newline at end of file
...@@ -4,6 +4,7 @@ import com.example.myapplication.model.auth.Token ...@@ -4,6 +4,7 @@ import com.example.myapplication.model.auth.Token
import com.example.myapplication.model.auth.UserCred import com.example.myapplication.model.auth.UserCred
import retrofit2.Call import retrofit2.Call
import retrofit2.http.Body import retrofit2.http.Body
import retrofit2.http.Header
import retrofit2.http.POST import retrofit2.http.POST
...@@ -12,7 +13,7 @@ interface BackendService { ...@@ -12,7 +13,7 @@ interface BackendService {
fun login(@Body userCred: UserCred): Call<Token> fun login(@Body userCred: UserCred): Call<Token>
@POST("auth/token") @POST("auth/token")
fun tokenCheck(@Body token: Token): Call<Token> fun tokenCheck(@Header("Authorization") token : String): Call<Token>
// @POST("bill/upload") // @POST("bill/upload")
// fun uploadBill(@Body bill: Bill): Call<BillResponse> // fun uploadBill(@Body bill: Bill): Call<BillResponse>
......
package com.example.myapplication.repository package com.example.myapplication.repository
import android.content.Context
import android.util.Log import android.util.Log
import com.example.myapplication.backendconnect.Client import com.example.myapplication.backendconnect.Client
import com.example.myapplication.model.auth.Token import com.example.myapplication.model.auth.Token
import com.example.myapplication.model.auth.UserCred import com.example.myapplication.model.auth.UserCred
import com.example.myapplication.util.EventBus
import com.example.myapplication.util.LoginListener import com.example.myapplication.util.LoginListener
import com.example.myapplication.util.SecretPreference import com.example.myapplication.util.SecretPreference
import retrofit2.Call import retrofit2.Call
...@@ -12,7 +12,6 @@ import retrofit2.Callback ...@@ -12,7 +12,6 @@ import retrofit2.Callback
import retrofit2.Response import retrofit2.Response
class AuthRepository ( class AuthRepository (
private val loginListener: LoginListener,
private val secretPreference: SecretPreference) { private val secretPreference: SecretPreference) {
fun loginRequest(email: String, password: String){ fun loginRequest(email: String, password: String){
...@@ -29,19 +28,51 @@ class AuthRepository ( ...@@ -29,19 +28,51 @@ class AuthRepository (
secretPreference.saveToken(token?.token ?: "") secretPreference.saveToken(token?.token ?: "")
// callback the loginActivity // callback the loginActivity
loginListener.onLoginSuccess() //loginListener.onLoginSuccess()
EventBus.publish("LOGIN_SUCCESS")
} else { } else {
Log.d("Development", "LOGIN FAILED, http code : ${response.code()}") Log.d("Development", "LOGIN FAILED, http code : ${response.code()}")
loginListener.onLoginFailure() //loginListener.onLoginFailure()
EventBus.publish("LOGIN_FAIL")
} }
} }
override fun onFailure(call: Call<Token>, t: Throwable) { override fun onFailure(call: Call<Token>, t: Throwable) {
Log.d("Development", "LOGIN FAILED, error on delivery : ${t.message}") Log.d("Development", "LOGIN FAILED, error on delivery : ${t.message}")
loginListener.onLoginFailure() // loginListener.onLoginFailure()
EventBus.publish("LOGIN_FAIL")
} }
} }
) )
} }
fun tokenCheckRequest() {
Log.d("Development", "Token check request to backend service")
val token = secretPreference.getToken() ?: ""
Client.connect.tokenCheck("Bearer $token").enqueue(
object : Callback<Token> {
override fun onResponse(call: Call<Token>, response: Response<Token>) {
Log.d("Development", "Response: ${response.body()}")
if (response.isSuccessful) {
Log.d("Development", "Token check success: Valid token")
}
else if (response.code() == 401) {
Log.d("Development", "Token check failed: Invalid token or expired")
EventBus.publish("TOKEN_EXPIRED")
}
else {
Log.d("Development", "TOKEN CHECK FAILED, http code : ${response.code()}")
EventBus.publish("TOKEN_EXPIRED")
}
}
override fun onFailure(call: Call<Token>, t: Throwable) {
Log.d("Development", "TOKEN CHECK FAILED, error on delivery : ${t.message}")
}
}
)
}
} }
\ No newline at end of file
package com.example.myapplication.service
import android.content.Context
import android.util.Log
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.example.myapplication.repository.AuthRepository
import com.example.myapplication.util.LoginListener
import com.example.myapplication.util.SecretPreference
class TokenExpiryWorker (appContext: Context, workerParams: WorkerParameters)
: CoroutineWorker(appContext, workerParams) {
private val secretPreference = SecretPreference(applicationContext)
private val authRepository = AuthRepository(secretPreference)
override suspend fun doWork(): Result {
Log.i("Development", "Token expiry worker started")
authRepository.tokenCheckRequest()
return Result.success()
}
}
\ No newline at end of file
package com.example.myapplication.util
import android.util.Log
object EventBus {
private var listeners: MutableMap<String, MutableList<() -> Unit>> = mutableMapOf()
fun subscribe(event: String, listener: () -> Unit) {
if (listeners.containsKey(event)) {
// add a new listener to an existing event
listeners[event]?.add(listener)
} else {
// add a new event with a new listener
listeners[event] = mutableListOf(listener)
}
}
fun publish(event: String) {
if (listeners.containsKey(event)) {
// call all listeners for the event
listeners[event]?.forEach { it() }
} else {
// no listeners for the event
Log.d("Development","No listeners for event: $event")
}
}
}
\ No newline at end of file
...@@ -25,4 +25,9 @@ class SecretPreference (private val context: Context) { ...@@ -25,4 +25,9 @@ class SecretPreference (private val context: Context) {
fun getToken(): String? = sharedPreferences.getString("token", null) fun getToken(): String? = sharedPreferences.getString("token", null)
fun clearToken(){
sharedPreferences.edit().remove("token").apply()
Log.i("Development", "Token cleared")
}
} }
\ No newline at end of file
...@@ -12,6 +12,7 @@ lifecycleLivedataKtx = "2.7.0" ...@@ -12,6 +12,7 @@ lifecycleLivedataKtx = "2.7.0"
lifecycleViewmodelKtx = "2.7.0" lifecycleViewmodelKtx = "2.7.0"
navigationFragmentKtx = "2.6.0" navigationFragmentKtx = "2.6.0"
navigationUiKtx = "2.6.0" navigationUiKtx = "2.6.0"
workRuntimeKtx = "2.9.0"
[libraries] [libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
...@@ -25,6 +26,7 @@ androidx-lifecycle-livedata-ktx = { group = "androidx.lifecycle", name = "lifecy ...@@ -25,6 +26,7 @@ androidx-lifecycle-livedata-ktx = { group = "androidx.lifecycle", name = "lifecy
androidx-lifecycle-viewmodel-ktx = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-ktx", version.ref = "lifecycleViewmodelKtx" } androidx-lifecycle-viewmodel-ktx = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-ktx", version.ref = "lifecycleViewmodelKtx" }
androidx-navigation-fragment-ktx = { group = "androidx.navigation", name = "navigation-fragment-ktx", version.ref = "navigationFragmentKtx" } androidx-navigation-fragment-ktx = { group = "androidx.navigation", name = "navigation-fragment-ktx", version.ref = "navigationFragmentKtx" }
androidx-navigation-ui-ktx = { group = "androidx.navigation", name = "navigation-ui-ktx", version.ref = "navigationUiKtx" } androidx-navigation-ui-ktx = { group = "androidx.navigation", name = "navigation-ui-ktx", version.ref = "navigationUiKtx" }
androidx-work-runtime-ktx = { group = "androidx.work", name = "work-runtime-ktx", version.ref = "workRuntimeKtx" }
[plugins] [plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" } androidApplication = { id = "com.android.application", version.ref = "agp" }
......
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment