Skip to content

Commit dd288a4

Browse files
committed
Fix runtime 401 not notifying developer to provide a new JWT
When OperationRepo handled FAIL_UNAUTHORIZED it invalidated the JWT and re-queued operations but never fired IUserJwtInvalidatedListener, leaving the queue permanently stuck. Wire a callback from OperationRepo through IdentityVerificationService to UserManager.fireJwtInvalidated() so the developer is notified and can supply a fresh token. Made-with: Cursor
1 parent 762917b commit dd288a4

File tree

4 files changed

+99
-0
lines changed

4 files changed

+99
-0
lines changed

OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/config/impl/IdentityVerificationService.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ internal class IdentityVerificationService(
2929
) : IStartableService, ISingletonModelStoreChangeHandler<ConfigModel> {
3030
override fun start() {
3131
_configModelStore.subscribe(this)
32+
_operationRepo.setJwtInvalidatedHandler { externalId ->
33+
_userManager.fireJwtInvalidated(externalId)
34+
}
3235
}
3336

3437
override fun onModelReplaced(

OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/operations/IOperationRepo.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,13 @@ interface IOperationRepo {
4949
* purge operations that cannot be executed without an authenticated user.
5050
*/
5151
fun removeOperationsWithoutExternalId()
52+
53+
/**
54+
* Register a handler to be called when a runtime 401 Unauthorized response
55+
* invalidates a JWT. This allows the caller to notify the developer so they
56+
* can supply a fresh token via [OneSignal.updateUserJwt].
57+
*/
58+
fun setJwtInvalidatedHandler(handler: ((String) -> Unit)?)
5259
}
5360

5461
// Extension function so the syntax containsInstanceOf<Operation>() can be used over

OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/operations/impl/OperationRepo.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ internal class OperationRepo(
4444
}
4545
}
4646

47+
private var _jwtInvalidatedHandler: ((String) -> Unit)? = null
48+
4749
internal class LoopWaiterMessage(
4850
val force: Boolean,
4951
val previousWaitedTime: Long = 0,
@@ -284,6 +286,7 @@ internal class OperationRepo(
284286
val externalId = startingOp.operation.externalId
285287
if (externalId != null) {
286288
_jwtTokenStore.invalidateJwt(externalId)
289+
_jwtInvalidatedHandler?.invoke(externalId)
287290
Logging.warn("Operation execution failed with 401 Unauthorized, JWT invalidated for user: $externalId. Operations re-queued.")
288291
synchronized(queue) {
289292
ops.reversed().forEach { queue.add(0, it) }
@@ -512,4 +515,8 @@ internal class OperationRepo(
512515
}
513516
}
514517
}
518+
519+
override fun setJwtInvalidatedHandler(handler: ((String) -> Unit)?) {
520+
_jwtInvalidatedHandler = handler
521+
}
515522
}

OneSignalSDK/onesignal/core/src/test/java/com/onesignal/core/internal/operations/OperationRepoTests.kt

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -894,6 +894,88 @@ class OperationRepoTests : FunSpec({
894894
// Verify that the grouped execution happened with both operations
895895
// We can't easily verify the exact list content with MockK, but we verified it in the execution order tracking
896896
}
897+
898+
test("FAIL_UNAUTHORIZED invalidates JWT and fires handler for identified user") {
899+
// Given
900+
val configModelStore =
901+
MockHelper.configModelStore {
902+
it.useIdentityVerification = true
903+
}
904+
val identityModelStore =
905+
MockHelper.identityModelStore {
906+
it.externalId = "test-user"
907+
}
908+
val jwtTokenStore = mockk<JwtTokenStore>(relaxed = true)
909+
every { jwtTokenStore.getJwt("test-user") } returns "valid-jwt"
910+
911+
val operationModelStore =
912+
run {
913+
val operationStoreList = mutableListOf<Operation>()
914+
val mock = mockk<OperationModelStore>()
915+
every { mock.loadOperations() } just runs
916+
every { mock.list() } answers { operationStoreList.toList() }
917+
every { mock.add(any()) } answers { operationStoreList.add(firstArg<Operation>()) }
918+
every { mock.remove(any()) } answers {
919+
val id = firstArg<String>()
920+
operationStoreList.removeIf { it.id == id }
921+
}
922+
mock
923+
}
924+
925+
val executor = mockk<IOperationExecutor>()
926+
every { executor.operations } returns listOf("DUMMY_OPERATION")
927+
coEvery { executor.execute(any()) } returns
928+
ExecutionResponse(ExecutionResult.FAIL_UNAUTHORIZED) andThen
929+
ExecutionResponse(ExecutionResult.SUCCESS)
930+
931+
val operationRepo =
932+
spyk(
933+
OperationRepo(
934+
listOf(executor),
935+
operationModelStore,
936+
configModelStore,
937+
Time(),
938+
getNewRecordState(configModelStore),
939+
jwtTokenStore,
940+
identityModelStore,
941+
),
942+
recordPrivateCalls = true,
943+
)
944+
945+
var handlerCalledWith: String? = null
946+
operationRepo.setJwtInvalidatedHandler { externalId ->
947+
handlerCalledWith = externalId
948+
}
949+
950+
val operation = mockOperation()
951+
every { operation.externalId } returns "test-user"
952+
953+
// When
954+
operationRepo.start()
955+
val response = operationRepo.enqueueAndWait(operation)
956+
957+
// Then
958+
response shouldBe true
959+
verify { jwtTokenStore.invalidateJwt("test-user") }
960+
handlerCalledWith shouldBe "test-user"
961+
}
962+
963+
test("FAIL_UNAUTHORIZED drops operations for anonymous user") {
964+
// Given
965+
val mocks = Mocks()
966+
coEvery { mocks.executor.execute(any()) } returns ExecutionResponse(ExecutionResult.FAIL_UNAUTHORIZED)
967+
968+
val operation = mockOperation()
969+
// externalId defaults to null in mockOperation
970+
971+
// When
972+
mocks.operationRepo.start()
973+
val response = mocks.operationRepo.enqueueAndWait(operation)
974+
975+
// Then
976+
response shouldBe false
977+
verify { mocks.operationModelStore.remove(operation.id) }
978+
}
897979
}) {
898980
companion object {
899981
private fun mockOperation(

0 commit comments

Comments
 (0)