Skip to content

Commit 514e4f7

Browse files
committed
Improve login token handling by actually parsing result JSON
1 parent d9a6f04 commit 514e4f7

3 files changed

Lines changed: 88 additions & 24 deletions

File tree

library/src/main/kotlin/me/proxer/library/internal/interceptor/LoginTokenInterceptor.kt

Lines changed: 64 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package me.proxer.library.internal.interceptor
22

33
import com.squareup.moshi.JsonDataException
4+
import com.squareup.moshi.JsonReader
45
import me.proxer.library.LoginTokenManager
56
import me.proxer.library.ProxerException.ServerErrorType
67
import me.proxer.library.util.ProxerUrls
@@ -16,8 +17,8 @@ internal class LoginTokenInterceptor(private val loginTokenManager: LoginTokenMa
1617
private const val LOGIN_TOKEN_HEADER = "proxer-api-token"
1718
private const val MAX_PEEK_BYTE_COUNT = 1_048_576L
1819

19-
private val LOGIN_TOKEN_PATTERN = Regex("\"token\":.*?\"(.+?)\"", RegexOption.DOT_MATCHES_ALL)
20-
private val ERROR_PATTERN = Regex("\"code\":.*?(\\d+\b?)", RegexOption.DOT_MATCHES_ALL)
20+
private val OPTIONS = JsonReader.Options.of("code", "data")
21+
private val TOKEN_OPTIONS = JsonReader.Options.of("token")
2122

2223
private val LOGIN_URL = ProxerUrls.apiBase.newBuilder()
2324
.addPathSegment("user")
@@ -55,36 +56,76 @@ internal class LoginTokenInterceptor(private val loginTokenManager: LoginTokenMa
5556
}
5657

5758
private fun handleResponse(response: Response): Response {
58-
val responseBody = peekResponseBody(response)
59-
val url = response.request.url
59+
if (response.isSuccessful) {
60+
val (errorCode, token) = peekResponse(response)
61+
62+
synchronized(lock) {
63+
val errorType = errorCode?.let { ServerErrorType.fromErrorCode(it) }
64+
65+
if (errorType != null) {
66+
if (errorType.isLoginError) {
67+
loginTokenManager.persist(null)
68+
}
69+
} else if (response.request.url.pathSegments == LOGIN_URL.pathSegments) {
70+
if (token != null) {
71+
loginTokenManager.persist(token)
72+
} else {
73+
throw JsonDataException("No token found after successful login.")
74+
}
75+
} else if (response.request.url.pathSegments == LOGOUT_URL.pathSegments) {
76+
loginTokenManager.persist(null)
77+
}
78+
}
79+
}
80+
81+
return response
82+
}
6083

61-
synchronized(lock) {
62-
val potentialError = ERROR_PATTERN.find(responseBody)
84+
private fun peekResponse(response: Response): PeekedResponse {
85+
if (response.body == null) return PeekedResponse()
6386

64-
if (potentialError != null) {
65-
val errorCode = potentialError.groupValues[1].toInt()
66-
val errorType = ServerErrorType.fromErrorCode(errorCode)
87+
val reader = JsonReader.of(response.peekBody(MAX_PEEK_BYTE_COUNT).source())
88+
.also { it.beginObject() }
6789

68-
if (errorType.isLoginError) {
69-
loginTokenManager.persist(null)
70-
}
71-
} else if (url.pathSegments == LOGIN_URL.pathSegments) {
72-
val token = LOGIN_TOKEN_PATTERN.find(responseBody)
90+
var errorCode: Int? = null
91+
var token: String? = null
7392

74-
if (token != null) {
75-
loginTokenManager.persist(token.groupValues[1])
76-
} else {
77-
throw JsonDataException("No token found after successful login.")
93+
while (reader.hasNext() && errorCode == null) {
94+
when (reader.selectName(OPTIONS)) {
95+
0 -> errorCode = reader.nextInt()
96+
1 -> when (reader.peek()) {
97+
JsonReader.Token.BEGIN_OBJECT -> token = peekForToken(reader)
98+
else -> reader.skipValue()
99+
}
100+
else -> {
101+
reader.skipName()
102+
reader.skipValue()
78103
}
79-
} else if (url.pathSegments == LOGOUT_URL.pathSegments) {
80-
loginTokenManager.persist(null)
81104
}
82105
}
83106

84-
return response
107+
return PeekedResponse(errorCode, token)
85108
}
86109

87-
private fun peekResponseBody(response: Response): String {
88-
return if (response.body != null) response.peekBody(MAX_PEEK_BYTE_COUNT).string() else ""
110+
private fun peekForToken(reader: JsonReader): String? {
111+
reader.beginObject()
112+
113+
var token: String? = null
114+
115+
while (reader.hasNext()) {
116+
when (reader.selectName(TOKEN_OPTIONS)) {
117+
0 -> token = reader.nextString()
118+
else -> {
119+
reader.skipName()
120+
reader.skipValue()
121+
}
122+
}
123+
}
124+
125+
reader.endObject()
126+
127+
return token
89128
}
129+
130+
private data class PeekedResponse(val errorCode: Int? = null, val token: String? = null)
90131
}

library/src/test/kotlin/me/proxer/library/internal/interceptor/LoginTokenInterceptorTest.kt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,14 +102,25 @@ class LoginTokenInterceptorTest : ProxerTest() {
102102
}
103103
}
104104

105+
@Test
106+
fun testTokenWithErrorResponse() {
107+
server.runRequestIgnoringError("login_token_and_error.json") {
108+
api.user.login("test", "secret").build().execute()
109+
}
110+
111+
val (_, request) = server.runRequest("news.json") { api.notifications.news().build().execute() }
112+
113+
request.headers.get("proxer-api-token").shouldBeNull()
114+
}
115+
105116
@Test
106117
fun testEmptyResponse() {
107118
server.runRequest(MockResponse()) {
108119
val result = invoking {
109120
api.user.login("test", "secret").build().execute()
110121
} shouldThrow ProxerException::class
111122

112-
result.exception.errorType shouldBe ErrorType.PARSING
123+
result.exception.errorType shouldBe ErrorType.IO
113124
}
114125
}
115126

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"data": {
3+
"uid": 121658,
4+
"avatar": "121658_VHuZqz.jpg",
5+
"token": "OmSjyOzMeyICUnErDD04lsDta7REW2fIn6ZWUxG96mIXHmplYymjYZK94BNXA1wloFSVcw3fTKdA6CT49ek7b4dfCYcdWQ0Xv2TFvTUoD8XGHOHP11Uc46rF4BSXrZUU1LxwEqSgxNWdAC3ACWMF2di3N0Xe9S88BEBe3tuAfoNP1NpAIadJlwK9DHlLxqS83rl6VPD9bqXabkKTsYBOslW61fOwFFDI7WLZLo8UM35XnPRPLsBdLwgJL5dpJQ6",
6+
"isTeam": true,
7+
"isDonator": true
8+
},
9+
"error": 1,
10+
"message": "Test error.",
11+
"code": 10000
12+
}

0 commit comments

Comments
 (0)