diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c3222dcf7d8d0b4b7eb8f0bf087e03804df82e1e..ff933a28d6473dd6f02ffde9e552e4cdf217143c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,6 +1,8 @@ <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> + + <!-- Permissions --> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> @@ -14,6 +16,8 @@ android:supportsRtl="true" android:theme="@style/Theme.Bondoman" tools:targetApi="31"> + + <!-- Activities --> <activity android:name=".TransactionActivity" android:exported="false" @@ -29,10 +33,24 @@ android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> - <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> + + <!-- Service --> + <service android:name="itb.bos.bondoman.services.JWTService" + android:exported="false" /> + + <!-- Broadcast Receiver --> + <receiver + android:name="itb.bos.bondoman.receivers.TokenExpiryBroadcastReceiver" + android:exported="false"> + <intent-filter> + <action android:name="itb.bos.bondoman.ACTION_TOKEN_EXPIRED" /> + </intent-filter> + </receiver> + + </application> </manifest> \ No newline at end of file diff --git a/app/src/main/java/itb/bos/bondoman/LogoutActivity.kt b/app/src/main/java/itb/bos/bondoman/LogoutActivity.kt index ea4e8cd051f4854505a1b3c13683ffdbf35592ba..c3fc41ace1c662f21c43843bae112dda1a1145d6 100644 --- a/app/src/main/java/itb/bos/bondoman/LogoutActivity.kt +++ b/app/src/main/java/itb/bos/bondoman/LogoutActivity.kt @@ -20,14 +20,7 @@ class LogoutActivity : AppCompatActivity() { val logoutButton = findViewById<Button>(R.id.btn_logout) logoutButton.setOnClickListener { - logout() + authViewModel.logout() } } - - private fun logout() { - authViewModel.removeToken() - val intent = Intent(this, LoginActivity::class.java) - intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK - startActivity(intent) - } } diff --git a/app/src/main/java/itb/bos/bondoman/helper/AuthHelper.kt b/app/src/main/java/itb/bos/bondoman/helper/AuthHelper.kt index 72881000dd6ffdce5ad9ac05b677f2ab34e00b4b..63bf5275ae3d4705f9c6f1bb9798ea9c471a661d 100644 --- a/app/src/main/java/itb/bos/bondoman/helper/AuthHelper.kt +++ b/app/src/main/java/itb/bos/bondoman/helper/AuthHelper.kt @@ -17,11 +17,12 @@ suspend fun performLogin(email: String, password: String): String { } } -suspend fun getToken(): String { +suspend fun checkExpireToken(token: String): Long { return withContext(Dispatchers.IO) { val retrofit = RetrofitClient.getInstance() val endpoint = retrofit.create(AuthEndpoint::class.java) - val response = endpoint.getToken() - response.token + val response = endpoint.checkExpireToken("Bearer $token") + + response.expiresAt } } diff --git a/app/src/main/java/itb/bos/bondoman/receivers/TokenExpiryBroadcastReceiver.kt b/app/src/main/java/itb/bos/bondoman/receivers/TokenExpiryBroadcastReceiver.kt new file mode 100644 index 0000000000000000000000000000000000000000..2e0f6c5c80fb7438d2221eeac313ab0611b7894a --- /dev/null +++ b/app/src/main/java/itb/bos/bondoman/receivers/TokenExpiryBroadcastReceiver.kt @@ -0,0 +1,23 @@ +package itb.bos.bondoman.receivers + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.util.Log +import itb.bos.bondoman.LoginActivity +import itb.bos.bondoman.helper.TokenHelper + +class TokenExpiryBroadcastReceiver : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + context?.let { + val tokenHelper = TokenHelper(it.applicationContext) + // Perform logout action + Log.d("TokenExpiredBroadcast", "Received token expired broadcast. Logging out...") + // You can implement logout logic here, for example: + tokenHelper.removeToken() + it.startActivity(Intent(it, LoginActivity::class.java).apply { + addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK) + }) + } + } +} diff --git a/app/src/main/java/itb/bos/bondoman/retrofit/data/AuthData.kt b/app/src/main/java/itb/bos/bondoman/retrofit/data/AuthData.kt index 6f5b22b5ccd2cbf074f10f8227058f7c9550506e..39f0a29f68e66402b568116ce61ee2e39acf262e 100644 --- a/app/src/main/java/itb/bos/bondoman/retrofit/data/AuthData.kt +++ b/app/src/main/java/itb/bos/bondoman/retrofit/data/AuthData.kt @@ -10,3 +10,9 @@ data class LoginRequest ( data class TokenResponse ( @SerializedName("token") val token: String ) + +data class CheckTokenResponse ( + @SerializedName("nim") val nim: String, + @SerializedName("iat") val issuedAt: Long, + @SerializedName("exp") val expiresAt: Long +) \ No newline at end of file diff --git a/app/src/main/java/itb/bos/bondoman/retrofit/endpoint/AuthEndpoint.kt b/app/src/main/java/itb/bos/bondoman/retrofit/endpoint/AuthEndpoint.kt index 766e1a7be4ea0e6add5edeaa2bcf9cc8fae19870..35d69b7522657a3a0ec0ff8281ebb8836c540c5d 100644 --- a/app/src/main/java/itb/bos/bondoman/retrofit/endpoint/AuthEndpoint.kt +++ b/app/src/main/java/itb/bos/bondoman/retrofit/endpoint/AuthEndpoint.kt @@ -1,8 +1,10 @@ package itb.bos.bondoman.retrofit.endpoint +import itb.bos.bondoman.retrofit.data.CheckTokenResponse import itb.bos.bondoman.retrofit.data.LoginRequest import itb.bos.bondoman.retrofit.data.TokenResponse import retrofit2.http.Body +import retrofit2.http.Header import retrofit2.http.POST interface AuthEndpoint { @@ -10,5 +12,5 @@ interface AuthEndpoint { suspend fun login(@Body request: LoginRequest): TokenResponse @POST("/api/auth/token") - suspend fun getToken(): TokenResponse + suspend fun checkExpireToken(@Header("Authorization") bearerToken: String): CheckTokenResponse } \ No newline at end of file diff --git a/app/src/main/java/itb/bos/bondoman/services/JWTService.kt b/app/src/main/java/itb/bos/bondoman/services/JWTService.kt new file mode 100644 index 0000000000000000000000000000000000000000..922646dc6e3f5770b5a66123033a25587faac279 --- /dev/null +++ b/app/src/main/java/itb/bos/bondoman/services/JWTService.kt @@ -0,0 +1,82 @@ +package itb.bos.bondoman.services + +import android.app.Service +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.os.IBinder +import android.util.Log +import itb.bos.bondoman.LoginActivity +import itb.bos.bondoman.helper.TokenHelper +import itb.bos.bondoman.helper.checkExpireToken +import itb.bos.bondoman.receivers.TokenExpiryBroadcastReceiver +import kotlinx.coroutines.* +import java.util.concurrent.TimeUnit + +class JWTService : Service() { + + private var isServiceRunning = false + private lateinit var tokenHelper: TokenHelper + private val broadcastReceiver = TokenExpiryBroadcastReceiver() + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + if (!isServiceRunning) { + isServiceRunning = true + tokenHelper = TokenHelper(applicationContext) + startTokenExpirationCheck() + } + return START_STICKY + } + + override fun onDestroy() { + isServiceRunning = false + unregisterReceiver(broadcastReceiver) + super.onDestroy() + } + + override fun onBind(intent: Intent?): IBinder? { + return null + } + + private fun startTokenExpirationCheck() { + CoroutineScope(Dispatchers.IO).launch { + while (isServiceRunning) { + // Perform token expiration check + checkTokenExpiry() + delay(TimeUnit.MINUTES.toMillis(5)) + } + } + } + + private suspend fun checkTokenExpiry() { + try { + val token = tokenHelper.getToken() + if (token != null) { + // Call the checkExpireToken function with the token + val expireTime = checkExpireToken(token) + + // If the token is retrieved successfully, log the expiry time + Log.d("TokenExpirationCheck", "Expiry time is: $expireTime") + + // Check if the current time has passed the expiry time + val currentTime = System.currentTimeMillis() / 1000 // Convert to seconds + if (currentTime > expireTime) { + // Token has expired, send broadcast to notify the application to logout + val intent = Intent(ACTION_TOKEN_EXPIRED) + sendBroadcast(intent) + } else { + Log.d("TokenExpirationCheck", "Token is still valid") + } + } else { + Log.d("TokenExpirationCheck", "No token found") + } + } catch (e: Exception) { + // Handle any exceptions + Log.e("TokenExpirationCheck", "Error checking token expiry: ${e.message}") + } + } + + companion object { + const val ACTION_TOKEN_EXPIRED = "itb.bos.bondoman.ACTION_TOKEN_EXPIRED" + } +} diff --git a/app/src/main/java/itb/bos/bondoman/viewModel/AuthViewModel.kt b/app/src/main/java/itb/bos/bondoman/viewModel/AuthViewModel.kt index ba6d403391e4acddf8323d894ca5af30aace64c6..6f8a7d88e4510a05ed626ea91b9f3fd86f3629af 100644 --- a/app/src/main/java/itb/bos/bondoman/viewModel/AuthViewModel.kt +++ b/app/src/main/java/itb/bos/bondoman/viewModel/AuthViewModel.kt @@ -8,6 +8,7 @@ import android.widget.Toast import androidx.security.crypto.EncryptedSharedPreferences import androidx.security.crypto.MasterKeys import androidx.lifecycle.ViewModel +import itb.bos.bondoman.LoginActivity import itb.bos.bondoman.LogoutActivity import itb.bos.bondoman.helper.TokenHelper import itb.bos.bondoman.helper.performLogin @@ -17,8 +18,6 @@ import kotlinx.coroutines.launch class AuthViewModel : ViewModel() { - private lateinit var encryptedSharedPreferences: EncryptedSharedPreferences - @SuppressLint("StaticFieldLeak") private lateinit var context: Context private lateinit var tokenHelper : TokenHelper @@ -47,7 +46,9 @@ class AuthViewModel : ViewModel() { val token = performLogin(email, password) storeToken(token) checkToken() - navigateToLogoutActivity() // Start LogoutActivity after successful login + val intent = Intent(context, LogoutActivity::class.java) + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + context.startActivity(intent) Toast.makeText(context, "Login Success", Toast.LENGTH_SHORT).show() } catch (e: Exception) { @@ -58,11 +59,14 @@ class AuthViewModel : ViewModel() { Toast.makeText(context, "Invalid email or password format", Toast.LENGTH_SHORT).show() } } - private fun navigateToLogoutActivity() { - val intent = Intent(context, LogoutActivity::class.java) + + fun logout() { + removeToken() + val intent = Intent(context, LoginActivity::class.java) intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK context.startActivity(intent) } + private fun handleLoginFailure(e: Exception) { val errorMessage = e.message ?: "Unknown error occurred" Toast.makeText(context, errorMessage, Toast.LENGTH_SHORT).show()