diff --git a/src/main/kotlin/com/osglab/account/features/storekit/services/StoreKitService.kt b/src/main/kotlin/com/osglab/account/features/storekit/services/StoreKitService.kt index 80599c7..9864978 100644 --- a/src/main/kotlin/com/osglab/account/features/storekit/services/StoreKitService.kt +++ b/src/main/kotlin/com/osglab/account/features/storekit/services/StoreKitService.kt @@ -123,7 +123,7 @@ class StoreKitService( } val now = clock.instant() if ( - transaction.purchasedAt > transaction.signedAt || + transaction.purchasedAt > transaction.signedAt.plus(MAX_CLOCK_SKEW) || transaction.signedAt > now.plus(MAX_CLOCK_SKEW) ) { throw StoreKitPurchaseConflict() diff --git a/src/test/kotlin/com/osglab/account/features/storekit/StoreKitServiceTest.kt b/src/test/kotlin/com/osglab/account/features/storekit/StoreKitServiceTest.kt index cf0f822..cc3981c 100644 --- a/src/test/kotlin/com/osglab/account/features/storekit/StoreKitServiceTest.kt +++ b/src/test/kotlin/com/osglab/account/features/storekit/StoreKitServiceTest.kt @@ -33,14 +33,16 @@ class StoreKitServiceTest : FunSpec({ accountToken: UUID = userId, productId: String = product.productId, revokedAt: Instant? = null, + purchasedAt: Instant = now.minusSeconds(10), + signedAt: Instant = now.minusSeconds(5), ) = VerifiedStoreKitTransaction( transactionId = transactionId, originalTransactionId = transactionId, appAccountToken = accountToken, productId = productId, environment = StoreKitEnvironment.SANDBOX, - purchasedAt = now.minusSeconds(10), - signedAt = now.minusSeconds(5), + purchasedAt = purchasedAt, + signedAt = signedAt, revokedAt = revokedAt, ) @@ -115,6 +117,33 @@ class StoreKitServiceTest : FunSpec({ store.ledger shouldHaveSize 0 } + test("small App Store signing clock skew is accepted") { + val store = TestBillingStore() + val verified = transaction( + purchasedAt = now.minusSeconds(10), + signedAt = now.minusSeconds(70), + ) + + val result = service(store, verified).submit(userId, signedTransaction) + + result.balanceAfter shouldBeExactly 3_000 + store.ledger shouldHaveSize 1 + } + + test("App Store signing clock skew beyond the tolerance is rejected") { + val store = TestBillingStore() + val verified = transaction( + purchasedAt = now.minusSeconds(10), + signedAt = now.minusSeconds(311), + ) + + shouldThrow { + service(store, verified).submit(userId, signedTransaction) + } + + store.ledger shouldHaveSize 0 + } + test("unknown or revoked products never grant credits") { val unknownStore = TestBillingStore() shouldThrow {