github.com/greenpau/go-authcrunch@v1.1.4/pkg/authn/handle_api_profile.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  	"encoding/json"
    20  	"fmt"
    21  	"io"
    22  	"net/http"
    23  	"time"
    24  
    25  	"github.com/greenpau/go-authcrunch/pkg/authn/enums/role"
    26  	"github.com/greenpau/go-authcrunch/pkg/requests"
    27  
    28  	"regexp"
    29  
    30  	"github.com/greenpau/go-authcrunch/pkg/user"
    31  	addrutil "github.com/greenpau/go-authcrunch/pkg/util/addr"
    32  	"go.uber.org/zap"
    33  )
    34  
    35  var tokenSecretRegexPattern = regexp.MustCompile(`^[A-Za-z0-9]{10,200}$`)
    36  var tokenIssuerRegexPattern = regexp.MustCompile(`^[A-Za-z0-9]{3,50}$`)
    37  var tokenDescriptionRegexPattern = regexp.MustCompile(`^[\w\s\-\_,\.]{3,255}$`)
    38  var tokenPasscodeRegexPattern = regexp.MustCompile(`^[0-9]{4,8}$`)
    39  
    40  func handleAPIProfileResponse(w http.ResponseWriter, rr *requests.Request, code int, resp map[string]interface{}) error {
    41  	resp["status"] = code
    42  	rr.Response.Code = code
    43  	respBytes, _ := json.Marshal(resp)
    44  	w.WriteHeader(rr.Response.Code)
    45  	w.Write(respBytes)
    46  	return nil
    47  }
    48  
    49  func (p *Portal) handleAPIProfile(ctx context.Context, w http.ResponseWriter, r *http.Request, rr *requests.Request, parsedUser *user.User) error {
    50  	resp := make(map[string]interface{})
    51  	resp["timestamp"] = time.Now().UTC().Format(time.RFC3339Nano)
    52  
    53  	if parsedUser == nil {
    54  		resp["message"] = "Profile API received nil user"
    55  		return handleAPIProfileResponse(w, rr, http.StatusBadRequest, resp)
    56  	}
    57  
    58  	usr, err := p.sessions.Get(parsedUser.Claims.ID)
    59  	if err != nil {
    60  		p.logger.Warn(
    61  			"jti session not found",
    62  			zap.String("session_id", rr.Upstream.SessionID),
    63  			zap.String("request_id", rr.ID),
    64  			zap.String("jti", parsedUser.Claims.ID),
    65  			zap.Any("error", err),
    66  			zap.String("source_address", addrutil.GetSourceAddress(r)),
    67  		)
    68  		resp["message"] = "Profile API failed to locate JTI session"
    69  		return handleAPIProfileResponse(w, rr, http.StatusUnauthorized, resp)
    70  	}
    71  
    72  	if err := p.authorizedRole(usr, []role.Kind{role.Admin, role.User}, rr.Response.Authenticated); err != nil {
    73  		resp["message"] = "Profile API did not find valid role for the user"
    74  		return handleAPIProfileResponse(w, rr, http.StatusForbidden, resp)
    75  	}
    76  
    77  	// Unpack the request and determine the type of the request.
    78  	var reqKind = "unknown"
    79  
    80  	// Read the request body
    81  	defer r.Body.Close()
    82  	body, err := io.ReadAll(r.Body)
    83  	if err != nil {
    84  		resp["message"] = "Profile API failed to parse request body"
    85  		return handleAPIProfileResponse(w, rr, http.StatusBadRequest, resp)
    86  	}
    87  	var bodyData map[string]interface{}
    88  	if err := json.Unmarshal(body, &bodyData); err != nil {
    89  		resp["message"] = "Profile API failed to parse request JSON body"
    90  		return handleAPIProfileResponse(w, rr, http.StatusBadRequest, resp)
    91  	}
    92  
    93  	if v, exists := bodyData["kind"]; exists {
    94  		reqKind = v.(string)
    95  	}
    96  
    97  	switch reqKind {
    98  	case "fetch_debug":
    99  	case "fetch_user_dashboard_data":
   100  	case "delete_user_multi_factor_authenticator":
   101  	case "fetch_user_multi_factor_authenticators":
   102  	case "fetch_user_multi_factor_authenticator":
   103  	case "fetch_user_app_multi_factor_authenticator_code":
   104  	case "test_user_app_multi_factor_authenticator":
   105  	case "add_user_app_multi_factor_authenticator":
   106  	case "test_user_webauthn_token":
   107  	case "test_user_app_token_passcode":
   108  	case "fetch_user_api_keys":
   109  	case "fetch_user_api_key":
   110  	case "delete_user_api_key":
   111  	case "add_user_api_key":
   112  	case "test_user_api_key":
   113  	case "fetch_user_ssh_keys":
   114  	case "fetch_user_ssh_key":
   115  	case "delete_user_ssh_key":
   116  	case "test_user_ssh_key":
   117  	case "add_user_ssh_key":
   118  	case "fetch_user_gpg_keys":
   119  	case "fetch_user_gpg_key":
   120  	case "delete_user_gpg_key":
   121  	case "test_user_gpg_key":
   122  	case "add_user_gpg_key":
   123  	case "fetch_user_u2f_reg_params":
   124  	case "fetch_user_u2f_ver_params":
   125  	case "test_user_u2f_reg":
   126  	case "add_user_u2f_token":
   127  	case "fetch_user_info":
   128  	case "update_user_password":
   129  	default:
   130  		resp["message"] = "Profile API received unsupported request type"
   131  		return handleAPIProfileResponse(w, rr, http.StatusBadRequest, resp)
   132  	}
   133  
   134  	// Determine supported authentication methods.
   135  
   136  	switch usr.Authenticator.Method {
   137  	case "local":
   138  	default:
   139  		resp["message"] = fmt.Sprintf("%s is not supported with profile API", usr.Authenticator.Method)
   140  		return handleAPIProfileResponse(w, rr, http.StatusNotImplemented, resp)
   141  	}
   142  
   143  	backend := p.getIdentityStoreByRealm(usr.Authenticator.Realm)
   144  	if backend == nil {
   145  		p.logger.Warn(
   146  			"backend not found",
   147  			zap.String("session_id", rr.Upstream.SessionID),
   148  			zap.String("request_id", rr.ID),
   149  			zap.String("realm", usr.Authenticator.Realm),
   150  			zap.String("jti", usr.Claims.ID),
   151  			zap.String("source_address", addrutil.GetSourceAddress(r)),
   152  		)
   153  		resp["message"] = fmt.Sprintf("backend for %s realm not found", usr.Authenticator.Realm)
   154  		return handleAPIProfileResponse(w, rr, http.StatusBadRequest, resp)
   155  	}
   156  
   157  	p.logger.Debug(
   158  		"backend found for handling api request",
   159  		zap.String("session_id", rr.Upstream.SessionID),
   160  		zap.String("request_id", rr.ID),
   161  		zap.String("realm", usr.Authenticator.Realm),
   162  		zap.String("jti", usr.Claims.ID),
   163  		zap.String("request_kind", reqKind),
   164  		zap.String("source_address", addrutil.GetSourceAddress(r)),
   165  	)
   166  
   167  	// Populate username (sub) and email address (email)
   168  	rr.User.Username = usr.Claims.Subject
   169  	rr.User.Email = usr.Claims.Email
   170  
   171  	switch reqKind {
   172  	case "fetch_debug":
   173  		return p.FetchDebug(ctx, w, r, rr, parsedUser, resp, usr, backend)
   174  	case "fetch_user_dashboard_data":
   175  		return p.FetchUserDashboardData(ctx, w, r, rr, parsedUser, resp, usr, backend)
   176  	case "fetch_user_info":
   177  		return p.FetchUserInfo(ctx, w, r, rr, parsedUser, resp, usr, backend)
   178  	case "update_user_password":
   179  		return p.UpdateUserPassword(ctx, w, r, rr, parsedUser, resp, usr, backend, bodyData)
   180  	case "fetch_user_multi_factor_authenticators":
   181  		return p.FetchUserMultiFactorVerifiers(ctx, w, r, rr, parsedUser, resp, usr, backend)
   182  	case "fetch_user_multi_factor_authenticator":
   183  		return p.FetchUserMultiFactorVerifier(ctx, w, r, rr, parsedUser, resp, usr, backend, bodyData)
   184  	case "delete_user_multi_factor_authenticator":
   185  		return p.DeleteUserMultiFactorVerifier(ctx, w, r, rr, parsedUser, resp, usr, backend, bodyData)
   186  	case "fetch_user_app_multi_factor_authenticator_code":
   187  		return p.FetchUserAppMultiFactorVerifierCode(ctx, w, r, rr, parsedUser, resp, usr, backend, bodyData)
   188  	case "test_user_app_multi_factor_authenticator":
   189  		return p.TestUserAppMultiFactorVerifier(ctx, w, r, rr, parsedUser, resp, usr, backend, bodyData)
   190  	case "add_user_app_multi_factor_authenticator":
   191  		return p.AddUserAppMultiFactorVerifier(ctx, w, r, rr, parsedUser, resp, usr, backend, bodyData)
   192  	case "test_user_webauthn_token":
   193  		return p.TestUserWebAuthnToken(ctx, w, r, rr, parsedUser, resp, usr, backend, bodyData)
   194  	case "test_user_app_token_passcode":
   195  		return p.TestUserAppTokenPasscode(ctx, w, r, rr, parsedUser, resp, usr, backend, bodyData)
   196  	case "fetch_user_u2f_reg_params":
   197  		return p.FetchUserUniSecFactorRegParams(ctx, w, r, rr, parsedUser, resp, usr, backend, bodyData)
   198  	case "fetch_user_u2f_ver_params":
   199  		return p.FetchUserUniSecFactorVerParams(ctx, w, r, rr, parsedUser, resp, usr, backend, bodyData)
   200  	case "test_user_u2f_reg":
   201  		return p.TestUserUniSecFactorReg(ctx, w, r, rr, parsedUser, resp, usr, backend, bodyData)
   202  	case "add_user_u2f_token":
   203  		return p.AddUserUniSecFactorToken(ctx, w, r, rr, parsedUser, resp, usr, backend, bodyData)
   204  	case "fetch_user_api_keys":
   205  		return p.FetchUserAPIKeys(ctx, w, r, rr, parsedUser, resp, usr, backend)
   206  	case "fetch_user_api_key":
   207  		return p.FetchUserAPIKey(ctx, w, r, rr, parsedUser, resp, usr, backend, bodyData)
   208  	case "delete_user_api_key":
   209  		return p.DeleteUserAPIKey(ctx, w, r, rr, parsedUser, resp, usr, backend, bodyData)
   210  	case "add_user_api_key":
   211  		return p.AddUserAPIKey(ctx, w, r, rr, parsedUser, resp, usr, backend, bodyData)
   212  	case "test_user_api_key":
   213  		return p.TestUserAPIKey(ctx, w, r, rr, parsedUser, resp, usr, backend, bodyData)
   214  	case "fetch_user_ssh_keys":
   215  		return p.FetchUserSSHKeys(ctx, w, r, rr, parsedUser, resp, usr, backend)
   216  	case "fetch_user_ssh_key":
   217  		return p.FetchUserSSHKey(ctx, w, r, rr, parsedUser, resp, usr, backend, bodyData)
   218  	case "delete_user_ssh_key":
   219  		return p.DeleteUserSSHKey(ctx, w, r, rr, parsedUser, resp, usr, backend, bodyData)
   220  	case "test_user_ssh_key":
   221  		return p.TestUserSSHKey(ctx, w, r, rr, parsedUser, resp, usr, backend, bodyData)
   222  	case "add_user_ssh_key":
   223  		return p.AddUserSSHKey(ctx, w, r, rr, parsedUser, resp, usr, backend, bodyData)
   224  	case "fetch_user_gpg_keys":
   225  		return p.FetchUserGPGKeys(ctx, w, r, rr, parsedUser, resp, usr, backend)
   226  	case "fetch_user_gpg_key":
   227  		return p.FetchUserGPGKey(ctx, w, r, rr, parsedUser, resp, usr, backend, bodyData)
   228  	case "delete_user_gpg_key":
   229  		return p.DeleteUserGPGKey(ctx, w, r, rr, parsedUser, resp, usr, backend, bodyData)
   230  	case "test_user_gpg_key":
   231  		return p.TestUserGPGKey(ctx, w, r, rr, parsedUser, resp, usr, backend, bodyData)
   232  	case "add_user_gpg_key":
   233  		return p.AddUserGPGKey(ctx, w, r, rr, parsedUser, resp, usr, backend, bodyData)
   234  	}
   235  
   236  	// Default response
   237  	resp["message"] = fmt.Sprintf("unsupported %s request kind with profile API", reqKind)
   238  	return handleAPIProfileResponse(w, rr, http.StatusNotImplemented, resp)
   239  }