diff --git a/IMPROVEMENTS.md b/IMPROVEMENTS.md new file mode 100644 index 0000000..863e0bf --- /dev/null +++ b/IMPROVEMENTS.md @@ -0,0 +1,656 @@ +# JWTDecode Shared-Domain Module - Improvement Considerations + +## Overview +This document outlines potential issues and areas for improvement in the `shared-domain` module of the JWTDecode project. The module currently provides basic JWT decoding functionality but requires significant enhancements to be production-ready and enterprise-grade. + +## ๐Ÿšจ Critical Security Issues + +### 1. No Signature Verification +**Current State**: The module only decodes JWTs but doesn't verify them cryptographically. + +**Code Example**: +```kotlin +// Current: Only decodes, doesn't verify +class JWT(private var token: String) { + // โŒ Accepts ANY token with valid format + // โŒ No cryptographic verification + // โŒ Vulnerable to token tampering +} +``` + +**Impact**: High security risk - attackers can modify JWT payloads without detection. + +**Attack Scenario**: +```kotlin +// Attacker can change user role from "user" to "admin" +val maliciousToken = "eyJhbGciOiJIUzI1NiJ9.eyJyb2xlIjoiYWRtaW4ifQ.fake_signature" +val jwt = JWT(maliciousToken) // โœ… Accepted as valid! +``` + +**Recommended Solution**: +```kotlin +interface JWTVerifier { + fun verify(token: String): VerificationResult + fun verify(jwt: JWT): VerificationResult +} + +sealed class VerificationResult { + object Valid : VerificationResult() + object InvalidSignature : VerificationResult() + object Expired : VerificationResult() + object NotYetValid : VerificationResult() + object InvalidIssuer : VerificationResult() +} +``` + +### 2. No Algorithm Validation +**Current State**: No validation of the algorithm specified in the JWT header. + +**Code Example**: +```kotlin +// Current: No algorithm checking +header = parseJson?>(base64Decode(parts[0])) +// โŒ Doesn't validate "alg" field +// โŒ Vulnerable to algorithm confusion attacks +``` + +**Recommended Solution**: +```kotlin +enum class SupportedAlgorithm { + HS256, HS384, HS512, + RS256, RS384, RS512, + ES256, ES384, ES512 +} + +fun validateAlgorithm(header: Map): SupportedAlgorithm +``` + +### 3. No Input Validation +**Current State**: Accepts any string input without validation. + +**Code Example**: +```kotlin +// Current: Accepts any string +init { + decode(token) // โŒ No validation of input + this.token = token +} + +// โŒ Empty strings accepted +// โŒ Null tokens not handled +// โŒ No length limits +// โŒ No character validation +``` + +**Recommended Solution**: +```kotlin +init { + requireNotNull(token) { "Token cannot be null" } + require(token.isNotBlank()) { "Token cannot be empty or blank" } + require(token.length <= MAX_TOKEN_LENGTH) { "Token exceeds maximum length" } + require(token.matches(TOKEN_PATTERN)) { "Token contains invalid characters" } + + decode(token) + this.token = token +} + +companion object { + private const val MAX_TOKEN_LENGTH = 8192 + private val TOKEN_PATTERN = Regex("^[A-Za-z0-9+/=_-]+\\.([A-Za-z0-9+/=_-]+)\\.([A-Za-z0-9+/=_-]*)$") +} +``` + +## โš ๏ธ Functional Issues + +### 4. Limited Error Handling +**Current State**: Generic exception handling with limited error information. + +**Code Example**: +```kotlin +// Current: Generic exceptions +class DecodeException : RuntimeException { + internal constructor(message: String?) : super(message) + internal constructor(message: String?, cause: Throwable?) : super(message, cause) +} + +// โŒ No specific error types +// โŒ No error codes for programmatic handling +// โŒ Internal constructors limit usage +``` + +**Recommended Solution**: +```kotlin +sealed class JWTError : Exception() { + object InvalidFormat : JWTError() + object InvalidBase64 : JWTError() + object InvalidJson : JWTError() + object UnsupportedAlgorithm : JWTError() + object InvalidSignature : JWTError() + object TokenExpired : JWTError() + object TokenNotYetValid : JWTError() + object DecodeFailed : JWTError() +} +``` + +### 5. Time Validation Issues +**Current State**: Basic expiration checking with potential clock manipulation vulnerabilities. + +**Code Example**: +```kotlin +// Current: Basic expiration check +fun isExpired(leeway: Duration): Boolean { + val todayTime = Instant.fromEpochSeconds(Clock.System.now().epochSeconds) + // โŒ Clock.System.now() can be manipulated + // โŒ No timezone handling + // โŒ No clock skew tolerance +} +``` + +**Recommended Solution**: +```kotlin +interface ClockProvider { + fun now(): Instant + fun getClockSkew(): Duration +} + +fun isExpired(leeway: Duration, clockProvider: ClockProvider = SystemClockProvider): Boolean { + val now = clockProvider.now() + val clockSkew = clockProvider.getClockSkew() + val adjustedLeeway = leeway + clockSkew + + // Enhanced validation logic +} +``` + +## ๐Ÿ—๏ธ Architecture Issues + +### 6. Decoding Logic in Constructor (Anti-Pattern) +**Current State**: The JWT class performs expensive decoding operations in its constructor. + +**Code Example**: +```kotlin +// Current: Anti-pattern - expensive operations in constructor +class JWT(private var token: String) { + private var payload: JWTPayload? = null + + init { + decode(token) // โŒ Expensive operation in constructor + this.token = token + } + + private fun decode(token: String) { + val parts = splitToken(token) + header = parseJson?>(base64Decode(parts[0])) + payload = parseJson(base64Decode(parts[1])) + signature = parts[2] + } +} +``` + +**Problems with Current Approach**: +- **Constructor Side Effects**: Triggers decoding, file I/O, network calls, etc. +- **Exception Handling Issues**: Constructors throwing exceptions for business logic +- **Performance Problems**: Every object creation triggers expensive operations +- **Testing Difficulties**: Hard to test without valid tokens +- **Memory Allocation Issues**: Objects may be created but never used + +**Recommended Solutions**: + +#### **Solution 1: Factory Pattern with Lazy Decoding** +```kotlin +class JWT private constructor( + private val token: String, + private val header: Map?, + private val payload: JWTPayload?, + private val signature: String? +) { + companion object { + fun decode(token: String): JWT { + return try { + val parts = splitToken(token) + val header = parseJson?>(base64Decode(parts[0])) + val payload = parseJson(base64Decode(parts[1])) + val signature = parts[2] + + JWT(token, header, payload, signature) + } catch (e: Exception) { + throw JWTError.DecodeFailed("Failed to decode token", e) + } + } + + fun createUnverified(token: String): JWT { + return JWT(token, null, null, null) + } + } + + // Lazy initialization of decoded data + private val decodedHeader by lazy { header ?: decodeHeader() } + private val decodedPayload by lazy { payload ?: decodePayload() } +} +``` + +#### **Solution 2: Separate Decoder and JWT Classes** +```kotlin +// Separate concerns +class JWTDecoder { + fun decode(token: String): JWT { + val parts = splitToken(token) + val header = parseJson?>(base64Decode(parts[0])) + val payload = parseJson(base64Decode(parts[1])) + val signature = parts[2] + + return JWT(token, header, payload, signature) + } +} + +// JWT class becomes a simple data holder +class JWT( + val token: String, + val header: Map?, + val payload: JWTPayload?, + val signature: String? +) { + val issuer: String? get() = payload?.iss + val subject: String? get() = payload?.sub +} +``` + +### 7. Tight Coupling +**Current State**: Direct instantiation makes testing and extension difficult. + +**Code Example**: +```kotlin +// Current: Direct instantiation +class JWT(private var token: String) { + // โŒ Hard to mock for testing + // โŒ Hard to extend + // โŒ Violates dependency inversion +} +``` + +**Recommended Solution**: +```kotlin +interface JWTDecoder { + fun decode(token: String): JWT +} + +interface JWTValidator { + fun validate(jwt: JWT): ValidationResult +} + +class JWTDecoderImpl( + private val cache: JWTCache, + private val validator: JWTValidator +) : JWTDecoder { + // Implementation with dependencies injected +} +``` + +### 8. No Interface Abstraction +**Current State**: Concrete class only, no abstraction layer. + +**Recommended Solution**: +```kotlin +interface JWT { + val header: Map + val payload: JWTPayload + val signature: String? + + fun getClaim(name: String): Claim + fun isExpired(leeway: Duration): Boolean +} + +class JWTImpl( + private val token: String, + private val header: Map, + private val payload: JWTPayload, + private val signature: String? +) : JWT { + // Implementation +} +``` + +### 9. Violation of Single Responsibility +**Current State**: JWT class handles multiple responsibilities. + +**Current Responsibilities**: +- โŒ Decoding +- โŒ Validation +- โŒ Claim access +- โŒ Time checking +- โŒ String representation + +**Recommended Solution**: +```kotlin +// Separate concerns into different classes +class JWTDecoder { /* Decoding logic */ } +class JWTValidator { /* Validation logic */ } +class JWTClaims { /* Claim access logic */ } +class JWTTimeValidator { /* Time validation logic */ } +class JWTRepresentation { /* String representation logic */ } + +class JWT( + private val decoder: JWTDecoder, + private val validator: JWTValidator, + private val claims: JWTClaims, + private val timeValidator: JWTTimeValidator, + private val representation: JWTRepresentation +) { + // Orchestrates the different components +} +``` + +## ๐ŸŒ Performance Issues + +### 10. No Caching Mechanism +**Current State**: Decodes tokens from scratch every time. + +**Code Example**: +```kotlin +// Current: Decodes every time +val jwt = JWT(token) // โŒ Always decodes from scratch +val jwt2 = JWT(token) // โŒ Decodes again even for same token +``` + +**Recommended Solution**: +```kotlin +interface JWTCache { + fun get(token: String): CachedJWT? + fun put(token: String, jwt: CachedJWT) + fun invalidate(token: String) + fun clear() +} + +data class CachedJWT( + val jwt: JWT, + val cachedAt: Instant, + val ttl: Duration +) { + fun isExpired(): Boolean = Instant.now().isAfter(cachedAt + ttl) +} +``` + +### 11. Memory Allocation Issues +**Current State**: Creates new objects for each claim access. + +**Code Example**: +```kotlin +// Current: Creates new objects for each claim access +fun getClaim(name: String): Claim { + return payload?.claimForName(name) ?: BaseClaim() // โŒ New object each time +} +``` + +**Recommended Solution**: +```kotlin +companion object { + private val EMPTY_CLAIM = BaseClaim() +} + +fun getClaim(name: String): Claim { + return payload?.claimForName(name) ?: EMPTY_CLAIM +} +``` + +## ๐Ÿงช Testing Issues + +### 13. Limited Test Coverage +**Current State**: Tests focus mainly on happy path scenarios. + +**Current Limitations**: +- โŒ No stress testing with large tokens +- โŒ No memory leak testing +- โŒ No performance benchmarking +- โŒ No concurrent access testing + +**Recommended Improvements**: +```kotlin +@Test +fun `should handle large tokens efficiently`() { + val largeToken = generateLargeToken(10000) + val startTime = System.currentTimeMillis() + + val jwt = JWT(largeToken) + + val endTime = System.currentTimeMillis() + assertTrue(endTime - startTime < 100) // Should complete within 100ms +} + +@Test +fun `should not leak memory with repeated operations`() { + repeat(1000) { + JWT(validToken) + } + // Verify memory usage hasn't increased significantly +} +``` + +## ๐Ÿ“š Documentation Issues + +### 14. Poor API Documentation +**Current State**: Basic KDoc without comprehensive examples. + +**Code Example**: +```kotlin +// Current: Basic KDoc +/** + * Get the value of the "iss" claim, or null if it's not available. + * + * @return the Issuer value or null. + */ +val issuer: String? get() = payload?.iss + +// โŒ No usage examples +// โŒ No error scenarios +// โŒ No performance considerations +``` + +**Recommended Improvement**: +```kotlin +/** + * Get the value of the "iss" claim, or null if it's not available. + * + * The issuer claim identifies the principal that issued the JWT. + * This is typically the authorization server or identity provider. + * + * @return the Issuer value or null if not present + * + * @example + * ```kotlin + * val jwt = JWT(token) + * val issuer = jwt.issuer + * if (issuer != null) { + * println("Token issued by: $issuer") + * } + * ``` + * + * @throws JWTError.InvalidFormat if the token format is invalid + * @throws JWTError.InvalidJson if the payload contains malformed JSON + */ +val issuer: String? get() = payload?.iss +``` + +### 15. No Migration Guide +**Current Limitations**: +- โŒ No version compatibility notes +- โŒ No breaking change documentation +- โŒ No upgrade path +- โŒ No deprecation warnings + +## ๐Ÿ“ฑ Example Applications + +### 16. Missing Sample Applications +**Current State**: No example applications demonstrating how to use the JWTDecode library. + +**Missing Examples**: +- โŒ No Android sample app +- โŒ No iOS sample app +- โŒ No JVM/Desktop sample app +- โŒ No web sample app +- โŒ No integration examples with popular frameworks + +**Recommended Solutions**: + +#### **Android Sample App** +```kotlin +// Example: Android app with JWT authentication +class LoginActivity : AppCompatActivity() { + private lateinit var jwtDecoder: JWTDecoder + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_login) + + jwtDecoder = JWTDecoder() + + loginButton.setOnClickListener { + val token = tokenInput.text.toString() + try { + val jwt = jwtDecoder.decode(token) + if (jwt.isExpired(0.seconds)) { + showError("Token has expired") + } else { + navigateToMain(jwt) + } + } catch (e: JWTError) { + showError("Invalid token: ${e.message}") + } + } + } +} +``` + +#### **iOS Sample App** +```swift +// Example: iOS app with JWT validation +class LoginViewController: UIViewController { + private let jwtDecoder = JWTDecoder() + + @IBAction func loginButtonTapped(_ sender: UIButton) { + guard let token = tokenTextField.text else { return } + + do { + let jwt = try jwtDecoder.decode(token: token) + if jwt.isExpired(leeway: 0) { + showAlert(message: "Token has expired") + } else { + navigateToMain(jwt: jwt) + } + } catch { + showAlert(message: "Invalid token: \(error.localizedDescription)") + } + } +} +``` + +#### **Integration Examples** +```kotlin +// Example: Spring Boot integration +@Service +class JWTService( + private val jwtDecoder: JWTDecoder, + private val jwtValidator: JWTValidator +) { + fun validateToken(token: String): ValidationResult { + return try { + val jwt = jwtDecoder.decode(token) + jwtValidator.validate(jwt) + } catch (e: JWTError) { + ValidationResult.Invalid(e) + } + } +} +``` + +### **Benefits of Adding Examples** +1. **Developer Onboarding** - Faster adoption of the library +2. **Best Practices** - Show recommended usage patterns +3. **Integration Guidance** - Demonstrate framework integration +4. **Testing** - Examples serve as integration tests +5. **Documentation** - Living documentation of API usage +6. **Community** - Encourage contributions and feedback + +## ๐Ÿ”’ Security Enhancement Areas + +### 17. Add Key Management +**Implementation**: +```kotlin +interface JWTKeyProvider { + fun getKey(algorithm: SupportedAlgorithm, keyId: String?): CryptoKey + fun getPublicKey(issuer: String): PublicKey +} +``` + +## ๐Ÿ“Š Performance Enhancement Areas + +### 18. Add Streaming Support +**Implementation**: +```kotlin +// For large JWTs +interface JWTStreamDecoder { + fun decodeHeader(input: InputStream): JWTHeader + fun decodePayload(input: InputStream): JWTPayload +} +``` + +### 19. Add Batch Processing +**Implementation**: +```kotlin +interface JWTBatchProcessor { + fun decodeBatch(tokens: List): List + fun validateBatch(jwts: List): List +} +``` + +## ๐Ÿ”„ Future-Proofing Areas + +### 20. Add Plugin System +**Implementation**: +```kotlin +interface JWTPlugin { + fun beforeDecode(token: String): String + fun afterDecode(jwt: JWT): JWT + fun beforeValidate(jwt: JWT): JWT +} +``` + +### 21. Add Metrics and Monitoring +**Implementation**: +```kotlin +interface JWTMetrics { + fun recordDecodeTime(duration: Duration) + fun recordValidationResult(result: ValidationResult) + fun recordError(error: JWTError) +} +``` + +## ๐Ÿ“‹ Priority Ranking + +### ๐Ÿ”ฅ Critical (Fix Immediately) +1. **Signature verification** - Security vulnerability +2. **Algorithm validation** - Security vulnerability +3. **Input validation** - Security vulnerability +4. **Constructor anti-pattern** - Fundamental architectural issue +5. **Error handling improvements** - Production readiness + +### โšก High Priority (Next Sprint) +6. **Performance optimizations** - User experience +7. **Caching mechanism** - Performance improvement +8. **Better architecture** - Maintainability +9. **Comprehensive testing** - Quality assurance + +### ๐Ÿ“ˆ Medium Priority (Next Release) +10. **Platform optimizations** - Cross-platform support +11. **Documentation improvements** - Developer experience +12. **Sample applications** - Developer onboarding +13. **Metrics and monitoring** - Observability + +### ๐Ÿš€ Low Priority (Future Releases) +14. **Plugin system** - Extensibility +15. **Advanced features** - Feature completeness +16. **Performance benchmarking** - Optimization + +## ๐Ÿ”— Related Resources + +- [JWT RFC 7519](https://tools.ietf.org/html/rfc7519) +- [OWASP JWT Security Guidelines](https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/06-Session_Management_Testing/10-Testing_JWT_Token) diff --git a/shared-domain/src/commonMain/kotlin/dev/sdkforge/jwt/decode/domain/JWT.kt b/shared-domain/src/commonMain/kotlin/dev/sdkforge/jwt/decode/domain/JWT.kt index d6485d5..5f564ea 100644 --- a/shared-domain/src/commonMain/kotlin/dev/sdkforge/jwt/decode/domain/JWT.kt +++ b/shared-domain/src/commonMain/kotlin/dev/sdkforge/jwt/decode/domain/JWT.kt @@ -167,7 +167,7 @@ class JWT(private var token: String) { string ?: return null try { - return Base64.UrlSafe.withPadding(Base64.PaddingOption.ABSENT).decode(string).decodeToString() + return base64Decoder.decode(string).decodeToString() } catch (e: IllegalArgumentException) { throw DecodeException("Received bytes didn't correspond to a valid Base64 encoded string.", e) } @@ -185,5 +185,6 @@ class JWT(private var token: String) { companion object { private val TAG: String? = JWT::class.simpleName + private val base64Decoder = Base64.UrlSafe.withPadding(Base64.PaddingOption.ABSENT) } } diff --git a/shared-domain/src/commonMain/kotlin/dev/sdkforge/jwt/decode/domain/JWTPayload.kt b/shared-domain/src/commonMain/kotlin/dev/sdkforge/jwt/decode/domain/JWTPayload.kt index e3ecd60..22fd141 100644 --- a/shared-domain/src/commonMain/kotlin/dev/sdkforge/jwt/decode/domain/JWTPayload.kt +++ b/shared-domain/src/commonMain/kotlin/dev/sdkforge/jwt/decode/domain/JWTPayload.kt @@ -62,7 +62,7 @@ object ClaimAsStringSerializer : KSerializer { } override fun deserialize(decoder: Decoder): Claim { - return ClaimImpl(Json.parseToJsonElement(decoder.decodeString())) + return JsonClaim(Json.parseToJsonElement(decoder.decodeString())) } } diff --git a/shared-domain/src/commonMain/kotlin/dev/sdkforge/jwt/decode/domain/ClaimImpl.kt b/shared-domain/src/commonMain/kotlin/dev/sdkforge/jwt/decode/domain/JsonClaim.kt similarity index 97% rename from shared-domain/src/commonMain/kotlin/dev/sdkforge/jwt/decode/domain/ClaimImpl.kt rename to shared-domain/src/commonMain/kotlin/dev/sdkforge/jwt/decode/domain/JsonClaim.kt index fe5c7d0..c15ff58 100644 --- a/shared-domain/src/commonMain/kotlin/dev/sdkforge/jwt/decode/domain/ClaimImpl.kt +++ b/shared-domain/src/commonMain/kotlin/dev/sdkforge/jwt/decode/domain/JsonClaim.kt @@ -20,7 +20,7 @@ import kotlinx.serialization.json.longOrNull * The ClaimImpl class implements the Claim interface. */ @OptIn(ExperimentalTime::class) -internal class ClaimImpl(private val value: JsonElement) : BaseClaim() { +internal class JsonClaim(private val value: JsonElement) : BaseClaim() { override fun asBoolean(): Boolean? = when (value) { !is JsonPrimitive -> null diff --git a/shared-domain/src/commonTest/kotlin/dev/sdkforge/jwt/decode/domain/ClaimImplTest.kt b/shared-domain/src/commonTest/kotlin/dev/sdkforge/jwt/decode/domain/ClaimImplTest.kt index 226a208..60b511d 100644 --- a/shared-domain/src/commonTest/kotlin/dev/sdkforge/jwt/decode/domain/ClaimImplTest.kt +++ b/shared-domain/src/commonTest/kotlin/dev/sdkforge/jwt/decode/domain/ClaimImplTest.kt @@ -21,7 +21,7 @@ class ClaimImplTest { @Test fun shouldGetBooleanValue() { val value: JsonElement = json.encodeToJsonElement(true) - val claim = ClaimImpl(value) + val claim = JsonClaim(value) assertNotNull(claim.asBoolean()) assertEquals(true, claim.asBoolean()) @@ -30,7 +30,7 @@ class ClaimImplTest { @Test fun shouldGetNullBooleanIfNotPrimitiveValue() { val value: JsonElement = json.encodeToJsonElement(Unit) - val claim = ClaimImpl(value) + val claim = JsonClaim(value) assertNull(claim.asBoolean()) } @@ -38,7 +38,7 @@ class ClaimImplTest { @Test fun shouldGetIntValue() { val value: JsonElement = json.encodeToJsonElement(123) - val claim = ClaimImpl(value) + val claim = JsonClaim(value) assertNotNull(claim.asInt()) assertEquals(123, claim.asInt()) @@ -47,7 +47,7 @@ class ClaimImplTest { @Test fun shouldGetLongValue() { val value: JsonElement = json.encodeToJsonElement(123L) - val claim = ClaimImpl(value) + val claim = JsonClaim(value) assertNotNull(claim.asLong()) assertEquals(123L, claim.asLong()) @@ -56,7 +56,7 @@ class ClaimImplTest { @Test fun shouldGetNullIntIfNotPrimitiveValue() { val value: JsonElement = json.encodeToJsonElement(Unit) - val claim = ClaimImpl(value) + val claim = JsonClaim(value) assertNull(claim.asInt()) } @@ -64,7 +64,7 @@ class ClaimImplTest { @Test fun shouldGetNullLongIfNotPrimitiveValue() { val value: JsonElement = json.encodeToJsonElement(Unit) - val claim = ClaimImpl(value) + val claim = JsonClaim(value) assertNull(claim.asLong()) } @@ -72,7 +72,7 @@ class ClaimImplTest { @Test fun shouldGetDoubleValue() { val value: JsonElement = json.encodeToJsonElement(1.5) - val claim = ClaimImpl(value) + val claim = JsonClaim(value) assertNotNull(claim.asDouble()) assertEquals(1.5, claim.asDouble()) @@ -81,7 +81,7 @@ class ClaimImplTest { @Test fun shouldGetNullDoubleIfNotPrimitiveValue() { val value: JsonElement = json.encodeToJsonElement(Unit) - val claim = ClaimImpl(value) + val claim = JsonClaim(value) assertNull(claim.asDouble()) } @@ -90,7 +90,7 @@ class ClaimImplTest { fun shouldGetLargeDateValue() { val seconds: Long = Int.MAX_VALUE + 10000L val value: JsonElement = json.encodeToJsonElement(seconds) - val claim = ClaimImpl(value) + val claim = JsonClaim(value) val date: Instant? = claim.asDate() assertNotNull(date) @@ -101,7 +101,7 @@ class ClaimImplTest { @Test fun shouldGetDateValue() { val value: JsonElement = json.encodeToJsonElement("1476824844") - val claim = ClaimImpl(value) + val claim = JsonClaim(value) assertNotNull(claim.asDate()) assertEquals(Instant.fromEpochSeconds(1476824844), claim.asDate()) @@ -110,7 +110,7 @@ class ClaimImplTest { @Test fun shouldGetNullDateIfNotPrimitiveValue() { val value: JsonElement = json.encodeToJsonElement(Unit) - val claim = ClaimImpl(value) + val claim = JsonClaim(value) assertNull(claim.asDate()) } @@ -118,7 +118,7 @@ class ClaimImplTest { @Test fun shouldGetStringValue() { val value: JsonElement = json.encodeToJsonElement("string") - val claim = ClaimImpl(value) + val claim = JsonClaim(value) assertNotNull(claim.asString()) assertEquals("string", claim.asString()) @@ -127,7 +127,7 @@ class ClaimImplTest { @Test fun shouldGetNullStringIfNotPrimitiveValue() { val value: JsonElement = json.encodeToJsonElement(Unit) - val claim = ClaimImpl(value) + val claim = JsonClaim(value) assertNull(claim.asString()) } @@ -135,7 +135,7 @@ class ClaimImplTest { @Test fun shouldGetListValueOfCustomClass() { val value: JsonElement = json.encodeToJsonElement(listOf(UserPojo("George", 1), UserPojo("Mark", 2))) - val claim = ClaimImpl(value) + val claim = JsonClaim(value) assertNotNull(claim.asList(UserPojo.serializer())) assertContentEquals( @@ -147,7 +147,7 @@ class ClaimImplTest { @Test fun shouldGetListValue() { val value: JsonElement = json.encodeToJsonElement(listOf("string1", "string2")) - val claim = ClaimImpl(value) + val claim = JsonClaim(value) assertNotNull(claim.asList(String.serializer())) assertContentEquals( @@ -159,7 +159,7 @@ class ClaimImplTest { @Test fun shouldGetEmptyListIfNullValue() { val value: JsonElement = json.encodeToJsonElement(null.orEmpty()) - val claim = ClaimImpl(value) + val claim = JsonClaim(value) assertNotNull(claim.asList(String.serializer())) assertContentEquals( @@ -171,7 +171,7 @@ class ClaimImplTest { @Test fun shouldGetEmptyListIfNonArrayValue() { val value: JsonElement = json.encodeToJsonElement(1) - val claim = ClaimImpl(value) + val claim = JsonClaim(value) assertNotNull(claim.asList(String.serializer())) assertContentEquals( @@ -183,7 +183,7 @@ class ClaimImplTest { @Test fun shouldThrowIfListClassMismatch() { val value: JsonElement = json.encodeToJsonElement(arrayOf("keys", "values")) - val claim = ClaimImpl(value) + val claim = JsonClaim(value) assertFailsWith { claim.asList(UserPojo.serializer()) @@ -194,13 +194,13 @@ class ClaimImplTest { fun shouldGetAsObject() { val data = UserPojo("George", 1) val userValue: JsonElement = json.encodeToJsonElement(data) - val userClaim = ClaimImpl(userValue) + val userClaim = JsonClaim(userValue) val intValue: JsonElement = json.encodeToJsonElement(1) - val intClaim = ClaimImpl(intValue) + val intClaim = JsonClaim(intValue) val booleanValue: JsonElement = json.encodeToJsonElement(true) - val booleanClaim = ClaimImpl(booleanValue) + val booleanClaim = JsonClaim(booleanValue) assertNotNull(userClaim.asObject(UserPojo.serializer())) assertEquals(UserPojo("George", 1), userClaim.asObject(UserPojo.serializer())) @@ -214,7 +214,7 @@ class ClaimImplTest { @Test fun shouldGetNullObjectIfNullValue() { - val claim = ClaimImpl(JsonNull) + val claim = JsonClaim(JsonNull) assertNull(claim.asObject(UserPojo.serializer())) } @@ -222,7 +222,7 @@ class ClaimImplTest { @Test fun shouldThrowIfObjectClassMismatch() { val value: JsonElement = json.encodeToJsonElement(1) - val claim = ClaimImpl(value) + val claim = JsonClaim(value) assertFailsWith { claim.asObject(UserPojo.serializer()) diff --git a/shared-domain/src/commonTest/kotlin/dev/sdkforge/jwt/decode/domain/JWTTest.kt b/shared-domain/src/commonTest/kotlin/dev/sdkforge/jwt/decode/domain/JWTTest.kt index 4415bd7..5b6f068 100644 --- a/shared-domain/src/commonTest/kotlin/dev/sdkforge/jwt/decode/domain/JWTTest.kt +++ b/shared-domain/src/commonTest/kotlin/dev/sdkforge/jwt/decode/domain/JWTTest.kt @@ -343,7 +343,7 @@ class JWTTest { fun shouldGetBaseClaimIfClaimIsMissing() { val jwt = JWT("eyJhbGciOiJIUzI1NiJ9.e30.K17vlwhE8FCMShdl1_65jEYqsQqBOVMPUU9IgG-QlTM") assertNotNull(jwt.getClaim("notExisting")) - assertTrue { jwt.getClaim("notExisting") is ClaimImpl } + assertTrue { jwt.getClaim("notExisting") is JsonClaim } assertTrue { jwt.getClaim("notExisting") is BaseClaim } } @@ -352,7 +352,7 @@ class JWTTest { val jwt = JWT("eyJhbGciOiJIUzI1NiJ9.eyJvYmplY3QiOnsibmFtZSI6ImpvaG4ifX0.lrU1gZlOdlmTTeZwq0VI-pZx2iV46UWYd5-lCjy6-c4") assertNotNull(jwt.getClaim("object")) - assertTrue { jwt.getClaim("object") is ClaimImpl } + assertTrue { jwt.getClaim("object") is JsonClaim } } @Test @@ -367,7 +367,7 @@ class JWTTest { assertNotNull(objectClaim) - assertTrue { objectClaim is ClaimImpl } + assertTrue { objectClaim is JsonClaim } val extraClaim: Claim = claims["sub"]!!