Skip to content

Commit a6f0bc8

Browse files
committed
feat: add different kinds of jwt generations
1 parent d42f5b0 commit a6f0bc8

File tree

9 files changed

+67
-26
lines changed

9 files changed

+67
-26
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package online.danielstefani.paddy.jwt
2+
3+
import jakarta.ws.rs.*
4+
import jakarta.ws.rs.core.MediaType
5+
import online.danielstefani.paddy.jwt.dto.JwtRequestDto
6+
import online.danielstefani.paddy.jwt.dto.JwtResponseDto
7+
8+
@Path("/jwt")
9+
@Consumes(MediaType.APPLICATION_JSON)
10+
@Produces(MediaType.TEXT_PLAIN)
11+
class JwtController(
12+
private val jwtService: JwtService
13+
) {
14+
15+
/*
16+
Mint a new JWT with permissions as required by the internal caller.
17+
Reminder that this is supposed to be called from inside the VPC.
18+
*/
19+
@POST
20+
@Path("/")
21+
fun getJwt(dto: JwtRequestDto): JwtResponseDto {
22+
return jwtService.makeJwt(dto.subject, dto.jwtType)
23+
}
24+
25+
}

src/main/kotlin/online/danielstefani/paddy/security/JwtService.kt renamed to src/main/kotlin/online/danielstefani/paddy/jwt/JwtService.kt

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
package online.danielstefani.paddy.security
1+
package online.danielstefani.paddy.jwt
22

33
import io.quarkus.logging.Log
44
import io.vertx.core.json.JsonObject
55
import io.vertx.ext.auth.impl.jose.JWT
66
import jakarta.enterprise.context.ApplicationScoped
7+
import online.danielstefani.paddy.jwt.dto.JwtResponseDto
8+
import online.danielstefani.paddy.jwt.dto.JwtType
79
import java.nio.charset.StandardCharsets
810
import java.security.Signature
911
import java.security.interfaces.RSAPublicKey
@@ -15,9 +17,9 @@ class JwtService(
1517
private val keychainHolder: KeychainHolder
1618
) {
1719
fun makeJwt(
18-
sub: String,
19-
jwtLifetimeSeconds: Long = 31540000
20-
): String {
20+
subject: String,
21+
jwtType: JwtType
22+
): JwtResponseDto {
2123
val (privateKey, _) = keychainHolder.keypair()
2224

2325
val jwtHeader: String = Base64.getUrlEncoder().withoutPadding().encodeToString(
@@ -30,20 +32,20 @@ class JwtService(
3032

3133
val jwtPayloadTemplate = """
3234
{
33-
"sub": "$sub",
35+
"sub": "$subject",
3436
"iss": "https://danielstefani.online",
3537
"iat": %s,
3638
"exp": %s,
37-
"aud": "Paddy MQTT Broker Clients"
39+
"aud": "${jwtType.audience}"
3840
}
3941
4042
""".trimIndent().replace(" ", "").replace("\n", "")
4143

44+
val now = Instant.now().epochSecond
45+
val expiry = Instant.now().plusSeconds(jwtType.lifetime).epochSecond
4246
val jwtPayload: String = Base64.getUrlEncoder().withoutPadding().encodeToString(
4347
String.format(
44-
jwtPayloadTemplate,
45-
Instant.now().epochSecond,
46-
Instant.now().plusSeconds(jwtLifetimeSeconds).epochSecond
48+
jwtPayloadTemplate, now, expiry
4749
).replace(" ", "").replace("\n", "")
4850
.toByteArray(StandardCharsets.UTF_8)
4951
)
@@ -58,7 +60,7 @@ class JwtService(
5860
signature.sign()
5961
)
6062

61-
return "$jwtContent.$jwtSignature"
63+
return JwtResponseDto("$jwtContent.$jwtSignature", expiry)
6264
}
6365

6466
fun makeJwks(): String {

src/main/kotlin/online/danielstefani/paddy/security/KeychainHolder.kt renamed to src/main/kotlin/online/danielstefani/paddy/jwt/KeychainHolder.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package online.danielstefani.paddy.security
1+
package online.danielstefani.paddy.jwt
22

33
import jakarta.enterprise.context.ApplicationScoped
44
import online.danielstefani.paddy.configuration.JwksConfiguration
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package online.danielstefani.paddy.jwt.dto
2+
3+
data class JwtRequestDto(
4+
val subject: String,
5+
val jwtType: JwtType
6+
)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package online.danielstefani.paddy.jwt.dto
2+
3+
data class JwtResponseDto(
4+
val jwt: String,
5+
val absoluteExpiryUnixSeconds: Long
6+
)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package online.danielstefani.paddy.jwt.dto
2+
3+
enum class JwtType(
4+
val lifetime: Long,
5+
val audience: String
6+
) {
7+
ADMIN(31540000, "paddy~internal"),
8+
PAD(31540000, "paddy~pad"),
9+
USER(3600, "paddy~user")
10+
}

src/main/kotlin/online/danielstefani/paddy/security/http/HttpAuthenticationController.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import jakarta.ws.rs.Path
66
import jakarta.ws.rs.Produces
77
import jakarta.ws.rs.core.MediaType
88
import online.danielstefani.paddy.security.AbstractAuthorizationController
9-
import online.danielstefani.paddy.security.JwtService
9+
import online.danielstefani.paddy.jwt.JwtService
1010
import online.danielstefani.paddy.security.dto.AuthorizationRequestDto
1111
import online.danielstefani.paddy.security.dto.AuthorizationResultDto
1212
import org.jboss.resteasy.reactive.RestResponse
@@ -16,7 +16,8 @@ import java.time.Instant
1616
@Consumes(MediaType.APPLICATION_JSON)
1717
@Produces(MediaType.APPLICATION_JSON)
1818
class HttpAuthenticationController(
19-
private val jwtService: JwtService) : AbstractAuthorizationController() {
19+
private val jwtService: JwtService
20+
) : AbstractAuthorizationController() {
2021

2122
@POST
2223
@Path("/validate")

src/main/kotlin/online/danielstefani/paddy/security/mqtt/MqttAuthenticationController.kt

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import jakarta.ws.rs.GET
44
import jakarta.ws.rs.Path
55
import jakarta.ws.rs.Produces
66
import jakarta.ws.rs.core.MediaType
7-
import online.danielstefani.paddy.security.JwtService
7+
import online.danielstefani.paddy.jwt.JwtService
88

99

1010
@Path("/")
@@ -21,14 +21,4 @@ class MqttAuthenticationController(
2121
fun getJwks(): String {
2222
return jwtService.makeJwks()
2323
}
24-
25-
/*
26-
Mint a new JWT. Should be called only by the backend
27-
as these JWTs have permissions to connect to all topics.
28-
*/
29-
@GET
30-
@Path("/admin-jwt")
31-
fun getJwt(): String {
32-
return jwtService.makeJwt("paddy-backend")
33-
}
3424
}

src/main/kotlin/online/danielstefani/paddy/security/mqtt/MqttAuthorizationController.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import jakarta.ws.rs.Path
77
import jakarta.ws.rs.Produces
88
import jakarta.ws.rs.core.MediaType
99
import online.danielstefani.paddy.security.AbstractAuthorizationController
10-
import online.danielstefani.paddy.security.JwtService
10+
import online.danielstefani.paddy.jwt.JwtService
1111
import online.danielstefani.paddy.security.dto.AuthorizationRequestDto
1212
import online.danielstefani.paddy.security.dto.AuthorizationResultDto
1313
import org.jboss.resteasy.reactive.RestResponse
@@ -16,7 +16,8 @@ import org.jboss.resteasy.reactive.RestResponse
1616
@Consumes(MediaType.APPLICATION_JSON)
1717
@Produces(MediaType.APPLICATION_JSON)
1818
class MqttAuthorizationController(
19-
private val jwtService: JwtService) : AbstractAuthorizationController() {
19+
private val jwtService: JwtService
20+
) : AbstractAuthorizationController() {
2021

2122
@POST
2223
@Path("/verify")

0 commit comments

Comments
 (0)