diff --git a/internal/api/hooks.go b/internal/api/hooks.go index c66a94b5f..1ef61a4c2 100644 --- a/internal/api/hooks.go +++ b/internal/api/hooks.go @@ -10,7 +10,6 @@ import ( "mime" "net" "net/http" - "net/url" "strings" "time" @@ -188,13 +187,9 @@ func (a *API) runHTTPHook(r *http.Request, hookConfig conf.ExtensibilityPointCon // transaction is opened. If calling invokeHook within a transaction, always // pass the current transaction, as pool-exhaustion deadlocks are very easy to // trigger. -func (a *API) invokeHook(conn *storage.Connection, r *http.Request, input, output any, uri string) error { +func (a *API) invokeHook(conn *storage.Connection, r *http.Request, input, output any) error { var err error var response []byte - u, err := url.Parse(uri) - if err != nil { - return err - } switch input.(type) { case *hooks.SendSMSInput: @@ -202,7 +197,7 @@ func (a *API) invokeHook(conn *storage.Connection, r *http.Request, input, outpu if !ok { panic("output should be *hooks.SendSMSOutput") } - if response, err = a.runHook(r, conn, a.config.Hook.SendSMS, input, output, u.Scheme); err != nil { + if response, err = a.runHook(r, conn, a.config.Hook.SendSMS, input, output); err != nil { return err } if err := json.Unmarshal(response, hookOutput); err != nil { @@ -226,7 +221,7 @@ func (a *API) invokeHook(conn *storage.Connection, r *http.Request, input, outpu if !ok { panic("output should be *hooks.SendEmailOutput") } - if response, err = a.runHook(r, conn, a.config.Hook.SendEmail, input, output, u.Scheme); err != nil { + if response, err = a.runHook(r, conn, a.config.Hook.SendEmail, input, output); err != nil { return err } if err := json.Unmarshal(response, hookOutput); err != nil { @@ -252,7 +247,7 @@ func (a *API) invokeHook(conn *storage.Connection, r *http.Request, input, outpu if !ok { panic("output should be *hooks.MFAVerificationAttemptOutput") } - if response, err = a.runHook(r, conn, a.config.Hook.MFAVerificationAttempt, input, output, u.Scheme); err != nil { + if response, err = a.runHook(r, conn, a.config.Hook.MFAVerificationAttempt, input, output); err != nil { return err } if err := json.Unmarshal(response, hookOutput); err != nil { @@ -279,7 +274,7 @@ func (a *API) invokeHook(conn *storage.Connection, r *http.Request, input, outpu panic("output should be *hooks.PasswordVerificationAttemptOutput") } - if response, err = a.runHook(r, conn, a.config.Hook.PasswordVerificationAttempt, input, output, u.Scheme); err != nil { + if response, err = a.runHook(r, conn, a.config.Hook.PasswordVerificationAttempt, input, output); err != nil { return err } if err := json.Unmarshal(response, hookOutput); err != nil { @@ -306,7 +301,7 @@ func (a *API) invokeHook(conn *storage.Connection, r *http.Request, input, outpu if !ok { panic("output should be *hooks.CustomAccessTokenOutput") } - if response, err = a.runHook(r, conn, a.config.Hook.CustomAccessToken, input, output, u.Scheme); err != nil { + if response, err = a.runHook(r, conn, a.config.Hook.CustomAccessToken, input, output); err != nil { return err } if err := json.Unmarshal(response, hookOutput); err != nil { @@ -345,20 +340,43 @@ func (a *API) invokeHook(conn *storage.Connection, r *http.Request, input, outpu return nil } -func (a *API) runHook(r *http.Request, conn *storage.Connection, hookConfig conf.ExtensibilityPointConfiguration, input, output any, scheme string) ([]byte, error) { +func (a *API) runHook(r *http.Request, conn *storage.Connection, hookConfig conf.ExtensibilityPointConfiguration, input, output any) ([]byte, error) { ctx := r.Context() + + logEntry := observability.GetLogEntry(r) + hookStart := time.Now() + var response []byte var err error - switch strings.ToLower(scheme) { - case "http", "https": + + switch { + case strings.HasPrefix(hookConfig.URI, "http:") || strings.HasPrefix(hookConfig.URI, "https:"): response, err = a.runHTTPHook(r, hookConfig, input) - case "pg-functions": + case strings.HasPrefix(hookConfig.URI, "pg-functions:"): response, err = a.runPostgresHook(ctx, conn, hookConfig, input, output) default: - return nil, fmt.Errorf("unsupported protocol: %v only postgres hooks and HTTPS functions are supported at the moment", scheme) + return nil, fmt.Errorf("unsupported protocol: %q only postgres hooks and HTTPS functions are supported at the moment", hookConfig.URI) } + + duration := time.Now().Sub(hookStart) + if err != nil { + logEntry.Entry.WithFields(logrus.Fields{ + "action": "run_hook", + "hook": hookConfig.URI, + "success": false, + "duration": duration.Microseconds(), + }).WithError(err).Warn("Hook errored out") + return nil, internalServerError("Error running hook URI: %v", hookConfig.URI).WithInternalError(err) } + + logEntry.Entry.WithFields(logrus.Fields{ + "action": "run_hook", + "hook": hookConfig.URI, + "success": true, + "duration": duration.Microseconds(), + }).WithError(err).Info("Hook ran successfully") + return response, nil } diff --git a/internal/api/hooks_test.go b/internal/api/hooks_test.go index d645ef41b..ab67da115 100644 --- a/internal/api/hooks_test.go +++ b/internal/api/hooks_test.go @@ -274,7 +274,7 @@ func (ts *HooksTestSuite) TestInvokeHookIntegration() { require.NoError(ts.T(), ts.Config.Hook.SendEmail.PopulateExtensibilityPoint()) ts.Run(tc.description, func() { - err = ts.API.invokeHook(tc.conn, tc.request, tc.input, tc.output, tc.uri) + err = ts.API.invokeHook(tc.conn, tc.request, tc.input, tc.output) if tc.expectedError != nil { require.EqualError(ts.T(), err, tc.expectedError.Error()) } else { diff --git a/internal/api/mail.go b/internal/api/mail.go index 30f358ad2..35f529e25 100644 --- a/internal/api/mail.go +++ b/internal/api/mail.go @@ -589,7 +589,7 @@ func (a *API) sendEmail(r *http.Request, tx *storage.Connection, u *models.User, EmailData: emailData, } output := hooks.SendEmailOutput{} - return a.invokeHook(tx, r, &input, &output, a.config.Hook.SendEmail.URI) + return a.invokeHook(tx, r, &input, &output) } switch emailActionType { diff --git a/internal/api/mfa.go b/internal/api/mfa.go index c15f4a3b4..df7b84d36 100644 --- a/internal/api/mfa.go +++ b/internal/api/mfa.go @@ -261,7 +261,7 @@ func (a *API) VerifyFactor(w http.ResponseWriter, r *http.Request) error { } output := hooks.MFAVerificationAttemptOutput{} - err := a.invokeHook(nil, r, &input, &output, a.config.Hook.MFAVerificationAttempt.URI) + err := a.invokeHook(nil, r, &input, &output) if err != nil { return err } diff --git a/internal/api/phone.go b/internal/api/phone.go index 2147a959b..86d569b94 100644 --- a/internal/api/phone.go +++ b/internal/api/phone.go @@ -104,7 +104,7 @@ func (a *API) sendPhoneConfirmation(r *http.Request, tx *storage.Connection, use }, } output := hooks.SendSMSOutput{} - err := a.invokeHook(tx, r, &input, &output, a.config.Hook.SendSMS.URI) + err := a.invokeHook(tx, r, &input, &output) if err != nil { return "", err } diff --git a/internal/api/token.go b/internal/api/token.go index 9a94f3e14..712a44bd7 100644 --- a/internal/api/token.go +++ b/internal/api/token.go @@ -185,7 +185,7 @@ func (a *API) ResourceOwnerPasswordGrant(ctx context.Context, w http.ResponseWri Valid: isValidPassword, } output := hooks.PasswordVerificationAttemptOutput{} - err := a.invokeHook(nil, r, &input, &output, a.config.Hook.PasswordVerificationAttempt.URI) + err := a.invokeHook(nil, r, &input, &output) if err != nil { return err } @@ -360,7 +360,7 @@ func (a *API) generateAccessToken(r *http.Request, tx *storage.Connection, user output := hooks.CustomAccessTokenOutput{} - err := a.invokeHook(tx, r, &input, &output, a.config.Hook.CustomAccessToken.URI) + err := a.invokeHook(tx, r, &input, &output) if err != nil { return "", 0, err }