github.com/greenpau/go-authcrunch@v1.1.4/pkg/authn/handle_external_login.go (about)

     1  // Copyright 2022 Paul Greenberg greenpau@outlook.com
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package authn
    16  
    17  import (
    18  	"context"
    19  	"net/http"
    20  	"strings"
    21  
    22  	"github.com/greenpau/go-authcrunch/pkg/authn/enums/operator"
    23  	"github.com/greenpau/go-authcrunch/pkg/requests"
    24  	"go.uber.org/zap"
    25  )
    26  
    27  func (p *Portal) handleHTTPExternalLogin(ctx context.Context, w http.ResponseWriter, r *http.Request, rr *requests.Request, authMethod string) error {
    28  	p.disableClientCache(w)
    29  	p.injectRedirectURL(ctx, w, r, rr)
    30  
    31  	if strings.Contains(r.URL.Path, "-js-callback") {
    32  		// Intercept callback with Javascript.
    33  		return p.handleJavascriptCallbackIntercept(ctx, w, r)
    34  	}
    35  
    36  	authRealm, err := getEndpoint(r.URL.Path, "/"+authMethod+"/")
    37  	if err != nil {
    38  		return p.handleHTTPError(ctx, w, r, rr, http.StatusBadRequest)
    39  	}
    40  	authRealm = strings.Split(authRealm, "/")[0]
    41  
    42  	rr.Upstream.Method = authMethod
    43  	rr.Upstream.Realm = authRealm
    44  	rr.Flags.Enabled = true
    45  
    46  	p.logger.Debug(
    47  		"External login requested",
    48  		zap.String("session_id", rr.Upstream.SessionID),
    49  		zap.String("request_id", rr.ID),
    50  		zap.String("base_url", rr.Upstream.BaseURL),
    51  		zap.String("base_path", rr.Upstream.BasePath),
    52  		zap.String("auth_method", rr.Upstream.Method),
    53  		zap.String("auth_realm", rr.Upstream.Realm),
    54  		zap.Any("request_path", r.URL.Path),
    55  	)
    56  
    57  	provider := p.getIdentityProviderByRealm(authRealm)
    58  	if provider == nil {
    59  		p.logger.Warn(
    60  			"Authentication failed",
    61  			zap.String("session_id", rr.Upstream.SessionID),
    62  			zap.String("request_id", rr.ID),
    63  			zap.String("error", "identity provider not found"),
    64  		)
    65  		return p.handleHTTPError(ctx, w, r, rr, http.StatusBadRequest)
    66  	}
    67  	err = provider.Request(operator.Authenticate, rr)
    68  	if err != nil {
    69  		p.logger.Warn(
    70  			"Authentication failed",
    71  			zap.String("session_id", rr.Upstream.SessionID),
    72  			zap.String("request_id", rr.ID),
    73  			zap.Error(err),
    74  		)
    75  		return p.handleHTTPError(ctx, w, r, rr, http.StatusUnauthorized)
    76  	}
    77  	switch rr.Response.Code {
    78  	case http.StatusBadRequest:
    79  		return p.handleHTTPError(ctx, w, r, rr, http.StatusBadRequest)
    80  	case http.StatusOK:
    81  		p.logger.Info(
    82  			"Successful login",
    83  			zap.String("session_id", rr.Upstream.SessionID),
    84  			zap.String("request_id", rr.ID),
    85  			zap.String("auth_method", rr.Upstream.Method),
    86  			zap.String("auth_realm", rr.Upstream.Realm),
    87  			zap.Any("user", rr.Response.Payload),
    88  		)
    89  	case http.StatusFound:
    90  		p.logger.Debug(
    91  			"Redirect to authorization server",
    92  			zap.String("session_id", rr.Upstream.SessionID),
    93  			zap.String("request_id", rr.ID),
    94  			zap.Any("url", rr.Response.RedirectURL),
    95  		)
    96  		http.Redirect(w, r, rr.Response.RedirectURL, http.StatusFound)
    97  		return nil
    98  	default:
    99  		return p.handleHTTPError(ctx, w, r, rr, http.StatusNotImplemented)
   100  	}
   101  	// User authenticated successfully.
   102  	if err := p.authorizeLoginRequest(ctx, w, r, rr); err != nil {
   103  		return p.handleHTTPErrorWithLog(ctx, w, r, rr, rr.Response.Code, err.Error())
   104  	}
   105  	w.WriteHeader(rr.Response.Code)
   106  	return nil
   107  }
   108  
   109  func (p *Portal) handleJavascriptCallbackIntercept(_ context.Context, w http.ResponseWriter, _ *http.Request) error {
   110  	p.disableClientCache(w)
   111  	w.WriteHeader(200)
   112  
   113  	w.Write([]byte(`<html>
   114    <body>
   115      <p>Redirecting to authentication endpoint.</p>
   116      <script>
   117        let redirectURL = window.location.href;
   118        const i = redirectURL.indexOf("#");
   119        if (i < 0) {
   120          redirectURL = redirectURL.replace('authorization-code-js-callback', 'authorization-code-callback');
   121          window.location = redirectURL;
   122        } else {
   123          redirectURI = redirectURL.slice(0, i).replace('authorization-code-js-callback', 'authorization-code-callback');
   124          window.location = redirectURI + "?" + redirectURL.slice(i+1);
   125        }
   126      </script>
   127    </body>
   128  </html>`))
   129  	return nil
   130  }