github.com/greenpau/go-authcrunch@v1.0.50/pkg/authn/handle_http_settings.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  	"fmt"
    20  	"github.com/greenpau/go-authcrunch/pkg/requests"
    21  	"github.com/greenpau/go-authcrunch/pkg/user"
    22  	addrutil "github.com/greenpau/go-authcrunch/pkg/util/addr"
    23  	"go.uber.org/zap"
    24  	"net/http"
    25  	"strings"
    26  )
    27  
    28  func (p *Portal) handleHTTPSettings(ctx context.Context, w http.ResponseWriter, r *http.Request, rr *requests.Request, parsedUser *user.User) error {
    29  	p.disableClientCache(w)
    30  	p.injectRedirectURL(ctx, w, r, rr)
    31  	if parsedUser == nil {
    32  		if rr.Response.RedirectURL == "" {
    33  			return p.handleHTTPRedirect(ctx, w, r, rr, "/login?redirect_url="+r.RequestURI)
    34  		}
    35  		return p.handleHTTPRedirect(ctx, w, r, rr, "/login")
    36  	}
    37  
    38  	usr, err := p.sessions.Get(parsedUser.Claims.ID)
    39  	if err != nil {
    40  		p.logger.Warn(
    41  			"jti session not found",
    42  			zap.String("session_id", rr.Upstream.SessionID),
    43  			zap.String("request_id", rr.ID),
    44  			zap.String("jti", parsedUser.Claims.ID),
    45  			zap.Any("error", err),
    46  			zap.String("source_address", addrutil.GetSourceAddress(r)),
    47  		)
    48  		return p.handleHTTPLogoutWithLocalRedirect(ctx, w, r, rr)
    49  	}
    50  
    51  	if permitted := usr.HasRole("authp/admin", "authp/user"); !permitted {
    52  		return p.handleHTTPError(ctx, w, r, rr, http.StatusForbidden)
    53  	}
    54  
    55  	switch usr.Authenticator.Method {
    56  	case "local":
    57  	default:
    58  		return p.handleHTTPGeneric(ctx, w, r, rr, http.StatusServiceUnavailable, http.StatusText(http.StatusServiceUnavailable))
    59  	}
    60  
    61  	endpoint, err := getEndpoint(r.URL.Path, "/settings")
    62  	if err != nil {
    63  		return p.handleHTTPError(ctx, w, r, rr, http.StatusBadRequest)
    64  	}
    65  
    66  	backend := p.getIdentityStoreByRealm(usr.Authenticator.Realm)
    67  	if backend == nil {
    68  		p.logger.Warn(
    69  			"backend not found",
    70  			zap.String("session_id", rr.Upstream.SessionID),
    71  			zap.String("request_id", rr.ID),
    72  			zap.String("realm", usr.Authenticator.Realm),
    73  			zap.String("jti", parsedUser.Claims.ID),
    74  			zap.String("source_address", addrutil.GetSourceAddress(r)),
    75  		)
    76  		return p.handleHTTPLogoutWithLocalRedirect(ctx, w, r, rr)
    77  	}
    78  
    79  	p.logger.Debug(
    80  		"Rendering settings page",
    81  		zap.String("session_id", rr.Upstream.SessionID),
    82  		zap.String("request_id", rr.ID),
    83  		zap.String("realm", usr.Authenticator.Realm),
    84  		zap.String("jti", parsedUser.Claims.ID),
    85  		zap.String("source_address", addrutil.GetSourceAddress(r)),
    86  		zap.String("endpoint", endpoint),
    87  	)
    88  
    89  	resp := p.ui.GetArgs()
    90  	resp.PageTitle = "Profile"
    91  	resp.BaseURL(rr.Upstream.BasePath)
    92  
    93  	// Populate username (sub) and email address (email)
    94  	rr.User.Username = usr.Claims.Subject
    95  	rr.User.Email = usr.Claims.Email
    96  
    97  	switch {
    98  	case strings.HasPrefix(endpoint, "/password"):
    99  		resp.PageTitle = "Password Management"
   100  		resp.NavItems = p.config.UI.GetNavigationItems("settings/password")
   101  		if p.config.UI.IsDisabledPage("settings/password") {
   102  			return p.handleHTTPError(ctx, w, r, rr, http.StatusForbidden)
   103  		}
   104  		if err := p.handleHTTPPasswordSettings(ctx, r, rr, usr, backend, resp.Data); err != nil {
   105  			return p.handleHTTPError(ctx, w, r, rr, http.StatusBadRequest)
   106  		}
   107  	case strings.HasPrefix(endpoint, "/apikeys"):
   108  		resp.PageTitle = "API Key Management"
   109  		resp.NavItems = p.config.UI.GetNavigationItems("settings/apikeys")
   110  		if p.config.UI.IsDisabledPage("settings/apikeys") {
   111  			return p.handleHTTPError(ctx, w, r, rr, http.StatusForbidden)
   112  		}
   113  		if err := p.handleHTTPAPIKeysSettings(ctx, r, rr, usr, backend, resp.Data); err != nil {
   114  			return p.handleHTTPError(ctx, w, r, rr, http.StatusBadRequest)
   115  		}
   116  	case strings.HasPrefix(endpoint, "/sshkeys"):
   117  		resp.PageTitle = "SSH Key Management"
   118  		resp.NavItems = p.config.UI.GetNavigationItems("settings/sshkeys")
   119  		if p.config.UI.IsDisabledPage("settings/sshkeys") {
   120  			return p.handleHTTPError(ctx, w, r, rr, http.StatusForbidden)
   121  		}
   122  		if err := p.handleHTTPSSHKeysSettings(ctx, r, rr, usr, backend, resp.Data); err != nil {
   123  			return p.handleHTTPError(ctx, w, r, rr, http.StatusBadRequest)
   124  		}
   125  	case strings.HasPrefix(endpoint, "/gpgkeys"):
   126  		resp.PageTitle = "GPG Key Management"
   127  		resp.NavItems = p.config.UI.GetNavigationItems("settings/gpgkeys")
   128  		if p.config.UI.IsDisabledPage("settings/gpgkeys") {
   129  			return p.handleHTTPError(ctx, w, r, rr, http.StatusForbidden)
   130  		}
   131  		if err := p.handleHTTPGPGKeysSettings(ctx, r, rr, usr, backend, resp.Data); err != nil {
   132  			return p.handleHTTPError(ctx, w, r, rr, http.StatusBadRequest)
   133  		}
   134  	case strings.HasPrefix(endpoint, "/mfa/barcode/"):
   135  		return p.handleHTTPMfaBarcode(ctx, w, r, endpoint)
   136  	case strings.HasPrefix(endpoint, "/mfa"):
   137  		resp.PageTitle = "Multi-Factor Authentication"
   138  		resp.NavItems = p.config.UI.GetNavigationItems("settings/mfa")
   139  		if p.config.UI.IsDisabledPage("settings/mfa") {
   140  			return p.handleHTTPError(ctx, w, r, rr, http.StatusForbidden)
   141  		}
   142  		if err := p.handleHTTPMfaSettings(ctx, r, rr, usr, backend, resp.Data); err != nil {
   143  			return p.handleHTTPError(ctx, w, r, rr, http.StatusBadRequest)
   144  		}
   145  	case strings.HasPrefix(endpoint, "/connected"):
   146  		resp.PageTitle = "Connected Accounts"
   147  		resp.NavItems = p.config.UI.GetNavigationItems("settings/connected")
   148  		if p.config.UI.IsDisabledPage("settings/connected") {
   149  			return p.handleHTTPError(ctx, w, r, rr, http.StatusForbidden)
   150  		}
   151  		resp.Data["view"] = "connected"
   152  	default:
   153  		resp.NavItems = p.config.UI.GetNavigationItems("settings/")
   154  		if err := p.handleHTTPGeneralSettings(ctx, r, rr, usr, backend, resp.Data); err != nil {
   155  			return p.handleHTTPError(ctx, w, r, rr, http.StatusBadRequest)
   156  		}
   157  	}
   158  	content, err := p.ui.Render("settings", resp)
   159  	if err != nil {
   160  		return p.handleHTTPRenderError(ctx, w, r, rr, err)
   161  	}
   162  	return p.handleHTTPRenderHTML(ctx, w, http.StatusOK, content.Bytes())
   163  }
   164  
   165  func getEndpoint(p, s string) (string, error) {
   166  	i := strings.Index(p, s)
   167  	if i < 0 {
   168  		return s, fmt.Errorf("%s is not in %s", p, s)
   169  	}
   170  	return strings.TrimPrefix(p[i:], s), nil
   171  }
   172  
   173  func getEndpointKeyID(p, s string) (string, error) {
   174  	sp, err := getEndpoint(p, s)
   175  	if err != nil {
   176  		return "", err
   177  	}
   178  	arr := strings.Split(sp, "/")
   179  	if len(arr) != 1 {
   180  		return "", fmt.Errorf("invalid key id")
   181  	}
   182  	return arr[0], nil
   183  }
   184  
   185  func attachView(data map[string]interface{}, entrypoint, action string, status bool) {
   186  	if action == "" {
   187  		data["view"] = entrypoint
   188  		return
   189  	}
   190  	if status {
   191  		data["view"] = fmt.Sprintf("%s-%s-status", entrypoint, action)
   192  		return
   193  	}
   194  	data["view"] = fmt.Sprintf("%s-%s", entrypoint, action)
   195  }
   196  
   197  func attachStatus(data map[string]interface{}, status, statusText string) {
   198  	data["status"] = status
   199  	data["status_reason"] = statusText
   200  }
   201  
   202  func attachSuccessStatus(data map[string]interface{}, statusText string) {
   203  	attachStatus(data, "SUCCESS", statusText)
   204  }
   205  
   206  func attachFailStatus(data map[string]interface{}, statusText string) {
   207  	attachStatus(data, "FAIL", statusText)
   208  }