new-api/middleware/muse_internal_auth.go
zizi 3f24ba87ee Move Muse mirror-user provisioning into new-api control plane
Muse needs a trusted path to create and disable per-user long-lived relay tokens so billing, routing, and downgrade logic can stay inside new-api instead of leaking back into Muse.

This adds a small internal route group guarded by a shared secret, provisions a mirror user plus a dedicated long-lived token, and exposes disable/status/revoke operations for the control plane. The sync path stores the Muse user id in remark as a temporary lookup anchor until Muse persists the returned new-api ids.

Constraint: Must reuse new-api billing/token model behavior without adding schema changes in phase 1
Constraint: Must avoid CreateUser side effects such as signup quota grants for mirrored internal users
Rejected: Reusing existing self-service token controllers | they only manage the authenticated owner and cannot return control-plane ids/keys
Rejected: Keying mirror users by username/email alone | unstable and collision-prone for external identity sync
Confidence: medium
Scope-risk: moderate
Reversibility: clean
Directive: Treat remark-based muse_user_id lookup as a phase-1 bridge; prefer persisted new_api_user_id from Muse binding records for later operations
Tested: go test ./controller ./service ./middleware -run MuseInternal -count=1
Tested: go test ./controller ./service ./middleware -count=1
Not-tested: Full relay/data-plane integration against a live Muse control-plane caller
2026-04-17 17:14:41 +08:00

26 lines
575 B
Go

package middleware
import (
"crypto/subtle"
"net/http"
"github.com/QuantumNous/new-api/common"
"github.com/gin-gonic/gin"
)
func MuseInternalAuth() gin.HandlerFunc {
return func(c *gin.Context) {
secret := c.GetHeader("X-Muse-Service-Secret")
expected := common.GetEnvOrDefaultString("MUSE_INTERNAL_SECRET", "")
if expected == "" || subtle.ConstantTimeCompare([]byte(secret), []byte(expected)) != 1 {
c.JSON(http.StatusUnauthorized, gin.H{
"success": false,
"message": "invalid muse internal secret",
})
c.Abort()
return
}
c.Next()
}
}