diff --git a/driver/config/provider.go b/driver/config/provider.go index 7ea72d385c6..5b90700bb2e 100644 --- a/driver/config/provider.go +++ b/driver/config/provider.go @@ -5,6 +5,7 @@ package config import ( "context" + "encoding/json" "fmt" "net/http" "net/url" @@ -467,10 +468,16 @@ func (p *DefaultProvider) AccessTokenStrategy(ctx context.Context, additionalSou return s } -type HookConfig struct { - URL string `json:"url"` - Headers map[string]string `json:"headers"` -} +type ( + Auth struct { + Type string `json:"type"` + Config json.RawMessage `json:"config"` + } + HookConfig struct { + URL string `json:"url"` + Auth *Auth `json:"auth"` + } +) func (p *DefaultProvider) getHookConfig(ctx context.Context, key string) *HookConfig { if hookURL := p.getProvider(ctx).RequestURIF(key, nil); hookURL != nil { diff --git a/driver/config/provider_test.go b/driver/config/provider_test.go index e48528219c4..1b175aa919b 100644 --- a/driver/config/provider_test.go +++ b/driver/config/provider_test.go @@ -5,6 +5,7 @@ package config import ( "context" + "encoding/json" "fmt" "io" "net/http" @@ -444,14 +445,16 @@ func TestHookConfigs(t *testing.T) { c.MustSet(ctx, key, map[string]any{ "url": "http://localhost:8080/hook2", - "headers": map[string]any{ - "My-Headers": "my-value", + "auth": map[string]any{ + "type": "api_key", + "config": json.RawMessage(`{"in":"header","name":"my-header","value":"my-value"}`), }, }) hc = getFunc(ctx) require.NotNil(t, hc) assert.EqualValues(t, "http://localhost:8080/hook2", hc.URL) - assert.EqualValues(t, "my-value", hc.Headers["My-Headers"]) + assert.EqualValues(t, "api_key", hc.Auth.Type) + assert.JSONEq(t, `{"in":"header","name":"my-header","value":"my-value"}`, string(hc.Auth.Config)) } } diff --git a/oauth2/token_hook.go b/oauth2/token_hook.go index 2be298bc0f0..377d15d0004 100644 --- a/oauth2/token_hook.go +++ b/oauth2/token_hook.go @@ -9,6 +9,8 @@ import ( "encoding/json" "net/http" + "github.com/pkg/errors" + "github.com/hashicorp/go-retryablehttp" "github.com/ory/hydra/v2/flow" @@ -56,6 +58,40 @@ type TokenHookResponse struct { Session flow.AcceptOAuth2ConsentRequestSession `json:"session"` } +type APIKeyAuthConfig struct { + In string `json:"in"` + Name string `json:"name"` + Value string `json:"value"` +} + +func applyAuth(req *retryablehttp.Request, auth *config.Auth) error { + if auth == nil { + return nil + } + + switch auth.Type { + case "api_key": + c := struct { + In string `json:"in"` + Name string `json:"name"` + Value string `json:"value"` + }{} + if err := json.Unmarshal(auth.Config, &c); err != nil { + return err + } + + switch c.In { + case "header": + req.Header.Set(c.Name, c.Value) + case "cookie": + req.AddCookie(&http.Cookie{Name: c.Name, Value: c.Value}) + } + default: + return errors.Errorf("unsupported auth type %q", auth.Type) + } + return nil +} + func executeHookAndUpdateSession(ctx context.Context, reg x.HTTPClientProvider, hookConfig *config.HookConfig, reqBodyBytes []byte, session *Session) error { req, err := retryablehttp.NewRequestWithContext(ctx, http.MethodPost, hookConfig.URL, bytes.NewReader(reqBodyBytes)) if err != nil { @@ -66,8 +102,12 @@ func executeHookAndUpdateSession(ctx context.Context, reg x.HTTPClientProvider, WithDebugf("Unable to prepare the HTTP Request: %s", err), ) } - for k, v := range hookConfig.Headers { - req.Header.Set(k, v) + if err := applyAuth(req, hookConfig.Auth); err != nil { + return errorsx.WithStack( + fosite.ErrServerError. + WithWrap(err). + WithDescription("An error occurred while applying the token hook authentication."). + WithDebugf("Unable to apply the token hook authentication: %s", err)) } req.Header.Set("Content-Type", "application/json; charset=UTF-8") diff --git a/spec/config.json b/spec/config.json index a812b4ee0b0..a1a4c47b0da 100644 --- a/spec/config.json +++ b/spec/config.json @@ -248,6 +248,48 @@ } } } + }, + "webhook_config": { + "type": "object", + "additionalProperties": false, + "description": "Configures a webhook.", + "required": ["url"], + "properties": { + "url": { + "type": "string", + "format": "uri", + "description": "The URL to send the webhook to." + }, + "auth": { + "type": "object", + "additionalProperties": false, + "required": ["type", "config"], + "properties": { + "type": { + "const": "api_key" + }, + "config": { + "type": "object", + "additionalProperties": false, + "required": ["name", "value"], + "properties": { + "in": { + "enum": ["header", "cookie"], + "default": "header" + }, + "name": { + "description": "The header or cookie name.", + "type": "string" + }, + "value": { + "description": "The header or cookie value.", + "type": "string" + } + } + } + } + } + } } }, "properties": { @@ -1047,20 +1089,7 @@ "format": "uri" }, { - "type": "object", - "properties": { - "url": { - "type": "string", - "format": "uri" - }, - "headers": { - "description": "Sets the header to be used when calling the refresh token hook endpoint.", - "type": "object", - "additionalProperties": { - "type": "string" - } - } - } + "$ref": "#/definitions/webhook_config" } ] }, @@ -1073,20 +1102,7 @@ "format": "uri" }, { - "type": "object", - "properties": { - "url": { - "type": "string", - "format": "uri" - }, - "headers": { - "description": "Sets the header to be used when calling the token hook endpoint.", - "type": "object", - "additionalProperties": { - "type": "string" - } - } - } + "$ref": "#/definitions/webhook_config" } ] }