fix: tolerate retried internal api nonce

This commit is contained in:
richjohnrambo
2026-08-28 10:43:01 +08:00
parent ba06ee3472
commit 4ea828fb09
2 changed files with 48 additions and 8 deletions
+22 -8
View File
@@ -185,7 +185,8 @@ func authorizeInternalAPI(wrt http.ResponseWriter, req *http.Request, capability
writeIMError(wrt, req, http.StatusUnauthorized, 30043, imReasonAuthRequired, "invalid internal signature", nil) writeIMError(wrt, req, http.StatusUnauthorized, 30043, imReasonAuthRequired, "invalid internal signature", nil)
return false return false
} }
if !claimInternalNonce(serviceName, nonce, time.Now().Add(window)) { requestClaim := internalRequestClaimValue(signingText)
if !claimInternalNonce(serviceName, nonce, requestClaim, time.Now().Add(window)) {
writeIMError(wrt, req, http.StatusUnauthorized, 30043, imReasonAuthRequired, "internal nonce replayed", nil) writeIMError(wrt, req, http.StatusUnauthorized, 30043, imReasonAuthRequired, "internal nonce replayed", nil)
return false return false
} }
@@ -572,23 +573,36 @@ func verifyInternalSignature(secret, signingText, signature string) bool {
return false return false
} }
func claimInternalNonce(serviceName, nonce string, expiresAt time.Time) bool { func claimInternalNonce(serviceName, nonce, requestClaim string, expiresAt time.Time) bool {
ttl := time.Until(expiresAt) ttl := time.Until(expiresAt)
if ok, enabled, err := store.ClaimShortState("internal_api_nonce", fmt.Sprintf("%s:%s", serviceName, nonce), ttl); enabled { nonceKey := fmt.Sprintf("%s:%s", serviceName, nonce)
if claimed, sameValue, enabled, err := store.ClaimShortStateValue("internal_api_nonce", nonceKey, requestClaim, ttl); enabled {
if err != nil { if err != nil {
logs.Warn.Println("internal api: failed to claim nonce in redis", err) logs.Warn.Println("internal api: failed to claim nonce in redis", err)
return false return false
} }
return ok return claimed || sameValue
} }
now := time.Now() now := time.Now()
internalAPI.nonces.Range(func(key, value any) bool { internalAPI.nonces.Range(func(key, value any) bool {
if expires, ok := value.(time.Time); ok && expires.Before(now) { if claim, ok := value.(internalNonceClaim); ok && claim.expiresAt.Before(now) {
internalAPI.nonces.Delete(key) internalAPI.nonces.Delete(key)
} }
return true return true
}) })
nonceKey := fmt.Sprintf("%s:%s", serviceName, nonce) if existing, loaded := internalAPI.nonces.LoadOrStore(nonceKey, internalNonceClaim{value: requestClaim, expiresAt: expiresAt}); loaded {
_, loaded := internalAPI.nonces.LoadOrStore(nonceKey, expiresAt) claim, ok := existing.(internalNonceClaim)
return !loaded return ok && claim.value == requestClaim
}
return true
}
type internalNonceClaim struct {
value string
expiresAt time.Time
}
func internalRequestClaimValue(signingText string) string {
sum := sha256.Sum256([]byte(signingText))
return hex.EncodeToString(sum[:])
} }
+26
View File
@@ -70,6 +70,7 @@ type groupMessageRuntime interface {
CachedSetContains(tenantID types.TenantID, namespace, id, member string) (bool, error) CachedSetContains(tenantID types.TenantID, namespace, id, member string) (bool, error)
AllowRate(tenantID types.TenantID, scope string, limit int, window time.Duration) (bool, int, time.Duration, error) AllowRate(tenantID types.TenantID, scope string, limit int, window time.Duration) (bool, int, time.Duration, error)
ClaimShortState(namespace, id string, ttl time.Duration) (bool, error) ClaimShortState(namespace, id string, ttl time.Duration) (bool, error)
ClaimShortStateValue(namespace, id, value string, ttl time.Duration) (bool, bool, error)
} }
var groupMsgRuntime groupMessageRuntime var groupMsgRuntime groupMessageRuntime
@@ -97,6 +98,14 @@ func ClaimShortState(namespace, id string, ttl time.Duration) (bool, bool, error
return ok, true, err return ok, true, err
} }
func ClaimShortStateValue(namespace, id, value string, ttl time.Duration) (bool, bool, bool, error) {
if groupMsgRuntime == nil || namespace == "" || id == "" || value == "" {
return false, false, false, nil
}
claimed, sameValue, err := groupMsgRuntime.ClaimShortStateValue(namespace, id, value, ttl)
return claimed, sameValue, true, err
}
func RefreshUserPresence(tenantID types.TenantID, uid types.Uid, sid, nodeID string) error { func RefreshUserPresence(tenantID types.TenantID, uid types.Uid, sid, nodeID string) error {
if groupMsgRuntime == nil || !tenantID.IsValid() || uid.IsZero() || sid == "" { if groupMsgRuntime == nil || !tenantID.IsValid() || uid.IsZero() || sid == "" {
return nil return nil
@@ -753,6 +762,23 @@ func (r *redisGroupMessageRuntime) ClaimShortState(namespace, id string, ttl tim
return r.client.SetNX(context.Background(), r.shortStateKey(namespace, id), "1", ttl).Result() return r.client.SetNX(context.Background(), r.shortStateKey(namespace, id), "1", ttl).Result()
} }
func (r *redisGroupMessageRuntime) ClaimShortStateValue(namespace, id, value string, ttl time.Duration) (bool, bool, error) {
if ttl <= 0 {
ttl = time.Duration(r.cfg.ShortTTL) * time.Second
}
key := r.shortStateKey(namespace, id)
ctx := context.Background()
claimed, err := r.client.SetNX(ctx, key, value, ttl).Result()
if err != nil || claimed {
return claimed, false, err
}
existing, err := r.client.Get(ctx, key).Result()
if err != nil {
return false, false, err
}
return false, existing == value, nil
}
func (r *redisGroupMessageRuntime) hotSeqIDs(tenantID types.TenantID, topic string, opts *types.QueryOpt, limit int) ([]int64, bool, error) { func (r *redisGroupMessageRuntime) hotSeqIDs(tenantID types.TenantID, topic string, opts *types.QueryOpt, limit int) ([]int64, bool, error) {
if opts != nil && len(opts.IdRanges) > 0 { if opts != nil && len(opts.IdRanges) > 0 {
seqs := expandRanges(opts.IdRanges, limit) seqs := expandRanges(opts.IdRanges, limit)