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)
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)
return false
}
@@ -572,23 +573,36 @@ func verifyInternalSignature(secret, signingText, signature string) bool {
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)
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 {
logs.Warn.Println("internal api: failed to claim nonce in redis", err)
return false
}
return ok
return claimed || sameValue
}
now := time.Now()
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)
}
return true
})
nonceKey := fmt.Sprintf("%s:%s", serviceName, nonce)
_, loaded := internalAPI.nonces.LoadOrStore(nonceKey, expiresAt)
return !loaded
if existing, loaded := internalAPI.nonces.LoadOrStore(nonceKey, internalNonceClaim{value: requestClaim, expiresAt: expiresAt}); loaded {
claim, ok := existing.(internalNonceClaim)
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)
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)
ClaimShortStateValue(namespace, id, value string, ttl time.Duration) (bool, bool, error)
}
var groupMsgRuntime groupMessageRuntime
@@ -97,6 +98,14 @@ func ClaimShortState(namespace, id string, ttl time.Duration) (bool, bool, error
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 {
if groupMsgRuntime == nil || !tenantID.IsValid() || uid.IsZero() || sid == "" {
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()
}
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) {
if opts != nil && len(opts.IdRanges) > 0 {
seqs := expandRanges(opts.IdRanges, limit)