Switch to jwks instead of custom /info

This commit is contained in:
Zoe Roux 2025-03-24 23:02:09 +01:00
parent 068b19c936
commit 6391a99bb9
No known key found for this signature in database
8 changed files with 62 additions and 47 deletions

View File

@ -1,20 +1,14 @@
import jwt from "@elysiajs/jwt";
import Elysia, { t } from "elysia";
import { createRemoteJWKSet } from "jose";
export let jwtSecret = process.env.JWT_SECRET!;
if (!jwtSecret) {
const auth = process.env.AUTH_SERVER ?? "http://auth:4568/auth";
try {
const ret = await fetch(`${auth}/info`);
const info = await ret.json();
jwtSecret = info.publicKey;
} catch (error) {
console.error(`Can't access auth server at ${auth}:\n${error}`);
}
}
const jwtSecret = process.env.JWT_SECRET;
const jwks = createRemoteJWKSet(
new URL(process.env.AUTH_SERVER ?? "http://auth:4568"),
);
export const auth = new Elysia({ name: "auth" })
.use(jwt({ secret: jwtSecret }))
.use(jwt({ secret: jwtSecret ?? jwks }))
.guard({
headers: t.Object({
authorization: t.String({ pattern: "^Bearer .+$" }),
@ -25,7 +19,7 @@ export const auth = new Elysia({ name: "auth" })
return {
beforeHandle: () => {},
resolve: async ({ headers: { authorization }, jwt }) => {
console.log(authorization.slice(7));
console.log(authorization?.slice(7));
const user = await jwt.verify(authorization?.slice(7));
console.log("macro", user);
return { user };

View File

@ -1,5 +1,4 @@
import { swagger } from "@elysiajs/swagger";
import { jwtSecret } from "./auth";
import { app } from "./base";
import { processImages } from "./controllers/seed/images";
import { migrate } from "./db";
@ -7,11 +6,6 @@ import { comment } from "./utils";
await migrate();
if (!jwtSecret) {
console.error("Missing jwt secret or auth server. exiting");
process.exit(1);
}
// run image processor task in background
processImages();

View File

@ -4,6 +4,9 @@
# http route prefix (will listen to $KEIBI_PREFIX/users for example)
KEIBI_PREFIX=""
# The url you can use to reach your kyoo instance. This is used during oidc to redirect users to your instance.
PUBLIC_URL=http://localhost:8901
# Database things
POSTGRES_USER=kyoo
POSTGRES_PASSWORD=password

View File

@ -17,13 +17,12 @@ type Configuration struct {
Prefix string
JwtPrivateKey *rsa.PrivateKey
JwtPublicKey *rsa.PublicKey
Issuer string
PublicUrl string
DefaultClaims jwt.MapClaims
ExpirationDelay time.Duration
}
var DefaultConfig = Configuration{
Issuer: "kyoo",
DefaultClaims: make(jwt.MapClaims),
ExpirationDelay: 30 * 24 * time.Hour,
}
@ -54,6 +53,7 @@ func LoadConfiguration(db *dbc.Queries) (*Configuration, error) {
}
}
ret.PublicUrl = os.Getenv("PUBLIC_URL")
ret.Prefix = os.Getenv("KEIBI_PREFIX")
if ret.JwtPrivateKey == nil {

View File

@ -16,7 +16,18 @@ require (
github.com/swaggo/swag v1.16.4
)
require github.com/otaxhu/type-mismatch-encoding v0.0.0-20241118152201-1861af90dd01 // indirect
require (
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect
github.com/goccy/go-json v0.10.3 // indirect
github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect
github.com/lestrrat-go/blackmagic v1.0.2 // indirect
github.com/lestrrat-go/httpcc v1.0.1 // indirect
github.com/lestrrat-go/iter v1.0.2 // indirect
github.com/lestrrat-go/jwx v1.2.30 // indirect
github.com/lestrrat-go/option v1.0.1 // indirect
github.com/otaxhu/type-mismatch-encoding v0.0.0-20241118152201-1861af90dd01 // indirect
github.com/pkg/errors v0.9.1 // indirect
)
require (
github.com/KyleBanks/depth v1.2.1 // indirect

View File

@ -9,6 +9,8 @@ github.com/alexedwards/argon2id v1.0.0/go.mod h1:tYKkqIjzXvZdzPvADMWOEZ+l6+BD6Ct
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
github.com/dhui/dktest v0.4.3 h1:wquqUxAFdcUgabAVLvSCOKOlag5cIZuaOjYIBOWdsR0=
github.com/dhui/dktest v0.4.3/go.mod h1:zNK8IwktWzQRm6I/l2Wjp7MakiyaFWv4G1hjmodmMTs=
github.com/dhui/dktest v0.4.4 h1:+I4s6JRE1yGuqflzwqG+aIaMdgXIorCf5P98JnaAWa8=
@ -54,6 +56,8 @@ github.com/go-playground/validator/v10 v10.23.0 h1:/PwmTwZhS0dPkav3cdK9kV1FsAmrL
github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-playground/validator/v10 v10.25.0 h1:5Dh7cjvzR7BRZadnsVOzPhWsrwUr0nmsZJxEAnFLNO8=
github.com/go-playground/validator/v10 v10.25.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
@ -99,6 +103,19 @@ github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A=
github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y=
github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k=
github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU=
github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E=
github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI=
github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4=
github.com/lestrrat-go/jwx v1.2.30 h1:VKIFrmjYn0z2J51iLPadqoHIVLzvWNa1kCsTqNDHYPA=
github.com/lestrrat-go/jwx v1.2.30/go.mod h1:vMxrwFhunGZ3qddmfmEm2+uced8MSI6QFWGTKygjSzQ=
github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
@ -134,7 +151,9 @@ github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/swaggo/echo-swagger v1.4.1 h1:Yf0uPaJWp1uRtDloZALyLnvdBeoEL5Kc7DtnjzO/TUk=

View File

@ -2,8 +2,6 @@ package main
import (
"context"
"crypto/x509"
"encoding/pem"
"fmt"
"maps"
"net/http"
@ -12,6 +10,7 @@ import (
"github.com/golang-jwt/jwt/v5"
"github.com/labstack/echo/v4"
"github.com/lestrrat-go/jwx/jwk"
)
type Jwt struct {
@ -19,11 +18,6 @@ type Jwt struct {
Token string `json:"token"`
}
type Info struct {
// The public key used to sign jwt tokens. It can be used by your services to check if the jwt is valid.
PublicKey string `json:"publicKey"`
}
// @Summary Get JWT
// @Description Convert a session token to a short lived JWT.
// @Tags jwt
@ -56,13 +50,13 @@ func (h *Handler) CreateJwt(c echo.Context) error {
claims := maps.Clone(session.User.Claims)
claims["sub"] = session.User.Id.String()
claims["sid"] = session.Id.String()
claims["iss"] = h.config.Issuer
claims["iss"] = h.config.PublicUrl
claims["iat"] = &jwt.NumericDate{
Time: time.Now().UTC(),
}
claims["exp"] = &jwt.NumericDate{
Time: time.Now().UTC().Add(time.Hour),
}
claims["iss"] = &jwt.NumericDate{
Time: time.Now().UTC(),
}
jwt := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
t, err := jwt.SignedString(h.config.JwtPrivateKey)
if err != nil {
@ -74,21 +68,21 @@ func (h *Handler) CreateJwt(c echo.Context) error {
})
}
// @Summary Info
// @Description Get info like the public key used to sign the jwts.
// @Summary Jwks
// @Description Get the jwks info, used to validate jwts.
// @Tags jwt
// @Produce json
// @Success 200 {object} Info
// @Router /info [get]
// @Success 200 {object} jwk.Key
// @Router /.well-known/jwks.json [get]
func (h *Handler) GetInfo(c echo.Context) error {
key := pem.EncodeToMemory(
&pem.Block{
Type: "RSA PUBLIC KEY",
Bytes: x509.MarshalPKCS1PublicKey(h.config.JwtPublicKey),
},
)
key, err := jwk.New(h.config.JwtPublicKey)
if err != nil {
return err
}
return c.JSON(200, Info{
PublicKey: string(key),
})
key.Set("use", "sig")
key.Set("key_ops", "verify")
set := jwk.NewSet()
set.Add(key)
return c.JSON(200, set)
}

View File

@ -184,7 +184,7 @@ func main() {
r.DELETE("/sessions/:id", h.Logout)
g.GET("/jwt", h.CreateJwt)
g.GET("/info", h.GetInfo)
e.GET("/.well-known/jwks.json", h.GetInfo)
g.GET("/swagger/*", echoSwagger.WrapHandler)