3 Commits

Author SHA1 Message Date
35c7712f85 [RELEASE] - release version: 0.4.0 2025-01-02 07:26:41 +01:00
84d09f6dbb replace local jwt with library version
- update ConfigurationController to handle hlaeja jwt instead of jwtService
- update MeasurementController to handle hlaeja jwt instead of jwtService
- add dependency for hlaeja jwt
- remove dependencies for jjwt
- remove JwtService.kt
- remove PublicKeyProvider.kt
- remove jwt key property explanation from additional-spring-configuration-metadata.json
2025-01-02 06:48:12 +01:00
c5ff6e555a [RELEASE] - bump version 2024-12-28 08:05:01 +01:00
10 changed files with 37 additions and 96 deletions

4
.gitignore vendored
View File

@@ -39,5 +39,5 @@ out/
### Kotlin ### ### Kotlin ###
.kotlin .kotlin
### cert ### #### Hlæja ###
cert/ /cert/

View File

@@ -16,6 +16,7 @@ Classes and endpoints, to shape and to steer, Devices and sensors, their purpose
| spring.data.redis.host | ✓ | Redis host | | spring.data.redis.host | ✓ | Redis host |
| spring.data.redis.port | | Redis port | | spring.data.redis.port | | Redis port |
| spring.data.redis.database | ✓ | Redis database | | spring.data.redis.database | ✓ | Redis database |
| spring.data.redis.password | ✗ | Redis password |
| cache.time-to-live | | Cache time to live (minutes) | | cache.time-to-live | | Cache time to live (minutes) |
| jwt.public-key | ✓ | JWT public key | | jwt.public-key | ✓ | JWT public key |
| device-registry.url | ✓ | Device Register URL | | device-registry.url | ✓ | Device Register URL |

View File

@@ -17,14 +17,12 @@ dependencies {
implementation(hlaeja.kotlinx.coroutines) implementation(hlaeja.kotlinx.coroutines)
implementation(hlaeja.micrometer.registry.influx) implementation(hlaeja.micrometer.registry.influx)
implementation(hlaeja.library.hlaeja.common.messages) implementation(hlaeja.library.hlaeja.common.messages)
implementation(hlaeja.library.hlaeja.jwt)
implementation(hlaeja.springboot.starter.actuator) implementation(hlaeja.springboot.starter.actuator)
implementation(hlaeja.springboot.starter.cache) implementation(hlaeja.springboot.starter.cache)
implementation(hlaeja.springboot.starter.redis) implementation(hlaeja.springboot.starter.redis)
implementation(hlaeja.springboot.starter.webflux) implementation(hlaeja.springboot.starter.webflux)
runtimeOnly(hlaeja.jjwt.impl)
runtimeOnly(hlaeja.jjwt.jackson)
testImplementation(hlaeja.kotlin.test.junit5) testImplementation(hlaeja.kotlin.test.junit5)
testImplementation(hlaeja.kotlinx.coroutines.test) testImplementation(hlaeja.kotlinx.coroutines.test)
testImplementation(hlaeja.mockk) testImplementation(hlaeja.mockk)

View File

@@ -1,7 +1,7 @@
kotlin.code.style=official kotlin.code.style=official
org.gradle.jvmargs=-Xmx1g org.gradle.jvmargs=-Xmx1g
version=0.3.0 version=0.4.0
catalog=0.7.0 catalog=0.8.0
docker.port.expose=8443 docker.port.expose=8443
container.port.expose=8443 container.port.expose=8443
container.port.host=9000 container.port.host=9000

View File

@@ -1,7 +1,9 @@
package ltd.hlaeja.controller package ltd.hlaeja.controller
import java.util.UUID
import ltd.hlaeja.jwt.service.PublicJwtService
import ltd.hlaeja.service.DeviceConfigurationService import ltd.hlaeja.service.DeviceConfigurationService
import ltd.hlaeja.service.JwtService import ltd.hlaeja.service.DeviceRegistryService
import ltd.hlaeja.util.toDeviceResponse import ltd.hlaeja.util.toDeviceResponse
import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestHeader import org.springframework.web.bind.annotation.RequestHeader
@@ -12,12 +14,17 @@ import org.springframework.web.bind.annotation.RestController
@RequestMapping("/configuration") @RequestMapping("/configuration")
class ConfigurationController( class ConfigurationController(
private val configurationService: DeviceConfigurationService, private val configurationService: DeviceConfigurationService,
private val jwtService: JwtService, private val deviceRegistry: DeviceRegistryService,
private val publicJwtService: PublicJwtService,
) { ) {
@GetMapping @GetMapping
suspend fun getNodeConfiguration( suspend fun getNodeConfiguration(
@RequestHeader("Identity") identityToken: String, @RequestHeader("Identity") identityToken: String,
): Map<String, String> = jwtService.getIdentity(identityToken) ): Map<String, String> = readIdentityToken(identityToken)
.let { deviceRegistry.getIdentityFromDevice(it) }
.let { configurationService.getConfiguration(it.node).toDeviceResponse() } .let { configurationService.getConfiguration(it.node).toDeviceResponse() }
private fun readIdentityToken(identityToken: String): UUID = publicJwtService
.verify(identityToken) { claims -> UUID.fromString(claims.payload["device"] as String) }
} }

View File

@@ -1,8 +1,11 @@
package ltd.hlaeja.controller package ltd.hlaeja.controller
import java.util.UUID
import ltd.hlaeja.jwt.service.PublicJwtService
import ltd.hlaeja.library.deviceData.MeasurementData import ltd.hlaeja.library.deviceData.MeasurementData
import ltd.hlaeja.library.deviceRegistry.Identity
import ltd.hlaeja.service.DeviceDataService import ltd.hlaeja.service.DeviceDataService
import ltd.hlaeja.service.JwtService import ltd.hlaeja.service.DeviceRegistryService
import org.springframework.http.HttpStatus.CREATED import org.springframework.http.HttpStatus.CREATED
import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.PostMapping
@@ -16,13 +19,14 @@ import org.springframework.web.bind.annotation.RestController
@RequestMapping("/measurement") @RequestMapping("/measurement")
class MeasurementController( class MeasurementController(
private val dataService: DeviceDataService, private val dataService: DeviceDataService,
private val jwtService: JwtService, private val deviceRegistry: DeviceRegistryService,
private val publicJwtService: PublicJwtService,
) { ) {
@GetMapping @GetMapping
suspend fun getNodeMeasurement( suspend fun getNodeMeasurement(
@RequestHeader("Identity") identityToken: String, @RequestHeader("Identity") identityToken: String,
): Map<String, Number> = jwtService.getIdentity(identityToken) ): Map<String, Number> = readIdentityToken(identityToken)
.let { dataService.getMeasurement(it.client, it.node).fields } .let { dataService.getMeasurement(it.client, it.node).fields }
@PostMapping @PostMapping
@@ -31,7 +35,7 @@ class MeasurementController(
@RequestHeader("Identity") identityToken: String, @RequestHeader("Identity") identityToken: String,
@RequestBody measurement: Map<String, Number>, @RequestBody measurement: Map<String, Number>,
) { ) {
return jwtService.getIdentity(identityToken) return readIdentityToken(identityToken)
.let { .let {
dataService.addMeasurement( dataService.addMeasurement(
it.client, it.client,
@@ -45,4 +49,8 @@ class MeasurementController(
) )
} }
} }
private suspend fun readIdentityToken(identityToken: String): Identity.Response = publicJwtService
.verify(identityToken) { claims -> UUID.fromString(claims.payload["device"] as String) }
.let { deviceRegistry.getIdentityFromDevice(it) }
} }

View File

@@ -1,42 +0,0 @@
package ltd.hlaeja.service
import io.github.oshai.kotlinlogging.KotlinLogging
import io.jsonwebtoken.JwtException
import io.jsonwebtoken.JwtParser
import io.jsonwebtoken.Jwts
import java.util.UUID
import ltd.hlaeja.library.deviceRegistry.Identity
import ltd.hlaeja.property.JwtProperty
import ltd.hlaeja.util.PublicKeyProvider
import org.springframework.http.HttpStatus
import org.springframework.stereotype.Service
import org.springframework.web.server.ResponseStatusException
private val log = KotlinLogging.logger {}
@Service
class JwtService(
jwtProperty: JwtProperty,
private val deviceRegistry: DeviceRegistryService,
) {
private val parser: JwtParser = Jwts.parser()
.verifyWith(PublicKeyProvider.load(jwtProperty.publicKey))
.build()
suspend fun getIdentity(
identityToken: String,
): Identity.Response = try {
readIdentity(identityToken)
.let { deviceRegistry.getIdentityFromDevice(it) }
} catch (e: JwtException) {
log.warn { e.localizedMessage }
throw ResponseStatusException(HttpStatus.UNAUTHORIZED)
}
private suspend fun readIdentity(
identity: String,
): UUID = parser.parseSignedClaims(identity)
.let { UUID.fromString(it.payload["device"] as String) }
.also { log.debug { "Identified client device: $it" } }
}

View File

@@ -1,35 +0,0 @@
package ltd.hlaeja.util
import java.security.KeyFactory
import java.security.interfaces.RSAPublicKey
import java.security.spec.X509EncodedKeySpec
import java.util.Base64.getDecoder
import ltd.hlaeja.exception.KeyProviderException
object PublicKeyProvider {
fun load(
pemFile: String,
): RSAPublicKey = readPublicPemFile(pemFile)
.let(::makePublicKey)
private fun makePublicKey(
publicKeyBytes: ByteArray,
): RSAPublicKey = KeyFactory.getInstance("RSA")
.generatePublic(X509EncodedKeySpec(publicKeyBytes)) as RSAPublicKey
private fun readPublicPemFile(
publicKey: String,
): ByteArray = javaClass.classLoader
.getResource(publicKey)
?.readText()
?.let(::getPublicKeyByteArray)
?: throw KeyProviderException("Could not load public key")
private fun getPublicKeyByteArray(
keyText: String,
): ByteArray = keyText.replace(Regex("[\r\n]+"), "")
.removePrefix("-----BEGIN PUBLIC KEY-----")
.removeSuffix("-----END PUBLIC KEY-----")
.let { getDecoder().decode(it) }
}

View File

@@ -20,11 +20,6 @@
"type": "java.lang.String", "type": "java.lang.String",
"description": "Application build os version." "description": "Application build os version."
}, },
{
"name": "jwt.public-key",
"type": "java.lang.String",
"description": "Jwt public key file."
},
{ {
"name": "device-registry.url", "name": "device-registry.url",
"type": "java.lang.String", "type": "java.lang.String",

View File

@@ -0,0 +1,9 @@
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3ZdlbISX729m5Ur1pVhg
XIvazcgUt0T0G32ML0tfwQ4aWTfwPII0SQRThaN6eiiBMRa0V8JMih1LT8JmGgst
dEx2nhMbVs/Osu8MhmP86c+HB/jPa1+0IR1TZKXoZoF52D2ZtoVf+mOWggAcm1R+
V0Fj2cR/pgLkVt3GKUE2OokFC1iFUQFjThd1EzKcOv53TUek8FY8t66npQ4t3unD
bXZKoGXMuXCqZVykMbGTUQFRuT3NAOXRrJP+UDeY2uM2Yk98J+8FtLDYD6jpmyi0
ghv6k8pK1w1n5NI3atVv5ZMUeQZ36AXL8SZi1105mamhLVQ0e0JixoMOPh7ziFyv
uwIDAQAB
-----END PUBLIC KEY-----