forked from shihaam/thijooree
handle mib session expirey and keepalive
This commit is contained in:
@@ -9,6 +9,8 @@ import java.security.MessageDigest
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.random.Random
|
||||
|
||||
class SessionExpiredException : Exception("MIB session expired")
|
||||
|
||||
class MibLoginFlow(private val prefs: android.content.SharedPreferences) {
|
||||
|
||||
private val TAG = "MibLoginFlow"
|
||||
@@ -22,6 +24,15 @@ class MibLoginFlow(private val prefs: android.content.SharedPreferences) {
|
||||
var lastProfiles: List<MibProfile> = emptyList()
|
||||
private set
|
||||
|
||||
/** Called after automatic session recovery so callers can update their session reference. */
|
||||
var onSessionRefreshed: ((MibSession, List<MibProfile>) -> Unit)? = null
|
||||
|
||||
// Stored after login so the session can be silently recovered on 419
|
||||
@Volatile private var storedUsername: String? = null
|
||||
@Volatile private var storedPasswordHash: String? = null
|
||||
@Volatile private var storedOtpSeed: String? = null
|
||||
private var inRelogin = false
|
||||
|
||||
private val client = OkHttpClient.Builder()
|
||||
.connectTimeout(30, TimeUnit.SECONDS)
|
||||
.readTimeout(30, TimeUnit.SECONDS)
|
||||
@@ -46,6 +57,9 @@ class MibLoginFlow(private val prefs: android.content.SharedPreferences) {
|
||||
* Returns list of accounts from all profiles on success.
|
||||
*/
|
||||
fun login(username: String, passwordHash: String, otpSeed: String): List<MibAccount> {
|
||||
storedUsername = username
|
||||
storedPasswordHash = passwordHash
|
||||
storedOtpSeed = otpSeed
|
||||
val appId = getOrCreateAppId()
|
||||
val key1 = prefs.getString("mib_key1_$username", null)
|
||||
val key2 = prefs.getString("mib_key2_$username", null)
|
||||
@@ -172,17 +186,48 @@ class MibLoginFlow(private val prefs: android.content.SharedPreferences) {
|
||||
}
|
||||
|
||||
private fun doRequest(session: MibSession, data: JSONObject, sfunc: String): JSONObject {
|
||||
val routePath = data.optString("routePath", "?")
|
||||
val response = try {
|
||||
sendRequest(session, data, sfunc)
|
||||
} catch (e: SessionExpiredException) {
|
||||
if (inRelogin) throw e
|
||||
"" // fall through to recovery below
|
||||
}
|
||||
|
||||
// Detect expired session: HTTP 419 (empty response) or JSON with reasonCode 505
|
||||
val isExpired = response.isEmpty() ||
|
||||
(response.trimStart().startsWith("{") &&
|
||||
JSONObject(response).optString("reasonCode") == "505")
|
||||
|
||||
if (isExpired && !inRelogin) {
|
||||
val u = storedUsername ?: throw SessionExpiredException()
|
||||
val ph = storedPasswordHash ?: throw SessionExpiredException()
|
||||
val os = storedOtpSeed ?: throw SessionExpiredException()
|
||||
inRelogin = true
|
||||
try { login(u, ph, os) } finally { inRelogin = false }
|
||||
val newSession = lastSession ?: throw SessionExpiredException()
|
||||
onSessionRefreshed?.invoke(newSession, lastProfiles)
|
||||
// Refresh nonce/xxid in the payload for the retry
|
||||
data.put("nonce", MibNonce.generate(newSession.nonceGenerator))
|
||||
data.put("appId", newSession.appId)
|
||||
data.put("sodium", MibNonce.randomSodium())
|
||||
data.put("xxid", newSession.xxid)
|
||||
val retryResponse = sendRequest(newSession, data, sfunc)
|
||||
if (retryResponse.trimStart().startsWith("{")) return JSONObject(retryResponse)
|
||||
return MibCrypto.decrypt(retryResponse, newSession.sessionKey)
|
||||
}
|
||||
|
||||
if (response.trimStart().startsWith("{")) return JSONObject(response)
|
||||
return MibCrypto.decrypt(response, session.sessionKey)
|
||||
}
|
||||
|
||||
private fun sendRequest(session: MibSession, data: JSONObject, sfunc: String): String {
|
||||
val encrypted = MibCrypto.encrypt(data, session.sessionKey)
|
||||
val formBody = FormBody.Builder()
|
||||
.add("xxid", session.xxid)
|
||||
.add("sfunc", sfunc)
|
||||
.add("data", encrypted)
|
||||
.build()
|
||||
val response = post(formBody)
|
||||
// Server returns plain JSON (not encrypted) for error responses (e.g. expired session)
|
||||
if (response.trimStart().startsWith("{")) return JSONObject(response)
|
||||
return MibCrypto.decrypt(response, session.sessionKey)
|
||||
return post(formBody)
|
||||
}
|
||||
|
||||
private fun baseData(session: MibSession, routePath: String): JSONObject = JSONObject().apply {
|
||||
@@ -276,6 +321,7 @@ class MibLoginFlow(private val prefs: android.content.SharedPreferences) {
|
||||
.post(body)
|
||||
.build()
|
||||
val response = client.newCall(request).execute()
|
||||
if (response.code == 419) throw SessionExpiredException()
|
||||
return response.body?.string() ?: throw IllegalStateException("Empty response body")
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user