feat: refuse instances Vantage HQ has locked under a dispute

This commit is contained in:
2026-09-10 13:24:47 +00:00
parent 8bd0d790dd
commit 913d3da386
6 changed files with 221 additions and 1 deletions
+34
View File
@@ -0,0 +1,34 @@
package auth
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
)
func TestRefuseLocked(t *testing.T) {
gin.SetMode(gin.TestMode)
prev := instanceLocked
t.Cleanup(func() { instanceLocked = prev })
instanceLocked = func(id string) bool { return id == "locked" }
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
if !refuseLocked(c, "locked") {
t.Fatal("a locked instance must be refused")
}
if w.Code != http.StatusUnauthorized {
t.Fatalf("status = %d, want 401", w.Code)
}
w = httptest.NewRecorder()
c, _ = gin.CreateTestContext(w)
if refuseLocked(c, "open") {
t.Fatal("an unlocked instance must pass")
}
if c.IsAborted() {
t.Fatal("an unlocked instance must not abort")
}
}
+21
View File
@@ -12,6 +12,20 @@ import (
const ctxSessionKey = "km_session"
// instanceLocked is a variable so tests can stub the Mongo-backed check.
var instanceLocked = services.InstanceLocked
// refuseLocked answers 401 for a session or token on an instance Vantage HQ has
// locked under a dispute. Same body as an expired session: a locked instance
// is not announced as locked to whoever holds a credential for it.
func refuseLocked(c *gin.Context, instanceID string) bool {
if !instanceLocked(instanceID) {
return false
}
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "session expired"})
return true
}
func GetSessionFromContext(c *gin.Context) *Session {
v, _ := c.Get(ctxSessionKey)
sess, _ := v.(*Session)
@@ -81,6 +95,13 @@ func Middleware() gin.HandlerFunc {
return
}
// Explicit, because the host guard below cannot do this: the resolver
// hides a locked instance, so its host resolves to nothing and that
// guard is skipped rather than tripped.
if refuseLocked(c, sess.InstanceID) {
return
}
c.Set(ctxSessionKey, sess)
// The host guard applies to both credential kinds. A token carries an