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

     1  // Copyright 2024 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  	"net/http"
    21  
    22  	"github.com/greenpau/go-authcrunch/pkg/authn/enums/operator"
    23  	"github.com/greenpau/go-authcrunch/pkg/ids"
    24  	"github.com/greenpau/go-authcrunch/pkg/requests"
    25  	"github.com/greenpau/go-authcrunch/pkg/tagging"
    26  	"github.com/greenpau/go-authcrunch/pkg/user"
    27  )
    28  
    29  // AddUserAppMultiFactorVerifier adds app multi factor authenticator to user identity.
    30  func (p *Portal) AddUserAppMultiFactorVerifier(
    31  	ctx context.Context,
    32  	w http.ResponseWriter,
    33  	r *http.Request,
    34  	rr *requests.Request,
    35  	parsedUser *user.User,
    36  	resp map[string]interface{},
    37  	usr *user.User,
    38  	backend ids.IdentityStore,
    39  	bodyData map[string]interface{}) error {
    40  
    41  	var tokenTitle, tokenDescription, tokenSecret string
    42  	var tokenLifetime, tokenDigits int
    43  	var tokenLabels []string = []string{}
    44  	var tokenTags []tagging.Tag = []tagging.Tag{}
    45  
    46  	// Extract data.
    47  	if v, exists := bodyData["period"]; exists {
    48  		switch exp := v.(type) {
    49  		case float64:
    50  			tokenLifetime = int(exp)
    51  		case int:
    52  			tokenLifetime = exp
    53  		case int64:
    54  			tokenLifetime = int(exp)
    55  		case json.Number:
    56  			i, _ := exp.Int64()
    57  			tokenLifetime = int(i)
    58  		}
    59  	} else {
    60  		resp["message"] = "Profile API did not find period in the request payload"
    61  		return handleAPIProfileResponse(w, rr, http.StatusBadRequest, resp)
    62  	}
    63  	if v, exists := bodyData["digits"]; exists {
    64  		switch exp := v.(type) {
    65  		case float64:
    66  			tokenDigits = int(exp)
    67  		case int:
    68  			tokenDigits = exp
    69  		case int64:
    70  			tokenDigits = int(exp)
    71  		case json.Number:
    72  			i, _ := exp.Int64()
    73  			tokenDigits = int(i)
    74  		}
    75  	} else {
    76  		resp["message"] = "Profile API did not find digits in the request payload"
    77  		return handleAPIProfileResponse(w, rr, http.StatusBadRequest, resp)
    78  	}
    79  	if v, exists := bodyData["title"]; exists {
    80  		tokenTitle = v.(string)
    81  	} else {
    82  		resp["message"] = "Profile API did not find title in the request payload"
    83  		return handleAPIProfileResponse(w, rr, http.StatusBadRequest, resp)
    84  	}
    85  	if v, exists := bodyData["description"]; exists {
    86  		tokenDescription = v.(string)
    87  	} else {
    88  		resp["message"] = "Profile API did not find description in the request payload"
    89  		return handleAPIProfileResponse(w, rr, http.StatusBadRequest, resp)
    90  	}
    91  	if v, exists := bodyData["secret"]; exists {
    92  		tokenSecret = v.(string)
    93  	} else {
    94  		resp["message"] = "Profile API did not find secret in the request payload"
    95  		return handleAPIProfileResponse(w, rr, http.StatusBadRequest, resp)
    96  	}
    97  
    98  	if extractedTokenTags, err := tagging.ExtractTags(bodyData); err == nil {
    99  		for _, extractedTokenTag := range extractedTokenTags {
   100  			tokenTags = append(tokenTags, *extractedTokenTag)
   101  		}
   102  	} else {
   103  		resp["message"] = "Profile API find malformed tags in the request payload"
   104  		return handleAPIProfileResponse(w, rr, http.StatusBadRequest, resp)
   105  	}
   106  
   107  	if extractedTokenLabels, err := tagging.ExtractLabels(bodyData); err == nil {
   108  		tokenLabels = extractedTokenLabels
   109  	} else {
   110  		resp["message"] = "Profile API find malformed tags in the request payload"
   111  		return handleAPIProfileResponse(w, rr, http.StatusBadRequest, resp)
   112  	}
   113  
   114  	// Validate data.
   115  	if !tokenIssuerRegexPattern.MatchString(tokenTitle) {
   116  		resp["message"] = "Profile API found non-compliant token title value"
   117  		return handleAPIProfileResponse(w, rr, http.StatusBadRequest, resp)
   118  	}
   119  	if !tokenDescriptionRegexPattern.MatchString(tokenDescription) && (tokenDescription != "") {
   120  		resp["message"] = "Profile API found non-compliant token description value"
   121  		return handleAPIProfileResponse(w, rr, http.StatusBadRequest, resp)
   122  	}
   123  	if !tokenSecretRegexPattern.MatchString(tokenSecret) {
   124  		resp["message"] = "Profile API found non-compliant token secret value"
   125  		return handleAPIProfileResponse(w, rr, http.StatusBadRequest, resp)
   126  	}
   127  	if tokenLifetime != 15 && tokenLifetime != 30 && tokenLifetime != 60 && tokenLifetime != 90 {
   128  		resp["message"] = "Profile API found non-compliant token lifetime value"
   129  		return handleAPIProfileResponse(w, rr, http.StatusBadRequest, resp)
   130  	}
   131  	if tokenDigits != 4 && tokenDigits != 6 && tokenDigits != 8 {
   132  		resp["message"] = "Profile API found non-compliant token digits value"
   133  		return handleAPIProfileResponse(w, rr, http.StatusBadRequest, resp)
   134  	}
   135  
   136  	rr.MfaToken.Type = "totp"
   137  	rr.MfaToken.SkipVerification = true
   138  	rr.MfaToken.Comment = tokenTitle
   139  	rr.MfaToken.Description = tokenDescription
   140  	rr.MfaToken.Secret = tokenSecret
   141  	rr.MfaToken.Period = tokenLifetime
   142  	rr.MfaToken.Digits = tokenDigits
   143  	rr.MfaToken.Labels = tokenLabels
   144  	rr.MfaToken.Tags = tokenTags
   145  
   146  	if err := backend.Request(operator.AddMfaToken, rr); err != nil {
   147  		resp["message"] = "Profile API failed to add token  to identity store"
   148  		return handleAPIProfileResponse(w, rr, http.StatusBadRequest, resp)
   149  	}
   150  
   151  	resp["entry"] = "Created"
   152  	return handleAPIProfileResponse(w, rr, http.StatusOK, resp)
   153  }