package identityruntime import ( "context" "errors" "time" "github.com/easyai/easyai-ai-gateway/apps/api/internal/identity" "github.com/easyai/easyai-ai-gateway/apps/api/internal/store" ) type tenantContextSyncRepository interface { DueOIDCTenantBindingSyncs(context.Context, string, string, int) ([]store.OIDCTenantBindingSyncTarget, error) ApplyOIDCTenantBindingSync(context.Context, string, identity.TenantContext, bool, time.Time) error RejectOIDCTenantBinding(context.Context, string, string, time.Time) error FailOIDCTenantBindingSync(context.Context, string, string, time.Time) error } type tenantContextSyncReader interface { Get(context.Context, string, string) (identity.TenantContext, bool, error) } func runTenantContextSynchronizer(ctx context.Context, repository tenantContextSyncRepository, reader tenantContextSyncReader, issuer, applicationID string) { ticker := time.NewTicker(15 * time.Second) defer ticker.Stop() for { select { case <-ctx.Done(): return case now := <-ticker.C: _ = synchronizeDueTenantContexts(ctx, repository, reader, issuer, applicationID, now.UTC()) } } } func synchronizeDueTenantContexts(ctx context.Context, repository tenantContextSyncRepository, reader tenantContextSyncReader, issuer, applicationID string, now time.Time) error { targets, err := repository.DueOIDCTenantBindingSyncs(ctx, issuer, applicationID, 50) if err != nil { return err } for _, target := range targets { tenant, unchanged, readErr := reader.Get(ctx, target.ExternalTenantID, target.ETag) switch { case readErr == nil && unchanged: err = repository.ApplyOIDCTenantBindingSync(ctx, target.ID, identity.TenantContext{}, true, now) case readErr == nil && tenant.Active(): err = repository.ApplyOIDCTenantBindingSync(ctx, target.ID, tenant, false, now) case readErr == nil: err = repository.RejectOIDCTenantBinding(ctx, target.ID, "tenant_inactive", now) case errors.Is(readErr, identity.ErrTenantContextNotFound): err = repository.RejectOIDCTenantBinding(ctx, target.ID, "tenant_not_found", now) default: err = repository.FailOIDCTenantBindingSync( ctx, target.ID, "tenant_context_unavailable", now.Add(tenantContextRetryDelay(target.FailureCount)), ) } if err != nil && !errors.Is(err, store.ErrOIDCTenantUnavailable) { return err } } return nil } func tenantContextRetryDelay(failureCount int) time.Duration { if failureCount < 0 { failureCount = 0 } delay := 30 * time.Second for index := 0; index < failureCount && delay < 5*time.Minute; index++ { delay *= 2 } if delay > 5*time.Minute { return 5 * time.Minute } return delay }