Skip to content

Commit

Permalink
Merge pull request #72 from eurofurence/issue-71-flow-errors
Browse files Browse the repository at this point in the history
feat(#71): display flow errors and show retry link
  • Loading branch information
Jumpy-Squirrel authored Feb 7, 2024
2 parents 345a87f + f6f180d commit f427310
Show file tree
Hide file tree
Showing 5 changed files with 50 additions and 16 deletions.
2 changes: 1 addition & 1 deletion internal/web/controller/authctl/authctl.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ func authHandler(w http.ResponseWriter, r *http.Request) {
func authErrorHandler(ctx context.Context, w http.ResponseWriter, regAppName string, dropOffUrl string, state string, status int, logMsg string, publicMsg string) {
aulogging.Logger.Ctx(ctx).Warn().Printf("FAIL auth(%s,%s)[%s]: %s", regAppName, dropOffUrl, state, logMsg)
w.WriteHeader(status)
_, _ = w.Write(controller.ErrorResponse(ctx, publicMsg))
_, _ = w.Write(controller.ErrorResponse(ctx, publicMsg, ""))
}

func validateDropOffURL(ctx context.Context, w http.ResponseWriter, exp string, dropOffUrl string) bool {
Expand Down
33 changes: 21 additions & 12 deletions internal/web/controller/dropoffctl/dropoffctl.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package dropoffctl

import (
"context"
"fmt"
aulogging "github.com/StephanHCB/go-autumn-logging"
"github.com/eurofurence/reg-auth-service/internal/web/controller"
"net/http"
Expand Down Expand Up @@ -35,45 +36,53 @@ func dropOffHandler(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query()
state := query.Get("state")
if state == "" {
dropOffErrorHandler(ctx, w, state, http.StatusBadRequest, "state parameter is missing", "invalid parameters")
return
}
authCode := query.Get("code")
if authCode == "" {
dropOffErrorHandler(ctx, w, state, http.StatusBadRequest, "authorization_code parameter is missing", "invalid parameters")
dropOffErrorHandler(ctx, w, state, http.StatusBadRequest, "state parameter is missing", "invalid parameters", "")
return
}

authRequest, err := database.GetRepository().GetAuthRequestByState(ctx, state)
if err != nil {
dropOffErrorHandler(ctx, w, state, http.StatusNotFound, "couldn't load auth request: "+err.Error(), "auth request not found or timed out")
dropOffErrorHandler(ctx, w, state, http.StatusNotFound, "couldn't load auth request: "+err.Error(), "auth request not found or timed out", "")
return
}

applicationConfig, err := config.GetApplicationConfig(authRequest.Application)
if err != nil {
dropOffErrorHandler(ctx, w, state, http.StatusInternalServerError, "couldn't load application config: "+err.Error(), "internal error")
dropOffErrorHandler(ctx, w, state, http.StatusInternalServerError, "couldn't load application config: "+err.Error(), "internal error", "")
return
}

errorCode := query.Get("error")
errorDescription := query.Get("error_description")
if errorCode != "" {
dropOffErrorHandler(ctx, w, state, http.StatusBadRequest, fmt.Sprintf("error parameter set (%s|%s)", errorCode, errorDescription), fmt.Sprintf("%s: %s", errorCode, errorDescription), applicationConfig.DefaultDropoffUrl)
return
}

authCode := query.Get("code")
if authCode == "" {
dropOffErrorHandler(ctx, w, state, http.StatusBadRequest, "authorization_code parameter is missing", "invalid parameters", "")
return
}

idToken, accessToken, httpstatus, err := fetchToken(ctx, authCode, *authRequest)
if err != nil {
dropOffErrorHandler(ctx, w, state, httpstatus, "couldn't fetch access codes: "+err.Error(), "failed to fetch token")
dropOffErrorHandler(ctx, w, state, httpstatus, "couldn't fetch access codes: "+err.Error(), "failed to fetch token", "")
return
}

err = setCookiesAndRedirectToDropOffUrl(ctx, w, idToken, accessToken, *authRequest, applicationConfig)
if err != nil {
dropOffErrorHandler(ctx, w, state, http.StatusInternalServerError, err.Error(), "internal error")
dropOffErrorHandler(ctx, w, state, http.StatusInternalServerError, err.Error(), "internal error", "")
return
}
aulogging.Logger.Ctx(ctx).Info().Printf("OK dropoff[%s]", state)
}

func dropOffErrorHandler(ctx context.Context, w http.ResponseWriter, state string, status int, logMsg string, publicMsg string) {
func dropOffErrorHandler(ctx context.Context, w http.ResponseWriter, state string, status int, logMsg string, publicMsg string, retryUrl string) {
aulogging.Logger.Ctx(ctx).Warn().Printf("FAIL dropoff[%s]: %s", state, logMsg)
w.WriteHeader(status)
_, _ = w.Write(controller.ErrorResponse(ctx, publicMsg))
_, _ = w.Write(controller.ErrorResponse(ctx, publicMsg, retryUrl))
}

func fetchToken(ctx context.Context, authCode string, ar entity.AuthRequest) (string, string, int, error) {
Expand Down
9 changes: 7 additions & 2 deletions internal/web/controller/errorresponse.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,12 @@ import (

const errTimeFormat = "02.01.-15:04:05"

func ErrorResponse(ctx context.Context, msg string) []byte {
func ErrorResponse(ctx context.Context, msg string, retryUrl string) []byte {
currentTime := time.Now().Format(errTimeFormat)
retry := ""
if retryUrl != "" {
retry = fmt.Sprintf(`<p>You can also <a href="%s">go back to try again</a>.</p>`, retryUrl)
}
response := fmt.Sprintf(`<HTML>
<HEAD>
<TITLE>Reg Auth Service Error</TITLE>
Expand All @@ -22,7 +26,8 @@ func ErrorResponse(ctx context.Context, msg string) []byte {
<p><b>error:</b> %s</p>
<p><font color="red"><b>code:</b> %s-%s</font></p>
<p>If you wish us to investigate an error, please provide us with the code.</p>
%s
</BODY>
</HTML>`, msg, ctxvalues.RequestId(ctx), currentTime)
</HTML>`, msg, ctxvalues.RequestId(ctx), currentTime, retry)
return []byte(response)
}
2 changes: 1 addition & 1 deletion internal/web/controller/logoutctl/logoutctl.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func logoutHandler(w http.ResponseWriter, r *http.Request) {
func logoutErrorHandler(ctx context.Context, w http.ResponseWriter, appName string, status int, logMsg string, publicMsg string) {
aulogging.Logger.Ctx(ctx).Warn().Printf("FAIL v1/logout(%s) -> %d: %s", appName, status, logMsg)
w.WriteHeader(status)
_, _ = w.Write(controller.ErrorResponse(ctx, publicMsg))
_, _ = w.Write(controller.ErrorResponse(ctx, publicMsg, ""))
}

func clearCookieAndRedirectToDropOffUrl(ctx context.Context, w http.ResponseWriter, applicationConfig config.ApplicationConfig) error {
Expand Down
20 changes: 20 additions & 0 deletions test/acceptance/dropoff_endpoint_acc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ import (
* * state - random-string identifier of this flow
* * code - temporary credential to obtain the access token from the OIDC provider
*
* Error parameters are:
* * state - random-string identifier of this flow
* * error - the error code
* * error_description - human-readable description text
*/

func TestDropoff_Success(t *testing.T) {
Expand Down Expand Up @@ -67,6 +71,22 @@ func TestDropoff_Success(t *testing.T) {
require.Equal(t, "example.com", ac.Domain)
}

func TestDropoff_Failure_IDPError(t *testing.T) {
docs.Given("given the standard test configuration")
tstSetup(tstDefaultConfigFile)
defer tstShutdown()

docs.When("when they call the dropoff endpoint with an error")
test_url := "/v1/dropoff?state=" + tstAuthRequest.State + "&error=request_unauthorized&error_description=The+request+could+not+be+authorized"
response := tstPerformGetNoRedirect(test_url)

docs.Then("then the correct error is displayed")
require.Equal(t, http.StatusBadRequest, response.StatusCode, "unexpected http response status, must be HTTP 400")
responseBody := tstResponseBodyString(&response)
require.Contains(t, responseBody, "<b>error:</b> request_unauthorized: The request could not be authorized")
require.Contains(t, responseBody, `You can also <a href="https://example.com/app/">go back to try again</a>.`)
}

func TestDropoff_Failure_StateMissing(t *testing.T) {
docs.Given("given the standard test configuration")
tstSetup(tstDefaultConfigFile)
Expand Down

0 comments on commit f427310

Please sign in to comment.