security: require JWT aud, add key_hash migration column, remove stale gRPC comment

- checkAudience now rejects tokens without aud when BROKER_JWT_AUDIENCE is set
- credentials migration adds key_hash column (fixes SaveCredential on fresh PG)
- Remove stale "shared between REST and gRPC" comment from main.go
This commit is contained in:
2026-04-07 22:24:44 -07:00
parent 6ad0541cd1
commit 93d175600a
3 changed files with 7 additions and 11 deletions
+1 -1
View File
@@ -56,7 +56,7 @@ func main() {
fundingSvc := funding.New()
// SOR core components (shared between REST and gRPC)
// SOR core components
sor := router.New(registry)
twapScheduler := router.NewTWAPScheduler(registry, sor)
feed := marketdata.NewFeed()
+5 -10
View File
@@ -42,12 +42,8 @@ var untrustedHeaders = []string{
func Middleware(iamEndpoint string) func(http.Handler) http.Handler {
jwksURL := iamEndpoint + "/.well-known/jwks"
// Expected audience for the broker service. Defaults to the IAM endpoint
// (matching Casdoor's default aud behavior) unless overridden by env.
// Expected audience for the broker service. When set, JWT aud claim is required.
expectedAud := os.Getenv("BROKER_JWT_AUDIENCE")
if expectedAud == "" {
expectedAud = iamEndpoint
}
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -80,8 +76,8 @@ func Middleware(iamEndpoint string) func(http.Handler) http.Handler {
return
}
// Validate audience contains the expected service identifier.
if !checkAudience(claims, expectedAud) {
// Validate audience when explicitly configured.
if expectedAud != "" && !checkAudience(claims, expectedAud) {
writeErr(w, http.StatusUnauthorized, "invalid token audience")
return
}
@@ -260,9 +256,8 @@ func ClaimStr(claims map[string]interface{}, key string) string {
func checkAudience(claims map[string]interface{}, expected string) bool {
aud, ok := claims["aud"]
if !ok {
// No aud claim — accept for backward compatibility with tokens
// issued before aud enforcement was added.
return true
// Reject tokens without aud claim — defense in depth.
return false
}
switch v := aud.(type) {
case string:
+1
View File
@@ -147,6 +147,7 @@ var migrations = []migration{
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
key_prefix TEXT NOT NULL DEFAULT '',
key_hash TEXT NOT NULL DEFAULT '',
permissions JSONB DEFAULT '[]',
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
expires_at TEXT NOT NULL DEFAULT ''