Skip to content

Commit d124592

Browse files
authored
fix(auth): Fix timestamp issue with v1 to v2 migration (#2799)
1 parent 7327a7e commit d124592

File tree

4 files changed

+77
-8
lines changed

4 files changed

+77
-8
lines changed

aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/data/AWSCognitoLegacyCredentialStore.kt

+13-1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ import com.amplifyframework.statemachine.codegen.data.DeviceMetadata
2929
import com.amplifyframework.statemachine.codegen.data.FederatedToken
3030
import com.amplifyframework.statemachine.codegen.data.SignInMethod
3131
import com.amplifyframework.statemachine.codegen.data.SignedInData
32+
import java.time.Instant
33+
import java.time.temporal.ChronoUnit
3234
import java.util.Date
3335
import java.util.Locale
3436

@@ -180,7 +182,17 @@ internal class AWSCognitoLegacyCredentialStore(
180182
val accessKey = idAndCredentialsKeyValue.get(namespace(AK_KEY))
181183
val secretKey = idAndCredentialsKeyValue.get(namespace(SK_KEY))
182184
val sessionToken = idAndCredentialsKeyValue.get(namespace(ST_KEY))
183-
val expiration = idAndCredentialsKeyValue.get(namespace(EXP_KEY))?.toLongOrNull()
185+
186+
// V2 AWSCredential expiration is in epoch seconds whereas legacy expiration may be in epoch milliseconds
187+
// Session expiration should be within 24 hours so if we see a date in the far future, we can assume the
188+
// timestamp is encoded in milliseconds.
189+
val expiration = idAndCredentialsKeyValue.get(namespace(EXP_KEY))?.toLongOrNull()?.let {
190+
if (Instant.ofEpochSecond(it).isAfter(Instant.now().plus(365, ChronoUnit.DAYS))) {
191+
it / 1000
192+
} else {
193+
it
194+
}
195+
}
184196

185197
return if (accessKey == null && secretKey == null && sessionToken == null) {
186198
null

aws-auth-cognito/src/main/java/com/amplifyframework/auth/cognito/helpers/SessionHelper.kt

+10-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package com.amplifyframework.auth.cognito.helpers
1818
import com.amplifyframework.statemachine.codegen.data.AWSCredentials
1919
import com.amplifyframework.statemachine.codegen.data.CognitoUserPoolTokens
2020
import java.time.Instant
21+
import java.time.temporal.ChronoUnit
2122

2223
internal object SessionHelper {
2324
/**
@@ -68,6 +69,14 @@ internal object SessionHelper {
6869
*/
6970
fun isValidSession(awsCredentials: AWSCredentials): Boolean {
7071
val currentTimeStamp = Instant.now()
71-
return currentTimeStamp < awsCredentials.expiration?.let { Instant.ofEpochSecond(it) }
72+
val credentialsExpirationInSecond = awsCredentials.expiration?.let { Instant.ofEpochSecond(it) }
73+
74+
// Check if current timestamp is BEFORE expiration && next year is AFTER expiration
75+
// The latter check is to fix v1 > v2 migration issues as found in:
76+
// https://github.com/aws-amplify/amplify-android/issues/2789
77+
return (
78+
currentTimeStamp < credentialsExpirationInSecond &&
79+
currentTimeStamp.plus(365, ChronoUnit.DAYS) > credentialsExpirationInSecond
80+
)
7281
}
7382
}

aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/data/AWSCognitoLegacyCredentialStoreTest.kt

+9-6
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ class AWSCognitoLegacyCredentialStoreTest {
9393

9494
private const val userDeviceDetailsCacheKey = "$deviceCachePrefix.$USER_POOL_ID.%s"
9595
private val deviceDetailsCacheKey = String.format(userDeviceDetailsCacheKey, userId)
96+
97+
private const val expirationTimestampInSec: Long = 1714431706
98+
private const val expirationTimestampInMs: Long = 1714431706486
9699
}
97100

98101
@Mock
@@ -181,7 +184,7 @@ class AWSCognitoLegacyCredentialStoreTest {
181184
`when`(mockKeyValue.get(cachedIdTokenKey)).thenReturn("idToken")
182185
`when`(mockKeyValue.get(cachedAccessTokenKey)).thenReturn(dummyToken)
183186
`when`(mockKeyValue.get(cachedRefreshTokenKey)).thenReturn("refreshToken")
184-
`when`(mockKeyValue.get(cachedTokenExpirationKey)).thenReturn("123123")
187+
`when`(mockKeyValue.get(cachedTokenExpirationKey)).thenReturn(expirationTimestampInMs.toString())
185188

186189
// Device Metadata
187190
`when`(mockKeyValue.get("DeviceKey")).thenReturn("someDeviceKey")
@@ -192,7 +195,7 @@ class AWSCognitoLegacyCredentialStoreTest {
192195
`when`(mockKeyValue.get("$IDENTITY_POOL_ID.${"accessKey"}")).thenReturn("accessKeyId")
193196
`when`(mockKeyValue.get("$IDENTITY_POOL_ID.${"secretKey"}")).thenReturn("secretAccessKey")
194197
`when`(mockKeyValue.get("$IDENTITY_POOL_ID.${"sessionToken"}")).thenReturn("sessionToken")
195-
`when`(mockKeyValue.get("$IDENTITY_POOL_ID.${"expirationDate"}")).thenReturn("123123")
198+
`when`(mockKeyValue.get("$IDENTITY_POOL_ID.${"expirationDate"}")).thenReturn(expirationTimestampInMs.toString())
196199

197200
// Identity ID
198201
`when`(mockKeyValue.get("$IDENTITY_POOL_ID.${"identityId"}")).thenReturn("identityPool")
@@ -225,10 +228,10 @@ class AWSCognitoLegacyCredentialStoreTest {
225228
"amplify_user",
226229
Date(0),
227230
SignInMethod.ApiBased(SignInMethod.ApiBased.AuthType.USER_SRP_AUTH),
228-
CognitoUserPoolTokens("idToken", dummyToken, "refreshToken", 123123)
231+
CognitoUserPoolTokens("idToken", dummyToken, "refreshToken", expirationTimestampInSec)
229232
),
230233
"identityPool",
231-
AWSCredentials("accessKeyId", "secretAccessKey", "sessionToken", 123123)
234+
AWSCredentials("accessKeyId", "secretAccessKey", "sessionToken", expirationTimestampInSec)
232235
)
233236
}
234237

@@ -249,10 +252,10 @@ class AWSCognitoLegacyCredentialStoreTest {
249252
"amplify_user",
250253
Date(0),
251254
SignInMethod.HostedUI(),
252-
CognitoUserPoolTokens("idToken", dummyToken, "refreshToken", 123123)
255+
CognitoUserPoolTokens("idToken", dummyToken, "refreshToken", expirationTimestampInSec)
253256
),
254257
"identityPool",
255-
AWSCredentials("accessKeyId", "secretAccessKey", "sessionToken", 123123)
258+
AWSCredentials("accessKeyId", "secretAccessKey", "sessionToken", expirationTimestampInSec)
256259
)
257260
}
258261
}

aws-auth-cognito/src/test/java/com/amplifyframework/auth/cognito/helpers/SessionHelperTests.kt

+45
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@ package com.amplifyframework.auth.cognito.helpers
1818
import com.amplifyframework.statemachine.codegen.data.AWSCredentials
1919
import com.amplifyframework.statemachine.codegen.data.CognitoUserPoolTokens
2020
import java.time.Instant
21+
import java.time.temporal.ChronoUnit
2122
import kotlin.test.assertEquals
2223
import kotlin.test.assertFalse
24+
import kotlin.test.assertTrue
2325
import org.junit.Test
2426

2527
class SessionHelperTests {
@@ -61,4 +63,47 @@ class SessionHelperTests {
6163
fun testsIsInvalidSession() {
6264
assertFalse(SessionHelper.isValidSession(AWSCredentials.empty))
6365
}
66+
67+
@Test
68+
fun `Pulling a V1 credential should fail isValidSession check`() {
69+
// Expiration is encoded in ms to simulate v1 > v2 migration issue
70+
assertFalse(
71+
SessionHelper.isValidSession(
72+
AWSCredentials(
73+
accessKeyId = dummyToken,
74+
secretAccessKey = dummyToken,
75+
sessionToken = dummyToken,
76+
expiration = Instant.now().plus(30, ChronoUnit.MINUTES).toEpochMilli()
77+
)
78+
)
79+
)
80+
}
81+
82+
@Test
83+
fun `Session with an expiration in the past should fail isValidSession check`() {
84+
assertFalse(
85+
SessionHelper.isValidSession(
86+
AWSCredentials(
87+
accessKeyId = dummyToken,
88+
secretAccessKey = dummyToken,
89+
sessionToken = dummyToken,
90+
expiration = Instant.now().minus(1, ChronoUnit.MINUTES).epochSecond
91+
)
92+
)
93+
)
94+
}
95+
96+
@Test
97+
fun `Session with an expiration in the future should pass isValidSession check`() {
98+
assertTrue(
99+
SessionHelper.isValidSession(
100+
AWSCredentials(
101+
accessKeyId = dummyToken,
102+
secretAccessKey = dummyToken,
103+
sessionToken = dummyToken,
104+
expiration = Instant.now().plus(1, ChronoUnit.MINUTES).epochSecond
105+
)
106+
)
107+
)
108+
}
64109
}

0 commit comments

Comments
 (0)