Skip to content

feat(service): add oauth2_token_endpoint outbound auth provider#36

Open
dy-shimizu wants to merge 6 commits into
developfrom
feature/oauth2-token-endpoint-auth
Open

feat(service): add oauth2_token_endpoint outbound auth provider#36
dy-shimizu wants to merge 6 commits into
developfrom
feature/oauth2-token-endpoint-auth

Conversation

@dy-shimizu
Copy link
Copy Markdown
Collaborator

Summary

Adds a new outbound auth provider oauth2_token_endpoint for non-OIDC OAuth2 client_credentials flows. The driver is DataRudder/Delorean which exposes /api/v1/auth/login accepting client_id + client_secret in form body and returning {access_token, token_type, expires_in} — RFC 6749-shaped but without an OIDC discovery document.

This PR is prerequisite for the upcoming DataRudder provider (separate PR coming after this merges).

What's added

Layer File Change
Auth provider pkg/executors/http/auth/oauth2_token_endpoint.go NEW — provider impl + extra_params denylist + token_type allowlist + empty-token guard
Token fetch pkg/executors/http/auth/token_fetcher.go FetchOAuth2TokenEndpointToken + fetchNewOAuth2TokenEndpointToken (routes via existing newPinnedTokenRequest for safehttp.Validate pre-flight) + buildOAuth2TokenEndpointData
Type registry pkg/executors/http/auth/types.go TypeOAuth2TokenEndpoint const + OAuth2TokenEndpointConfig struct
Factory pkg/executors/http/auth/factory.go New case arm + parseOAuth2TokenEndpointConfig
Errors pkg/executors/http/auth/errors.go 8 typed sentinels
Model pkg/model/executor_configuration{,_input,_test}.go Allowlist + oneof= tag extensions
JSON Schema pkg/executors/http/http.go New conditional if/then config block
API docs api/{docs.go,swagger.json,swagger.yaml,openapi.yaml} Enum addition
Tests *_test.go (unit + integration) 16 unit subtests + 4 integration scenarios

Hardening

  • extra_params denylist — canonical fields (client_id, client_secret, scope, audience) cannot be overridden via map injection.
  • token_type allowlist — Bearer only (case-insensitive) or empty (defaults to Bearer); rejects MAC/DPoP/Basic with ErrOAuth2TokenEndpointUnsupportedTokenType. Test asserts no access_token leakage into any header on rejection.
  • Empty access_token guard — defends against misbehaving IdPs returning {"access_token":"","token_type":"Bearer",...}. Returns ErrOAuth2TokenEndpointEmptyAccessToken instead of emitting Authorization: Bearer .
  • Cache namespace — prefix oauth2te (distinct from OIDC cc/user).
  • SSRFsafehttp.Validate pre-flight + dial-time IP policy via safehttp.NewClient-wrapped transport (consumes PR feat(infrastructure): defense-in-depth SSRF protection for outbound HTTP #24's helper, no reinvention).

Test plan

  • make test-unit — PASS (includes 24 oauth2 unit tests)
  • go test -tags=unit ./... — PASS (covers all packages)
  • make test-integration — PASS (4 new TestHTTPProviderAuthOAuth2TokenEndpoint* scenarios + existing OIDC tests)
  • make test-e2e — PASS (TestFullE2E + TestWebhookE2E full lifecycle)
  • gofmt -l pkg/executors/http/auth/ — clean
  • go vet ./pkg/executors/http/auth/... — clean
  • golangci-lint run ./pkg/executors/http/auth/... — 0 issues
  • Real-world smoke test against https://inference.api-stg.delorean-ai.com via downstream DataRudder branch — overallStatus: passed, all 3 stages green. Re-smoke after codereview fixes also passed.

Notes for reviewers

  1. Struct tags on OAuth2TokenEndpointConfig intentionally omitted — the package does not invoke validator.Struct on this config. Validation runs via explicit if-checks in NewOAuth2TokenEndpointProvider. Docblock at types.go:100-106 documents the intent. (Sibling OIDC configs have decorative tags that also never run — that's pre-existing pattern.)

  2. fetchNewOAuth2TokenEndpointToken routes through newPinnedTokenRequest — matches the 3 OIDC fetch paths exactly (post-PR feat(infrastructure): defense-in-depth SSRF protection for outbound HTTP #24 safehttp migration). Pre-flight safehttp.Validate enforces SSRF policy without URL pinning, keeping TLS SNI correct.

  3. No grant_type set by default in the form body — intentional Delorean accommodation (their /api/v1/auth/login does not require it). Callers needing strict RFC 6749 compliance set grant_type=client_credentials via extra_params. Locked in by TestOAuth2TokenEndpointProvider_NoGrantTypeByDefault.

  4. API artifact regen via hand-patchmake generate-docs is broken at HEAD due to a pre-existing malformed @Param annotation in internal/adapters/http/in/webhook/handler.go:83. Hand-patched api/* with narrow enum-only diffs to avoid touching out-of-scope files. Reported to platform team separately.

Findings reported separately

During integration, several observations outside this PR's scope were communicated to @alexgarzao:

  • HTTPS DNS-only-SAN cert bug latent in Midaz/Tracer connectivity probers
  • docs/connectivity-prober-guide.md silent on TLS SNI / safehttp.Validate adoption
  • safehttp.Validate available since PR feat(infrastructure): defense-in-depth SSRF protection for outbound HTTP #24 but not adopted by Tracer/Midaz probers
  • Several parity issues in existing OIDC providers (http:// IssuerURL accepted; cache key omits Audience/ExtraParams; Basic auth not URL-encoded per RFC 6749 §2.3.1; token endpoint error body interpolated into error strings; empty access_token emits literal Authorization: Bearer ; etc.)
  • make generate-docs broken by webhook handler annotation
  • CI gap: make test-unit doesn't include -tags=unit tests, so datarudder/midaz/tracer unit tests don't run in _pre-validations

🤖 Generated with Claude Code

shimizu-lerian and others added 5 commits May 22, 2026 08:51
New outbound HTTP auth type for OAuth2-like flows whose token endpoints
don't publish OIDC discovery metadata. Reuses the existing TokenFetcher
cache infrastructure (getCachedToken, cacheToken, executeTokenRequest)
and TokenResponse shape — no parallel token-fetch code path is introduced.

Credentials can be sent in the form body (default) or as a Basic
Authorization header via credentials_location. grant_type is not sent
by default — callers that need one set it via extra_params.

Security hardenings included:

- extra_params rejects reserved keys (client_id, client_secret, scope,
  audience) to prevent silent override of validated canonical fields.
- token_type allowlist: only Bearer (case-insensitive) is accepted from
  the token endpoint response. Rejects Basic/MAC/DPoP to close a
  credential-disclosure vector where a malicious IdP could force a
  non-Bearer scheme on downstream requests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…list

Wires the new outbound auth type into the existing integration surface
so configs can flow through the API end-to-end:

- pkg/model/executor_configuration.go: adds oauth2_token_endpoint to the
  validTypes whitelist and to the ErrExecutorConfigAuthTypeInvalid message.
- pkg/model/executor_configuration_input.go: extends the validator
  'oneof' tag on ExecutorAuthenticationInput.Type.
- pkg/executors/http/http.go: adds oauth2_token_endpoint to the JSON
  Schema 'auth.type' enum and a matching allOf if/then branch with
  the config shape (token_url required, credentials_location enum,
  scopes/audience/extra_params).

Without these, the /v1/catalog/executors/http/validate endpoint and the
ExecutorConfiguration domain constructor would reject configs with the
new type even though the underlying provider exists.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds end-to-end tests that drive the new provider through the public
runner surface (httpExecutor.NewRunner().Execute) so the entire chain
— config map → factory → provider → token endpoint → downstream call —
is exercised on the same shape consumed by the production HTTP path.

Tests added:

- TestHTTPProviderAuthOAuth2TokenEndpoint: body credentials happy path
- TestHTTPProviderAuthOAuth2TokenEndpointBasicHeader: basic_header path
- TestHTTPProviderAuthOAuth2TokenEndpointWithExtraParams: verifies
  extra_params reach the token endpoint
- TestHTTPProviderAuthOAuth2TokenEndpointInvalidCredentials: 401 from
  token endpoint surfaces as ExecutionStatusError

The new mockTokenEndpointServer helper exposes ONLY POST /token (no
/.well-known/openid-configuration handler) and intentionally fails the
test if discovery is attempted — this proves the oauth2_token_endpoint
provider does not perform OIDC discovery.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Route fetchNewOAuth2TokenEndpointToken through newPinnedTokenRequest,
  matching the 3 OIDC fetch paths after PR #24 (safehttp).
- Drop misleading validate:"required,url" struct tags from
  OAuth2TokenEndpointConfig — the package does not invoke validator.Struct
  on this config, so the tags signaled validation that never runs.
  Replaced by docblock comment explaining intent.
- Apply gofmt alignment to factory_test.go map literal.
F1 — Regenerate Swagger/OpenAPI artifacts to include oauth2_token_endpoint
  in the auth-type enum. Hand-patched narrowly (api/docs.go, api/swagger.json,
  api/swagger.yaml, api/openapi.yaml) because `make generate-docs` is broken
  at HEAD by a pre-existing malformed @Param annotation in
  internal/adapters/http/in/webhook/handler.go:83 (out-of-scope; flagged for
  product owner as Finding A.19).

F2 — Add TestOAuth2TokenEndpointProvider_SSRFBlocksPrivateNetwork to verify
  the SSRF rejection invariant for the new provider. Uses
  safehttp.SetAllowPrivateForTest(false) + t.Cleanup to override the
  suite-wide allow-private setup from main_test.go without leaking state.

F3 — Stop mutating the caller's *OAuth2TokenEndpointConfig in the
  constructor. The empty-string default for CredentialsLocation is now
  applied at the request-build site (buildOAuth2TokenEndpointData treats
  "" and "body" identically). Test inverted to assert the caller's cfg
  pointer is preserved AND that body-credentials behavior is correct.

F4 — Add five missing test scenarios:
  - CacheKeyIsolation: two providers with different ClientID must hit
    the endpoint independently (no cross-config cache reuse).
  - TokenEndpointReturns401: assert error, no Authorization header,
    error message identifies the upstream status.
  - TokenEndpointReturnsMalformedJSON: decode failure propagates.
  - RejectsEmptyAccessToken: new sentinel
    ErrOAuth2TokenEndpointEmptyAccessToken + Apply-side guard rejects
    {"access_token":"",...} responses (defense-in-depth).
  - NoGrantTypeByDefault: locks in the documented Delorean accommodation
    (no grant_type form field unless caller supplies via extra_params).

F5 — Strengthen factory_test "full valid config" success assertion via
  a same-package testConfigSnapshot() helper that round-trips and asserts
  all 7 fields (token_url, client_id, client_secret, credentials_location,
  scopes, audience, extra_params). Catches mapToStruct deserialization
  regressions that the prior provider.Type()-only assertion would miss.

Validation: build, vet, gofmt, golangci-lint, unit, integration, e2e — all
clean. Gate 8 codereview (10 parallel reviewers) PASS in iteration 2 with
no NEW blocking findings.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 22, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: c2204ec6-b79c-422f-99bb-e53580339c38

📥 Commits

Reviewing files that changed from the base of the PR and between 8d17a05 and 4db4568.

📒 Files selected for processing (1)
  • pkg/executors/http/auth/types.go

Walkthrough

This PR adds OAuth2 token endpoint authentication to the HTTP executor. A new oauth2_token_endpoint type enables direct client-credentials authentication against configurable token endpoints (body or Basic header), supports scopes/audience and extra form parameters, and includes types, schema updates, model validation, token fetching, provider logic, and unit/integration tests.

Changes

OAuth2 Token Endpoint Authentication

Layer / File(s) Summary
Type definitions and error contracts
pkg/executors/http/auth/types.go, pkg/executors/http/auth/errors.go
TypeOAuth2TokenEndpoint constant and OAuth2TokenEndpointConfig struct define token URL, client credentials, credentials location, scopes, audience, and extra parameters; new validation/error sentinels added.
API documentation and JSON schema
api/docs.go, api/openapi.yaml, api/swagger.json, api/swagger.yaml, pkg/executors/http/http.go
OpenAPI/Swagger specs and HTTP executor JSON schema add oauth2_token_endpoint enum value and a conditional schema block requiring token_url, client_id, client_secret and allowing credentials_location, scopes, audience, and extra_params.
Model authentication validation
pkg/model/executor_configuration.go, pkg/model/executor_configuration_input.go, pkg/model/executor_configuration_test.go
Executor authentication model and input validation accept oauth2_token_endpoint; tests added to confirm acceptance and config preservation.
OAuth2 token endpoint token fetching
pkg/executors/http/auth/token_fetcher.go
FetchOAuth2TokenEndpointToken API and helpers construct SSRF-protected POST requests, optionally use Basic auth, build form payloads with credentials/scopes/audience/extra params, and cache token responses.
OAuth2TokenEndpointProvider implementation and unit tests
pkg/executors/http/auth/oauth2_token_endpoint.go, pkg/executors/http/auth/oauth2_token_endpoint_export_test.go, pkg/executors/http/auth/oauth2_token_endpoint_test.go
Provider implementation with constructor validation, reserved-extra-parameter rejection, default cache handling, and Apply enforcing Bearer-only tokens and setting downstream Authorization; extensive unit tests cover credential transport, parameter forwarding, caching/isolation, validation and response error cases, SSRF blocking, token-type handling, and grant_type omission.
Factory pattern integration
pkg/executors/http/auth/factory.go, pkg/executors/http/auth/factory_test.go
NewFromConfig recognizes TypeOAuth2TokenEndpoint, validates config via parseOAuth2TokenEndpointConfig, derives cache config, and instantiates the provider; factory tests verify success and failure scenarios.
End-to-end integration tests
tests/integration/http_provider_auth_test.go
Integration tests with a mock token endpoint validate body vs Basic header credentials, extra-parameter forwarding, and invalid credential error handling against the protected endpoint.

Comment @coderabbitai help to get the list of available commands and usage tips.

@lerian-studio
Copy link
Copy Markdown
Contributor

lerian-studio commented May 22, 2026

🔒 Security Scan Results — flowker

Trivy

Filesystem Scan

✅ No vulnerabilities or secrets found.

Docker Image Scan

✅ No vulnerabilities found.


Docker Hub Health Score Compliance

✅ Policies — 4/4 met

Policy Status
Default non-root user ✅ Passed
No fixable critical/high CVEs ✅ Passed
No high-profile vulnerabilities ✅ Passed
No AGPL v3 licenses ✅ Passed

Pre-release Version Check

✅ No unstable version pins found.


🔍 View full scan logs

@lerian-studio
Copy link
Copy Markdown
Contributor

lerian-studio commented May 22, 2026

📊 Unit Test Coverage Report: flowker

Metric Value
Overall Coverage 47.2% ⚠️ BELOW THRESHOLD
Threshold 85%

Coverage by Package

Package Coverage
github.com/LerianStudio/flowker/api 100.0%
github.com/LerianStudio/flowker/internal/adapters/http/in/audit 0.0%
github.com/LerianStudio/flowker/internal/adapters/http/in/catalog 60.9%
github.com/LerianStudio/flowker/internal/adapters/http/in/dashboard 91.5%
github.com/LerianStudio/flowker/internal/adapters/http/in/execution 81.0%
github.com/LerianStudio/flowker/internal/adapters/http/in/executor_configuration 70.8%
github.com/LerianStudio/flowker/internal/adapters/http/in/health 100.0%
github.com/LerianStudio/flowker/internal/adapters/http/in/middleware/testutil 0.0%
github.com/LerianStudio/flowker/internal/adapters/http/in/middleware 57.9%
github.com/LerianStudio/flowker/internal/adapters/http/in/provider_configuration 33.1%
github.com/LerianStudio/flowker/internal/adapters/http/in/readyz 81.3%
github.com/LerianStudio/flowker/internal/adapters/http/in/webhook 87.5%
github.com/LerianStudio/flowker/internal/adapters/http/in/workflow 69.7%
github.com/LerianStudio/flowker/internal/adapters/http/in 33.3%
github.com/LerianStudio/flowker/internal/adapters/mongodb/dashboard 43.0%
github.com/LerianStudio/flowker/internal/adapters/mongodb/execution 64.8%
github.com/LerianStudio/flowker/internal/adapters/mongodb/executor_configuration 36.6%
github.com/LerianStudio/flowker/internal/adapters/mongodb/provider_configuration 63.0%
github.com/LerianStudio/flowker/internal/adapters/mongodb/workflow 69.2%
github.com/LerianStudio/flowker/internal/adapters/postgresql/audit 41.9%
github.com/LerianStudio/flowker/internal/bootstrap 40.5%
github.com/LerianStudio/flowker/internal/services/command 42.1%
github.com/LerianStudio/flowker/internal/services/query 11.2%
github.com/LerianStudio/flowker/internal/services 3.2%
github.com/LerianStudio/flowker/internal/testutil 63.6%
github.com/LerianStudio/flowker/pkg/circuitbreaker 97.6%
github.com/LerianStudio/flowker/pkg/clock 0.0%
github.com/LerianStudio/flowker/pkg/condition 87.9%
github.com/LerianStudio/flowker/pkg/contextutil 100.0%
github.com/LerianStudio/flowker/pkg/executor/base 20.0%
github.com/LerianStudio/flowker/pkg/executor 39.5%
github.com/LerianStudio/flowker/pkg/executors/http/auth 72.1%
github.com/LerianStudio/flowker/pkg/executors/http 49.5%
github.com/LerianStudio/flowker/pkg/executors/midaz 80.8%
github.com/LerianStudio/flowker/pkg/executors/s3 0.0%
github.com/LerianStudio/flowker/pkg/executors/tracer 88.0%
github.com/LerianStudio/flowker/pkg/executors 57.1%
github.com/LerianStudio/flowker/pkg/model 60.7%
github.com/LerianStudio/flowker/pkg/net/http 91.3%
github.com/LerianStudio/flowker/pkg/pagination 0.0%
github.com/LerianStudio/flowker/pkg/safehttp 90.3%
github.com/LerianStudio/flowker/pkg/ssrfopt 100.0%
github.com/LerianStudio/flowker/pkg/templates/tracer_midaz 81.5%
github.com/LerianStudio/flowker/pkg/templates 0.0%
github.com/LerianStudio/flowker/pkg/transformation 67.2%
github.com/LerianStudio/flowker/pkg/triggers/webhook 0.0%
github.com/LerianStudio/flowker/pkg/triggers 0.0%
github.com/LerianStudio/flowker/pkg/webhook 100.0%
github.com/LerianStudio/flowker/pkg 90.3%

Generated by Go PR Analysis workflow

Comment thread pkg/executors/http/auth/types.go Fixed
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/executors/http/auth/factory.go`:
- Around line 181-213: Replace sentinel auth errors in
parseOAuth2TokenEndpointConfig with pkg/errors.go ValidationError values: if
mapToStruct returns an error, wrap it into a ValidationError (e.g.,
NewValidationError or constructing ValidationError) with field="" or more
specific context from the mapToStruct error; for empty
TokenURL/ClientID/ClientSecret return a ValidationError that identifies the
specific field ("token_url", "client_id", "client_secret") and an appropriate
message; for invalid CredentialsLocation return a ValidationError on field
"credentials_location"; and when reservedOAuth2ExtraParams[k] is true return a
ValidationError that references the offending extra param (field like
fmt.Sprintf("extra_params.%s", k)) and includes the reserved-param message; keep
the original checks (mapToStruct, reservedOAuth2ExtraParams loop) and replace
the ErrOAuth2TokenEndpoint* returns with constructed ValidationError instances
from pkg/errors.go so downstream code receives typed validation errors.

In `@pkg/executors/http/auth/oauth2_token_endpoint.go`:
- Around line 82-87: The OAuth2TokenEndpointProvider constructor is storing the
caller's mutable cfg pointer (config: cfg), which can lead to races or drifting
behavior if the caller later mutates Scopes or ExtraParams; update the
constructor that returns *OAuth2TokenEndpointProvider to deep-clone cfg (copy
primitive fields, duplicate slices like Scopes and maps like ExtraParams) and
store that copy in the provider's config field so Apply and cacheKey semantics
remain stable and thread-safe; ensure any code referencing config inside
OAuth2TokenEndpointProvider and its Apply method uses the cloned copy and not
the original cfg.
- Around line 79-80: The cache key built for the OAuth2 token endpoint is
missing auth-shaping fields and can cause token reuse collisions; update the
generateCacheKey invocation that assigns cacheKey (currently using "oauth2te",
cfg.TokenURL, cfg.ClientID, cfg.Scopes) to also include cfg.Audience,
cfg.CredentialsLocation, and cfg.ExtraParams (or their normalized/stringified
form) so the key uniquely represents request-shaping parameters; ensure any
map/struct in ExtraParams is deterministically serialized before passing to
generateCacheKey.

In `@pkg/executors/http/auth/token_fetcher.go`:
- Around line 390-416: FetchOAuth2TokenEndpointToken currently accepts a
*OAuth2TokenEndpointConfig but doesn't validate it, causing
fetchNewOAuth2TokenEndpointToken to dereference a nil cfg; add an early nil
check at the top of FetchOAuth2TokenEndpointToken that returns a typed
validation error (e.g., a wrapped or package-level error value indicating "nil
OAuth2TokenEndpointConfig") when cfg == nil, so callers get a clear, non-panic
failure instead of a runtime panic in fetchNewOAuth2TokenEndpointToken or
buildOAuth2TokenEndpointData.

In `@pkg/executors/http/http.go`:
- Around line 325-328: The schema currently allows forbidden keys in the
"extra_params" object despite the comment; update the JSON Schema for the
"extra_params" property so validation rejects keys "client_id", "client_secret",
"scope", and "audience" (e.g., add a "propertyNames" constraint with a "not" +
"enum" listing those reserved names or equivalent denylist). Modify the schema
for the "extra_params" property in pkg/executors/http/http.go (the object that
currently has "additionalProperties": { "type": "string" }) to include that
propertyNames denylist so invalid configs fail at schema validation time.

In `@tests/integration/http_provider_auth_test.go`:
- Around line 778-831: The test currently relies on capturedTenant being set by
the tokenSrv handler before the assertion after runner.Execute; to make the
intent explicit and avoid any future flakiness, change the test to synchronize
the token handler and the assertion: create a channel (or a sync.WaitGroup) and
have the tokenSrv handler send the tenant value (or signal done) when it
extracts r.Form.Get("tenant") (instead of only writing to capturedTenant), then
after runner.Execute but before the assert, receive from that channel (or wait)
to guarantee the handler ran; refer to capturedTenant, tokenSrv (the httptest
handler) and runner.Execute to locate where to add the channel send/receive (or
WaitGroup.Done/Wait).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: a59d529a-c995-45f1-a749-8b6951726747

📥 Commits

Reviewing files that changed from the base of the PR and between 45e8c4a and 8d17a05.

📒 Files selected for processing (17)
  • api/docs.go
  • api/openapi.yaml
  • api/swagger.json
  • api/swagger.yaml
  • pkg/executors/http/auth/errors.go
  • pkg/executors/http/auth/factory.go
  • pkg/executors/http/auth/factory_test.go
  • pkg/executors/http/auth/oauth2_token_endpoint.go
  • pkg/executors/http/auth/oauth2_token_endpoint_export_test.go
  • pkg/executors/http/auth/oauth2_token_endpoint_test.go
  • pkg/executors/http/auth/token_fetcher.go
  • pkg/executors/http/auth/types.go
  • pkg/executors/http/http.go
  • pkg/model/executor_configuration.go
  • pkg/model/executor_configuration_input.go
  • pkg/model/executor_configuration_test.go
  • tests/integration/http_provider_auth_test.go

Comment on lines +181 to +213
func parseOAuth2TokenEndpointConfig(data map[string]any) (*OAuth2TokenEndpointConfig, error) {
cfg := &OAuth2TokenEndpointConfig{}

if err := mapToStruct(data, cfg); err != nil {
return nil, fmt.Errorf("parse oauth2_token_endpoint config: %w", err)
}

if cfg.TokenURL == "" {
return nil, ErrOAuth2TokenEndpointURLRequired
}

if cfg.ClientID == "" {
return nil, ErrOAuth2TokenEndpointClientRequired
}

if cfg.ClientSecret == "" {
return nil, ErrOAuth2TokenEndpointSecretRequired
}

if cfg.CredentialsLocation != "" &&
cfg.CredentialsLocation != "body" &&
cfg.CredentialsLocation != "basic_header" {
return nil, ErrOAuth2TokenEndpointLocationInvalid
}

for k := range cfg.ExtraParams {
if reservedOAuth2ExtraParams[k] {
return nil, ErrOAuth2TokenEndpointReservedExtraParam
}
}

return cfg, nil
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift

Use pkg/errors.go validation types for parser failures.

parseOAuth2TokenEndpointConfig currently returns auth-local sentinel errors; this diverges from the repository’s typed error contract. Please map these validation failures to ValidationError (with field-level context) for consistent downstream handling.

As per coding guidelines, Use custom error types from pkg/errors.go (EntityNotFoundError, ValidationError) for error handling.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/executors/http/auth/factory.go` around lines 181 - 213, Replace sentinel
auth errors in parseOAuth2TokenEndpointConfig with pkg/errors.go ValidationError
values: if mapToStruct returns an error, wrap it into a ValidationError (e.g.,
NewValidationError or constructing ValidationError) with field="" or more
specific context from the mapToStruct error; for empty
TokenURL/ClientID/ClientSecret return a ValidationError that identifies the
specific field ("token_url", "client_id", "client_secret") and an appropriate
message; for invalid CredentialsLocation return a ValidationError on field
"credentials_location"; and when reservedOAuth2ExtraParams[k] is true return a
ValidationError that references the offending extra param (field like
fmt.Sprintf("extra_params.%s", k)) and includes the reserved-param message; keep
the original checks (mapToStruct, reservedOAuth2ExtraParams loop) and replace
the ErrOAuth2TokenEndpoint* returns with constructed ValidationError instances
from pkg/errors.go so downstream code receives typed validation errors.

Comment on lines +79 to +80
// Distinct prefix "oauth2te" to avoid collision with OIDC ("cc", "user").
cacheKey := generateCacheKey("oauth2te", cfg.TokenURL, cfg.ClientID, cfg.Scopes)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Cache key is missing auth-shaping fields, causing token reuse collisions.

At Line [80], the key excludes Audience, CredentialsLocation, and ExtraParams. Two providers with same token_url + client_id + scopes but different request parameters can incorrectly share one cached token.

Suggested fix
-	cacheKey := generateCacheKey("oauth2te", cfg.TokenURL, cfg.ClientID, cfg.Scopes)
+	cacheKey := generateCacheKey(
+		"oauth2te",
+		cfg.TokenURL,
+		cfg.ClientID,
+		cfg.CredentialsLocation,
+		cfg.Scopes,
+		cfg.Audience,
+		cfg.ExtraParams,
+	)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/executors/http/auth/oauth2_token_endpoint.go` around lines 79 - 80, The
cache key built for the OAuth2 token endpoint is missing auth-shaping fields and
can cause token reuse collisions; update the generateCacheKey invocation that
assigns cacheKey (currently using "oauth2te", cfg.TokenURL, cfg.ClientID,
cfg.Scopes) to also include cfg.Audience, cfg.CredentialsLocation, and
cfg.ExtraParams (or their normalized/stringified form) so the key uniquely
represents request-shaping parameters; ensure any map/struct in ExtraParams is
deterministically serialized before passing to generateCacheKey.

Comment on lines +82 to +87
return &OAuth2TokenEndpointProvider{
config: cfg,
cacheConfig: cacheCfg,
tokenFetcher: NewTokenFetcher(httpClient, nil),
cacheKey: cacheKey,
}, nil
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Provider keeps a mutable caller config pointer; clone it to avoid races and drift.

At Lines [82]-[87], config: cfg stores external mutable state. If the caller mutates Scopes/ExtraParams after construction, Apply behavior and cache semantics can change unexpectedly, and concurrent map access can race.

Suggested fix
+func cloneOAuth2TokenEndpointConfig(in *OAuth2TokenEndpointConfig) *OAuth2TokenEndpointConfig {
+	if in == nil {
+		return nil
+	}
+	out := *in
+	if in.Scopes != nil {
+		out.Scopes = append([]string(nil), in.Scopes...)
+	}
+	if in.ExtraParams != nil {
+		out.ExtraParams = make(map[string]string, len(in.ExtraParams))
+		for k, v := range in.ExtraParams {
+			out.ExtraParams[k] = v
+		}
+	}
+	return &out
+}
+
 func NewOAuth2TokenEndpointProvider(cfg *OAuth2TokenEndpointConfig, cacheCfg *CacheCfg, httpClient *http.Client) (*OAuth2TokenEndpointProvider, error) {
@@
 	return &OAuth2TokenEndpointProvider{
-		config:       cfg,
+		config:       cloneOAuth2TokenEndpointConfig(cfg),
 		cacheConfig:  cacheCfg,
 		tokenFetcher: NewTokenFetcher(httpClient, nil),
 		cacheKey:     cacheKey,
 	}, nil
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return &OAuth2TokenEndpointProvider{
config: cfg,
cacheConfig: cacheCfg,
tokenFetcher: NewTokenFetcher(httpClient, nil),
cacheKey: cacheKey,
}, nil
func cloneOAuth2TokenEndpointConfig(in *OAuth2TokenEndpointConfig) *OAuth2TokenEndpointConfig {
if in == nil {
return nil
}
out := *in
if in.Scopes != nil {
out.Scopes = append([]string(nil), in.Scopes...)
}
if in.ExtraParams != nil {
out.ExtraParams = make(map[string]string, len(in.ExtraParams))
for k, v := range in.ExtraParams {
out.ExtraParams[k] = v
}
}
return &out
}
func NewOAuth2TokenEndpointProvider(cfg *OAuth2TokenEndpointConfig, cacheCfg *CacheCfg, httpClient *http.Client) (*OAuth2TokenEndpointProvider, error) {
// ... function body ...
return &OAuth2TokenEndpointProvider{
config: cloneOAuth2TokenEndpointConfig(cfg),
cacheConfig: cacheCfg,
tokenFetcher: NewTokenFetcher(httpClient, nil),
cacheKey: cacheKey,
}, nil
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/executors/http/auth/oauth2_token_endpoint.go` around lines 82 - 87, The
OAuth2TokenEndpointProvider constructor is storing the caller's mutable cfg
pointer (config: cfg), which can lead to races or drifting behavior if the
caller later mutates Scopes or ExtraParams; update the constructor that returns
*OAuth2TokenEndpointProvider to deep-clone cfg (copy primitive fields, duplicate
slices like Scopes and maps like ExtraParams) and store that copy in the
provider's config field so Apply and cacheKey semantics remain stable and
thread-safe; ensure any code referencing config inside
OAuth2TokenEndpointProvider and its Apply method uses the cloned copy and not
the original cfg.

Comment on lines +390 to +416
func (f *TokenFetcher) FetchOAuth2TokenEndpointToken(
ctx context.Context,
cfg *OAuth2TokenEndpointConfig,
cacheKey string,
cacheConfig *CacheCfg,
) (*TokenResponse, error) {
if token := f.getCachedToken(cacheKey, cacheConfig); token != nil {
return token, nil
}

token, err := f.fetchNewOAuth2TokenEndpointToken(ctx, cfg)
if err != nil {
return nil, err
}

f.cacheToken(cacheKey, token, cacheConfig)

return token, nil
}

func (f *TokenFetcher) fetchNewOAuth2TokenEndpointToken(ctx context.Context, cfg *OAuth2TokenEndpointConfig) (*TokenResponse, error) {
data := f.buildOAuth2TokenEndpointData(cfg)

req, err := newPinnedTokenRequest(ctx, cfg.TokenURL, data.Encode())
if err != nil {
return nil, err
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add a nil-config guard to prevent panic in the public fetch API.

FetchOAuth2TokenEndpointToken accepts a pointer but does not validate it. If cfg == nil, dereferences in fetchNewOAuth2TokenEndpointToken will panic (for example, Line [413]). Please fail fast with a typed validation error.

Suggested fix
 func (f *TokenFetcher) FetchOAuth2TokenEndpointToken(
 	ctx context.Context,
 	cfg *OAuth2TokenEndpointConfig,
 	cacheKey string,
 	cacheConfig *CacheCfg,
 ) (*TokenResponse, error) {
+	if cfg == nil {
+		return nil, ErrOAuth2TokenEndpointConfigRequired
+	}
+
 	if token := f.getCachedToken(cacheKey, cacheConfig); token != nil {
 		return token, nil
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/executors/http/auth/token_fetcher.go` around lines 390 - 416,
FetchOAuth2TokenEndpointToken currently accepts a *OAuth2TokenEndpointConfig but
doesn't validate it, causing fetchNewOAuth2TokenEndpointToken to dereference a
nil cfg; add an early nil check at the top of FetchOAuth2TokenEndpointToken that
returns a typed validation error (e.g., a wrapped or package-level error value
indicating "nil OAuth2TokenEndpointConfig") when cfg == nil, so callers get a
clear, non-panic failure instead of a runtime panic in
fetchNewOAuth2TokenEndpointToken or buildOAuth2TokenEndpointData.

Comment on lines +325 to +328
"scopes": { "type": "array", "items": { "type": "string" }, "description": "OAuth2 scopes to request" },
"audience": { "type": "string", "description": "Optional audience parameter" },
"extra_params": { "type": "object", "additionalProperties": { "type": "string" }, "description": "Extra form parameters for token request (must not include client_id, client_secret, scope, audience)" }
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Enforce the reserved extra_params key denylist in schema.

Line 327 documents forbidden keys, but the schema still accepts them. Add a structural constraint so invalid configs fail at schema validation time.

Suggested diff
-                  "extra_params": { "type": "object", "additionalProperties": { "type": "string" }, "description": "Extra form parameters for token request (must not include client_id, client_secret, scope, audience)" }
+                  "extra_params": {
+                    "type": "object",
+                    "additionalProperties": { "type": "string" },
+                    "propertyNames": {
+                      "not": { "enum": ["client_id", "client_secret", "scope", "audience"] }
+                    },
+                    "description": "Extra form parameters for token request (must not include client_id, client_secret, scope, audience)"
+                  }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/executors/http/http.go` around lines 325 - 328, The schema currently
allows forbidden keys in the "extra_params" object despite the comment; update
the JSON Schema for the "extra_params" property so validation rejects keys
"client_id", "client_secret", "scope", and "audience" (e.g., add a
"propertyNames" constraint with a "not" + "enum" listing those reserved names or
equivalent denylist). Modify the schema for the "extra_params" property in
pkg/executors/http/http.go (the object that currently has
"additionalProperties": { "type": "string" }) to include that propertyNames
denylist so invalid configs fail at schema validation time.

Comment on lines +778 to +831
var capturedTenant string

tokenSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
http.Error(w, "invalid form", http.StatusBadRequest)
return
}

capturedTenant = r.Form.Get("tenant")

clientID := r.Form.Get("client_id")
clientSecret := r.Form.Get("client_secret")

if clientID != "test-client" || clientSecret != "test-secret" {
w.WriteHeader(http.StatusUnauthorized)
return
}

w.Header().Set("Content-Type", "application/json")
writeJSON(t, w, map[string]any{
"access_token": "mock-access-token-client-credentials",
"token_type": "Bearer",
"expires_in": 3600,
})
}))
defer tokenSrv.Close()

target := mockTargetServer(t)
defer target.Close()

runner := httpExecutor.NewRunner()

input := executor.ExecutionInput{
Config: map[string]any{
"method": "GET",
"url": target.URL + "/protected",
"auth": map[string]any{
"type": "oauth2_token_endpoint",
"config": map[string]any{
"token_url": tokenSrv.URL + "/token",
"client_id": "test-client",
"client_secret": "test-secret",
"extra_params": map[string]any{"tenant": "acme"},
},
},
},
HTTPClient: &http.Client{Timeout: 10 * time.Second},
}

result, err := runner.Execute(context.Background(), input)
require.NoError(t, err)
assert.Equal(t, executor.ExecutionStatusSuccess, result.Status)
assert.Equal(t, "acme", capturedTenant, "extra_params must reach the token endpoint")
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the test file and inspect the relevant section
ls -la tests/integration/http_provider_auth_test.go

# Print the snippet region with line numbers
sed -n '740,860p' tests/integration/http_provider_auth_test.go | nl -ba

# Find all occurrences of capturedTenant in the file
rg -n "capturedTenant" tests/integration/http_provider_auth_test.go

# Also locate the exact test function name for context (if present)
rg -n "TestHTTPProviderAuthOAuth2TokenEndpointWithExtraParams" -S tests/integration/http_provider_auth_test.go

# Search for other similar patterns in the integration tests to compare style
rg -n "ParseForm\(\)|tenantCh|extra_params must reach the token endpoint" tests/integration/http_provider_auth_test.go

Repository: LerianStudio/flowker

Length of output: 220


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="tests/integration/http_provider_auth_test.go"
echo "== File =="; ls -la "$FILE"

echo "== Captured tenant occurrences ==";
rg -n "capturedTenant" "$FILE"

echo "== Context around the snippet (approx lines 760-860) ==";
sed -n '760,860p' "$FILE" | cat -n

echo "== Test function signature (exact) ==";
rg -n "func TestHTTPProviderAuthOAuth2TokenEndpointWithExtraParams" "$FILE"

Repository: LerianStudio/flowker

Length of output: 4136


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Locate httpExecutor runner implementation
rg -n "package httpExecutor|NewRunner\\(" -S . -g'*.go'
rg -n "type Runner|func \\(.*\\) Execute\\(" -S . -g'*.go' | head -n 50

# 2) Narrow search to the http executor package by using the identifier seen in the test
rg -n "httpExecutor\\.NewRunner" -S . -g'*.go'
fd -a -t f 'http*executor*' . 2>/dev/null || true
fd -t f 'http_executor*' . 2>/dev/null || true
fd -t f '*executor*' . | rg -n "http" | head -n 50 || true

# 3) Once files are found, print the Execute method body (first ~120 lines around it)
# (We rely on rg output; if it fails, the next block will still run useful searches.)
echo "---- End of searches ----"

# 4) Attempt race test for the specific test (may fail if Go toolchain/deps aren't available)
go test ./... -run TestHTTPProviderAuthOAuth2TokenEndpointWithExtraParams -race -count=1 2>&1 | head -n 200 || true

Repository: LerianStudio/flowker

Length of output: 12988


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Print runner.go with line numbers (bounded)
sed -n '1,220p' pkg/executors/http/runner.go | cat -n

echo "----"
# Print http.go (maybe contains auth wiring) with line numbers (bounded)
sed -n '1,220p' pkg/executors/http/http.go | cat -n

echo "----"
# Search for goroutines in the http executor package
rg -n "^\s*go\s" pkg/executors/http -S -g'*.go' || true
rg -n "goroutine" pkg/executors/http -S -g'*.go' || true

echo "----"
# Search for oauth2_token_endpoint handling
rg -n "oauth2_token_endpoint|token_endpoint" -S pkg/executors pkg | head -n 50

echo "----"
# Search for "token_url" usage in code
rg -n "token_url" -S pkg/executors pkg | head -n 50

Repository: LerianStudio/flowker

Length of output: 27828


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Look for any goroutines in http executor package
rg -n "^\s*go\s" pkg/executors/http -S -g'*.go' || true
rg -n "goroutine" pkg/executors/http -S -g'*.go' || true

# Inspect applyAuth tail in runner.go (for any async patterns)
sed -n '200,320p' pkg/executors/http/runner.go | cat -n

# Find oauth2_token_endpoint implementation files
fd -t f 'oauth2*' pkg/executors/http/auth 2>/dev/null || true
fd -t f '*token*endpoint*' pkg/executors/http/auth 2>/dev/null || true
ls -ლა pkg/executors/http/auth

# Print oauth2 token endpoint files (bounded)
for f in $(ls pkg/executors/http/auth | rg -n 'oauth2|token_endpoint' | cut -d: -f1 || true); do
  echo "---- $f ----"
  sed -n '1,260p' "pkg/executors/http/auth/$f" | cat -n
done

# Also search within auth package for Apply method and goroutines
rg -n "func \\(.*\\) Apply\\(" pkg/executors/http/auth -S -g'*.go'
rg -n "oauth2_token_endpoint" pkg/executors/http/auth -S -g'*.go'

Repository: LerianStudio/flowker

Length of output: 4112


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show oauth2_token_endpoint implementation (bounded)
sed -n '1,260p' pkg/executors/http/auth/oauth2_token_endpoint.go | cat -n

# Search inside it for any async behavior / goroutines
rg -n "^\s*go\s|goroutine|WaitGroup|chan\s*<-" pkg/executors/http/auth/oauth2_token_endpoint.go -S || true
rg -n "client\\.Do\\(|http\\.NewRequest|Do\\(" pkg/executors/http/auth/oauth2_token_endpoint.go -S || true

# Also inspect related auth types for Apply implementation if Apply is in another file
rg -n "func \\(.*\\) Apply\\(" pkg/executors/http/auth -S -g'*.go'

# Finally, show the Apply() method body for oauth2 token endpoint (narrow by grep around its function)
rg -n "Apply\\(" pkg/executors/http/auth/oauth2_token_endpoint.go -S || true

Repository: LerianStudio/flowker

Length of output: 6109


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find token fetcher implementation
rg -n "type TokenFetcher|NewTokenFetcher|FetchOAuth2TokenEndpointToken" pkg/executors/http/auth -S -g'*.go'

# Show the relevant file(s) around TokenFetcher and FetchOAuth2TokenEndpointToken (bounded)
FILES=$(rg -l "FetchOAuth2TokenEndpointToken" pkg/executors/http/auth -S -g'*.go')
echo "$FILES"

for f in $FILES; do
  echo "---- $f ----"
  sed -n '1,260p' "$f" | cat -n
  rg -n "^\s*go\s|goroutine|WaitGroup|chan\s*<-|sync\\.|sync\\.Mutex|Mutex|RWMutex" "$f" -S -g'*.go' || true
done

# Search for any goroutines/channels anywhere in oauth2 token endpoint codepath
rg -n "^\s*go\s|goroutine|WaitGroup|chan\s*<-" pkg/executors/http/auth -S -g'*.go' || true

Repository: LerianStudio/flowker

Length of output: 15001


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="pkg/executors/http/auth/token_fetcher.go"

# Goroutines in token_fetcher
rg -n "^\s*go\s|goroutine|WaitGroup|chan\s*<-" "$FILE" -S || true

# Show around FetchOAuth2TokenEndpointToken and helpers
sed -n '340,520p' "$FILE" | cat -n

# Show executeTokenRequest to see sync behavior
rg -n "func \\(f \\*TokenFetcher\\) executeTokenRequest" "$FILE"
sed -n '520,700p' "$FILE" | cat -n

# Search for any refresh/loop that might call token endpoint multiple times
rg -n "FetchOAuth2TokenEndpointToken|refresh|retry" "$FILE" -S

Repository: LerianStudio/flowker

Length of output: 5356


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="pkg/executors/http/auth/token_fetcher.go"

rg -n "^\s*go\s|goroutine|WaitGroup|chan\s*<-" "$FILE" -S || true

# Show refreshToken implementation (bounded)
rg -n "func \\(f \\*TokenFetcher\\) refreshToken\\(" "$FILE"
sed -n '260,380p' "$FILE" | cat -n

Repository: LerianStudio/flowker

Length of output: 5438


Rephrase tenant-capture guidance: race is unlikely given synchronous token fetch.

capturedTenant is written by the token server handler, but the HTTP executor path is synchronous (http.Client.Do + io.ReadAll completes before runner.Execute returns), so the assertion runs after the token response has been fully processed—this makes an actual race/flaky behavior unlikely. Synchronizing via the proposed channel/mutex is still a good way to make intent explicit (especially if this test is ever refactored to issue the token request asynchronously).

Proposed fix
 func TestHTTPProviderAuthOAuth2TokenEndpointWithExtraParams(t *testing.T) {
-	var capturedTenant string
+	tenantCh := make(chan string, 1)
@@
-		capturedTenant = r.Form.Get("tenant")
+		tenantCh <- r.Form.Get("tenant")
@@
-	assert.Equal(t, "acme", capturedTenant, "extra_params must reach the token endpoint")
+	select {
+	case tenant := <-tenantCh:
+		assert.Equal(t, "acme", tenant, "extra_params must reach the token endpoint")
+	case <-time.After(2 * time.Second):
+		t.Fatal("token endpoint was not called")
+	}
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/integration/http_provider_auth_test.go` around lines 778 - 831, The
test currently relies on capturedTenant being set by the tokenSrv handler before
the assertion after runner.Execute; to make the intent explicit and avoid any
future flakiness, change the test to synchronize the token handler and the
assertion: create a channel (or a sync.WaitGroup) and have the tokenSrv handler
send the tenant value (or signal done) when it extracts r.Form.Get("tenant")
(instead of only writing to capturedTenant), then after runner.Execute but
before the assert, receive from that channel (or wait) to guarantee the handler
ran; refer to capturedTenant, tokenSrv (the httptest handler) and runner.Execute
to locate where to add the channel send/receive (or WaitGroup.Done/Wait).

…Endpoint

Mirrors the directive already applied to TypeOIDCClientCredentials at line 32.
The Type constant value is an auth-flow identifier (string discriminator used
by the factory dispatch), not a credential value — same false-positive shape
gosec G101 flags on both.

Unblocks the CI required check on PR #36.
@dy-shimizu
Copy link
Copy Markdown
Collaborator Author

Thanks @coderabbitai for the thorough review! 🙏

After verifying each finding against the existing OIDC providers (oidc_providers.go / factory.go / token_fetcher.go / http.go), 6 of the 7 actionable items are patterns that already exist in the OIDC code paths — not regressions introduced by this PR. As an external collaborator, I'm scoping this PR to the new auth provider and documenting parity concerns for platform-wide consideration (already shared with @alexgarzao via DM).

The only item unique to this PR (gosec G101) is fixed in 4db4568 — added the // #nosec G101 directive that already exists on TypeOIDCClientCredentials at pkg/executors/http/auth/types.go:32.

Per-item analysis

# Comment Existing OIDC equivalent Decision
factory.go ValidationError types parseOIDCClientCredentialsConfig (factory.go:133-153) + parseOIDCUserConfig (factory.go:155-179) return identical shared-sentinel pkg.ValidationError instances (ErrOIDCClientCredentialsIssuerRequired, etc.). The new parseOAuth2TokenEndpointConfig follows the same shape. Parity — flagged for product owner
Store mutable cfg pointer NewOIDCClientCredentialsProvider (oidc_providers.go:67-72) and NewOIDCUserProvider (oidc_providers.go:158-163) both do config: cfg without deep-cloning. Parity — flagged for product owner
Cache key omits Audience/CredentialsLocation/ExtraParams OIDC ClientCredentials: generateCacheKey(\"cc\", cfg.IssuerURL, cfg.ClientID, cfg.Scopes) (oidc_providers.go:65) — also omits Audience + ExtraParams. OIDC User similarly at :156. Parity — flagged for product owner
FetchOAuth2TokenEndpointToken nil cfg guard FetchClientCredentialsToken (token_fetcher.go:73) and FetchPasswordToken (:170) also have no nil-cfg guard. Defense lives at constructor boundary (which this PR also has at oauth2_token_endpoint.go:38). Parity — flagged for product owner
JSON Schema extra_params denylist OIDC schemas at http.go:282 (oidc_cc) and :305 (oidc_user) use the same additionalProperties: { type: string } without propertyNames.not.enum. The Go-side denylist in oauth2_token_endpoint.go:60-64 matches the security posture of OIDC paths. Parity — flagged for product owner
Test sync via channel/WaitGroup The capturedX = r.Form.Get(...) + post-runner.Execute assertion pattern is used elsewhere in http_provider_auth_test.go. runner.Execute is synchronous (waits for the HTTP response before returning), so the handler runs to completion before the assertion executes. The race is theoretical. Parity — flagged for product owner

Real-world validation

This PR + its downstream DataRudder branch (#37) were smoke-tested end-to-end against https://inference.api-stg.delorean-ai.com:

  • overallStatus: passed, all 3 stages green
  • ~570 ms first run, ~979 ms re-test post the F1-F5 codereview fixes
  • Validates real OAuth2 token exchange + form-body credentials + Bearer header propagation

Next steps

  • Awaiting CI re-run with 4db4568 to confirm gosec passes
  • Platform-wide parity items will be addressed by the product team in follow-up work as appropriate (out of this PR's scope)

Happy to discuss any of the above if my reading is off on a specific item.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 22, 2026

Tip

For best results, initiate chat on the files or code changes.

@dy-shimizu — thank you for the thorough cross-referencing work. Your parity analysis is well-reasoned and I agree with the conclusions on each item:

  • Factory ValidationError types / mutable cfg pointer / cache key omissions / nil cfg guard: All confirmed present in the existing OIDC paths. Holding this PR to a stricter standard than the surrounding code would be inconsistent, and the right fix is a platform-wide pass.
  • JSON Schema extra_params denylist: The Go-side denylist in oauth2_token_endpoint.go actually provides stronger protection than the OIDC JSON Schema blocks (which have no propertyNames.not.enum), so this PR is ahead of parity there, not behind.
  • Test sync: The runner.Execute synchronous-call argument is correct — the HTTP handler completes before any assertion runs, so the race is theoretical in this execution model.
  • gosec G101: The // #nosec G101 directive in 4db4568 is exactly the right fix and is consistent with TypeOIDCClientCredentials at Line 32 of types.go.

The scoping decision (keep this PR focused on the new provider; track parity concerns separately with the product owner) is sound for an external collaborator contribution.


Would you like me to open GitHub tracking issues for the platform-wide parity items so they don't get lost after this PR merges? I can open one consolidated issue (or separate ones) covering:

  1. Deep-clone config pointers in all auth providers (NewOIDCClientCredentialsProvider, NewOIDCUserProvider, NewOAuth2TokenEndpointProvider)
  2. Expand cache keys to include Audience, CredentialsLocation, and ExtraParams across all relevant providers
  3. Add nil-cfg guards to FetchClientCredentialsToken, FetchPasswordToken, and FetchOAuth2TokenEndpointToken
  4. Add propertyNames.not.enum denylist to OIDC JSON Schema blocks in http.go

Just let me know and I'll create them assigned to you or @alexgarzao as you prefer.

@dy-shimizu dy-shimizu changed the title feature(http-auth): add oauth2_token_endpoint outbound auth provider feat(http-auth): add oauth2_token_endpoint outbound auth provider May 22, 2026
@dy-shimizu dy-shimizu closed this May 22, 2026
@dy-shimizu dy-shimizu reopened this May 22, 2026
@dy-shimizu dy-shimizu changed the title feat(http-auth): add oauth2_token_endpoint outbound auth provider feat(service): add oauth2_token_endpoint outbound auth provider May 22, 2026
@dy-shimizu dy-shimizu closed this May 22, 2026
@dy-shimizu dy-shimizu reopened this May 22, 2026
@dy-shimizu dy-shimizu removed their assignment May 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants