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 }