package identityruntime import ( "context" "errors" "sync" "sync/atomic" "time" "github.com/easyai/easyai-ai-gateway/apps/api/internal/auth" "github.com/easyai/easyai-ai-gateway/apps/api/internal/identity" "github.com/easyai/easyai-ai-gateway/apps/api/internal/oidcsession" "github.com/easyai/easyai-ai-gateway/apps/api/internal/securityevents" ) type Repository interface { IdentityConfigurationRevision(context.Context, string) (identity.Revision, error) ActiveIdentityConfigurationRevision(context.Context) (identity.Revision, error) MarkIdentityRevisionValidated(context.Context, string, int64, string, string) (identity.Revision, error) MarkIdentityRevisionFailed(context.Context, string, int64, string, string, string) (identity.Revision, error) ActivateIdentityRevision(context.Context, string, int64) (identity.Revision, bool, error) DisableActiveIdentityRevision(context.Context, int64) (identity.Revision, error) } type Builder interface { Build(context.Context, identity.Revision) (*Runtime, error) } type Runtime struct { Revision identity.Revision Verifier *auth.OIDCVerifier PublicClient *auth.OIDCPublicClient Sessions *oidcsession.Service SessionCipher *oidcsession.Cipher SecurityEvents *securityevents.ConnectionManager CookieSecure bool close func() } func (runtime *Runtime) Close() { if runtime != nil && runtime.close != nil { runtime.close() } } type Manager struct { repository Repository builder Builder operation sync.Mutex current atomic.Pointer[Runtime] } func NewManager(repository Repository, builder Builder) *Manager { return &Manager{repository: repository, builder: builder} } func (manager *Manager) Current() *Runtime { return manager.current.Load() } func (manager *Manager) LoadActive(ctx context.Context) error { manager.operation.Lock() defer manager.operation.Unlock() revision, err := manager.repository.ActiveIdentityConfigurationRevision(ctx) if errors.Is(err, identity.ErrRevisionNotFound) { return nil } if err != nil { return err } runtime, err := manager.builder.Build(ctx, revision) if err != nil { return err } runtime.Revision = revision manager.current.Store(runtime) return nil } func (manager *Manager) Validate(ctx context.Context, id string, expectedVersion int64, traceID, auditID string) (identity.Revision, error) { manager.operation.Lock() defer manager.operation.Unlock() revision, err := manager.repository.IdentityConfigurationRevision(ctx, id) if err != nil { return identity.Revision{}, err } if revision.Version != expectedVersion || revision.State != identity.RevisionDraft && revision.State != identity.RevisionSuperseded { return identity.Revision{}, identity.ErrRevisionConflict } candidate, buildErr := manager.builder.Build(ctx, revision) if buildErr != nil { _, _ = manager.repository.MarkIdentityRevisionFailed(ctx, id, expectedVersion, "validation_failed", traceID, auditID) return identity.Revision{}, buildErr } candidate.Close() return manager.repository.MarkIdentityRevisionValidated(ctx, id, expectedVersion, traceID, auditID) } func (manager *Manager) Activate(ctx context.Context, id string, expectedVersion int64) (identity.Revision, error) { manager.operation.Lock() defer manager.operation.Unlock() revision, err := manager.repository.IdentityConfigurationRevision(ctx, id) if err != nil { return identity.Revision{}, err } if revision.Version != expectedVersion || revision.State != identity.RevisionValidated { return identity.Revision{}, identity.ErrRevisionConflict } candidate, err := manager.builder.Build(ctx, revision) if err != nil { return identity.Revision{}, err } activated, _, err := manager.repository.ActivateIdentityRevision(ctx, id, expectedVersion) if err != nil { candidate.Close() return identity.Revision{}, err } candidate.Revision = activated old := manager.current.Swap(candidate) retireRuntime(old) return activated, nil } func (manager *Manager) Disable(ctx context.Context, expectedVersion int64) (identity.Revision, error) { manager.operation.Lock() defer manager.operation.Unlock() disabled, err := manager.repository.DisableActiveIdentityRevision(ctx, expectedVersion) if err != nil { return identity.Revision{}, err } old := manager.current.Swap(nil) retireRuntime(old) return disabled, nil } func (manager *Manager) PrepareSecurityEvents(ctx context.Context, revision identity.Revision, secret []byte) error { preparer, ok := manager.builder.(interface { PrepareSecurityEvents(context.Context, identity.Revision, []byte) error }) if !ok { return errors.New("security event runtime preparation is unavailable") } return preparer.PrepareSecurityEvents(ctx, revision, secret) } func retireRuntime(runtime *Runtime) { if runtime == nil || runtime.close == nil { return } time.AfterFunc(30*time.Second, runtime.Close) }