package com.osglab.account.features.auth import com.nimbusds.jose.JWSAlgorithm import com.nimbusds.jose.crypto.ECDSAVerifier import com.nimbusds.jwt.SignedJWT import com.osglab.account.config.AppleConfig import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.shouldBe import java.security.KeyPairGenerator import java.security.interfaces.ECPublicKey import java.security.spec.ECGenParameterSpec import java.time.Clock import java.time.Instant import java.time.ZoneOffset import java.util.Base64 class AppleClientSecretProviderTest : FunSpec({ test("creates a verifiable short-lived ES256 Apple client secret") { val now = Instant.parse("2026-08-16T00:00:00Z") val keyPair = KeyPairGenerator.getInstance("EC").apply { initialize(ECGenParameterSpec("secp256r1")) }.generateKeyPair() val privateKeyPem = Base64.getMimeEncoder(64, "\n".toByteArray()) .encodeToString(keyPair.private.encoded) .let { "-----BEGIN PRIVATE KEY-----\n$it\n-----END PRIVATE KEY-----" } val config = AppleConfig( teamId = "TEAM123", keyId = "KEY123", clientId = "com.example.ios", privateKeyPem = privateKeyPem, jwksUrl = "https://appleid.apple.com/auth/keys", tokenUrl = "https://appleid.apple.com/auth/token", revokeUrl = "https://appleid.apple.com/auth/revoke", ) val serialized = AppleClientSecretProvider( config, Clock.fixed(now, ZoneOffset.UTC), ).create() val jwt = SignedJWT.parse(serialized) jwt.header.algorithm shouldBe JWSAlgorithm.ES256 jwt.header.keyID shouldBe "KEY123" jwt.verify(ECDSAVerifier(keyPair.public as ECPublicKey)) shouldBe true jwt.jwtClaimsSet.issuer shouldBe "TEAM123" jwt.jwtClaimsSet.subject shouldBe "com.example.ios" jwt.jwtClaimsSet.audience shouldBe listOf("https://appleid.apple.com") jwt.jwtClaimsSet.issueTime.toInstant() shouldBe now jwt.jwtClaimsSet.expirationTime.toInstant() shouldBe now.plusSeconds(300) } test("rejects an EC key that is not Apple P-256") { val keyPair = KeyPairGenerator.getInstance("EC").apply { initialize(ECGenParameterSpec("secp384r1")) }.generateKeyPair() val privateKeyPem = Base64.getMimeEncoder(64, "\n".toByteArray()) .encodeToString(keyPair.private.encoded) .let { "-----BEGIN PRIVATE KEY-----\n$it\n-----END PRIVATE KEY-----" } val config = AppleConfig( teamId = "TEAM123", keyId = "KEY123", clientId = "com.example.ios", privateKeyPem = privateKeyPem, jwksUrl = "https://appleid.apple.com/auth/keys", tokenUrl = "https://appleid.apple.com/auth/token", revokeUrl = "https://appleid.apple.com/auth/revoke", ) shouldThrow { AppleClientSecretProvider(config).create() } } })