Add Neon access review driver support

Register Neon as a connector provider and add a new access review
driver that fetches organization members from the Neon API with
cursor-based pagination.

Neon's OAuth is partner-gated, so the connector is API-key only
(Bearer, the default scheme). A personal or organization API key can
belong to several organizations; the operator supplies the ID of the
one to review. The members endpoint exposes per-user MFA state
(has_mfa) and deactivation, which map to the access entry MFA status
and active flag; the stable account UUID (user_id) is used as the
external ID over the membership ID.

Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
This commit is contained in:
Aurélien Sibiril
2026-06-10 00:49:57 +02:00
parent 7640376d32
commit ec858e58df
17 changed files with 763 additions and 1 deletions

View File

@@ -408,6 +408,60 @@ func (r *renderNameResolver) ResolveInstanceName(ctx context.Context) (string, e
return resp.Name, nil
}
// neonNameResolver resolves the Neon organization name.
type neonNameResolver struct {
httpClient *http.Client
organizationID string
}
func NewNeonNameResolver(httpClient *http.Client, organizationID string) NameResolver {
return &neonNameResolver{
httpClient: httpClient,
organizationID: organizationID,
}
}
func (r *neonNameResolver) ResolveInstanceName(ctx context.Context) (string, error) {
if r.organizationID == "" {
return "", nil
}
endpoint, err := url.JoinPath(neonAPIBaseURL, "organizations", url.PathEscape(r.organizationID))
if err != nil {
return "", fmt.Errorf("cannot build neon organization URL: %w", err)
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
if err != nil {
return "", fmt.Errorf("cannot create neon organization request: %w", err)
}
req.Header.Set("Accept", "application/json")
httpResp, err := r.httpClient.Do(req)
if err != nil {
return "", fmt.Errorf("cannot execute neon organization request: %w", err)
}
defer func() { _ = httpResp.Body.Close() }()
// Best-effort: a non-2xx (revoked key, deleted org, stale ID) must not
// make the source-name worker retry forever. Give up gracefully and keep
// the generic source name; a dead key surfaces on the next ListAccounts.
if httpResp.StatusCode < 200 || httpResp.StatusCode >= 300 {
return "", nil
}
var resp struct {
Name string `json:"name"`
}
if err := json.NewDecoder(httpResp.Body).Decode(&resp); err != nil {
return "", fmt.Errorf("cannot decode neon organization response: %w", err)
}
return resp.Name, nil
}
// hubspotNameResolver resolves the HubSpot account name.
type hubspotNameResolver struct {
httpClient *http.Client