diff --git a/pkg/accessreview/source_service.go b/pkg/accessreview/source_service.go index 496e36eaf..e147d2312 100644 --- a/pkg/accessreview/source_service.go +++ b/pkg/accessreview/source_service.go @@ -184,6 +184,11 @@ func (s *Service) UpdateSource( } source.ConnectorID = *req.ConnectorID + + // A (re)linked connector may resolve to a different instance + // name; clear the synced flag so the source-name worker picks + // the row up and re-resolves it. + source.NameSyncedAt = nil } if req.CsvData != nil { @@ -444,6 +449,16 @@ func (s *Service) ConfigureAccessReviewSource( return fmt.Errorf("cannot update connector: %w", err) } + // The selected org changed, so the resolvable instance name may + // have too; clear the synced flag so the source-name worker + // re-resolves the display name. + source.NameSyncedAt = nil + source.UpdatedAt = time.Now() + + if err := source.Update(ctx, conn, scope); err != nil { + return fmt.Errorf("cannot reset access source name sync: %w", err) + } + return nil }, ) @@ -453,3 +468,22 @@ func (s *Service) ConfigureAccessReviewSource( return source, nil } + +// ResetSourceNameSyncForConnector clears the synced-name flag on every access +// source backed by connectorID so the source-name worker re-resolves the +// display name. Called after a connector is reconnected — the new grant may +// scope a different org/workspace, changing the resolvable name. +func (s *Service) ResetSourceNameSyncForConnector( + ctx context.Context, + scope coredata.Scoper, + connectorID gid.GID, +) error { + return s.pg.WithTx( + ctx, + func(ctx context.Context, conn pg.Tx) error { + sources := &coredata.AccessReviewSources{} + + return sources.ClearNameSyncedAtByConnectorID(ctx, conn, scope, connectorID) + }, + ) +} diff --git a/pkg/coredata/access_review_source.go b/pkg/coredata/access_review_source.go index f4cbec353..b3ec71d15 100644 --- a/pkg/coredata/access_review_source.go +++ b/pkg/coredata/access_review_source.go @@ -359,6 +359,43 @@ WHERE return count, nil } +// ClearNameSyncedAtByConnectorID resets name_synced_at to NULL for every +// access source backed by connectorID so the source-name worker re-resolves +// the display name. A reconnect (possibly with a new scope/org) or a manual +// org (re)configuration can change the resolvable instance name; without this +// a source that was terminal-marked keeps its generic name forever. It is a +// no-op when no source references the connector. +func (sources *AccessReviewSources) ClearNameSyncedAtByConnectorID( + ctx context.Context, + conn pg.Tx, + scope Scoper, + connectorID gid.GID, +) error { + q := ` +UPDATE access_review_sources +SET + name_synced_at = NULL, + updated_at = @updated_at +WHERE + %s + AND connector_id = @connector_id + AND name_synced_at IS NOT NULL +` + q = fmt.Sprintf(q, scope.SQLFragment()) + + args := pgx.StrictNamedArgs{ + "connector_id": connectorID, + "updated_at": time.Now(), + } + maps.Copy(args, scope.SQLArguments()) + + if _, err := conn.Exec(ctx, q, args); err != nil { + return fmt.Errorf("cannot clear access source name sync: %w", err) + } + + return nil +} + // ErrNoAccessReviewSourceNameSyncAvailable is returned when no access source // needs its name synced from its connector. var ErrNoAccessReviewSourceNameSyncAvailable = fmt.Errorf("no access source name sync available") diff --git a/pkg/server/api/console/v1/resolver.go b/pkg/server/api/console/v1/resolver.go index 750a7bc43..7896448d2 100644 --- a/pkg/server/api/console/v1/resolver.go +++ b/pkg/server/api/console/v1/resolver.go @@ -155,6 +155,7 @@ func NewMux( logger, baseURL, proboSvc, + accessReviewSvc, connectorRegistry, safeRedirect, ), @@ -174,6 +175,7 @@ func handleConnectorComplete( logger *log.Logger, baseURL *baseurl.BaseURL, proboSvc *probo.Service, + accessReviewSvc *accessreview.Service, connectorRegistry *connector.ConnectorRegistry, safeRedirect *saferedirect.SafeRedirect, ) http.HandlerFunc { @@ -309,6 +311,14 @@ func handleConnectorComplete( return } + + // The reconnect may carry a different scope/org, changing the + // resolvable instance name. Clear the synced-name flag so the + // source-name worker re-resolves it. Best-effort: a failure here + // must not fail the OAuth callback redirect. + if err := accessReviewSvc.ResetSourceNameSyncForConnector(r.Context(), scope, cnnctr.ID); err != nil { + logger.WarnCtx(r.Context(), "cannot reset access source name sync after reconnect", log.Error(err)) + } } else { createReq := probo.CreateConnectorRequest{ OrganizationID: organizationID,