github.com/greenpau/go-authcrunch@v1.1.4/pkg/authn/api_test_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 "time" 22 23 "github.com/greenpau/go-authcrunch/pkg/identity" 24 "github.com/greenpau/go-authcrunch/pkg/ids" 25 "github.com/greenpau/go-authcrunch/pkg/requests" 26 "github.com/greenpau/go-authcrunch/pkg/user" 27 "github.com/greenpau/go-authcrunch/pkg/util" 28 ) 29 30 // TestUserAppMultiFactorVerifier tests app multi factor authenticator passcode. 31 func (p *Portal) TestUserAppMultiFactorVerifier( 32 ctx context.Context, 33 w http.ResponseWriter, 34 r *http.Request, 35 rr *requests.Request, 36 parsedUser *user.User, 37 resp map[string]interface{}, 38 usr *user.User, 39 backend ids.IdentityStore, 40 bodyData map[string]interface{}) error { 41 var tokenLifetime, tokenDigits int 42 var tokenSecret, tokenPasscode string 43 44 // Extract data. 45 if v, exists := bodyData["period"]; exists { 46 switch exp := v.(type) { 47 case float64: 48 tokenLifetime = int(exp) 49 case int: 50 tokenLifetime = exp 51 case int64: 52 tokenLifetime = int(exp) 53 case json.Number: 54 i, _ := exp.Int64() 55 tokenLifetime = int(i) 56 } 57 } else { 58 resp["message"] = "Profile API did not find period in the request payload" 59 return handleAPIProfileResponse(w, rr, http.StatusBadRequest, resp) 60 } 61 if v, exists := bodyData["digits"]; exists { 62 switch exp := v.(type) { 63 case float64: 64 tokenDigits = int(exp) 65 case int: 66 tokenDigits = exp 67 case int64: 68 tokenDigits = int(exp) 69 case json.Number: 70 i, _ := exp.Int64() 71 tokenDigits = int(i) 72 } 73 } else { 74 resp["message"] = "Profile API did not find digits in the request payload" 75 return handleAPIProfileResponse(w, rr, http.StatusBadRequest, resp) 76 } 77 if v, exists := bodyData["secret"]; exists { 78 tokenSecret = v.(string) 79 } else { 80 resp["message"] = "Profile API did not find secret in the request payload" 81 return handleAPIProfileResponse(w, rr, http.StatusBadRequest, resp) 82 } 83 if v, exists := bodyData["passcode"]; exists { 84 tokenPasscode = v.(string) 85 } else { 86 resp["message"] = "Profile API did not find passcode in the request payload" 87 return handleAPIProfileResponse(w, rr, http.StatusBadRequest, resp) 88 } 89 90 // Validate data. 91 if !tokenSecretRegexPattern.MatchString(tokenSecret) { 92 resp["message"] = "Profile API found non-compliant token secret value" 93 return handleAPIProfileResponse(w, rr, http.StatusBadRequest, resp) 94 } 95 if !tokenPasscodeRegexPattern.MatchString(tokenPasscode) { 96 resp["message"] = "Profile API found non-compliant token passcode value" 97 return handleAPIProfileResponse(w, rr, http.StatusBadRequest, resp) 98 } 99 if tokenLifetime != 15 && tokenLifetime != 30 && tokenLifetime != 60 && tokenLifetime != 90 { 100 resp["message"] = "Profile API found non-compliant token lifetime value" 101 return handleAPIProfileResponse(w, rr, http.StatusBadRequest, resp) 102 } 103 if tokenDigits != 4 && tokenDigits != 6 && tokenDigits != 8 { 104 resp["message"] = "Profile API found non-compliant token digits value" 105 return handleAPIProfileResponse(w, rr, http.StatusBadRequest, resp) 106 } 107 108 respData := make(map[string]interface{}) 109 appToken := identity.MfaToken{ 110 ID: util.GetRandomString(40), 111 CreatedAt: time.Now().UTC(), 112 Parameters: make(map[string]string), 113 Flags: make(map[string]bool), 114 Comment: "TBD", 115 Type: "totp", 116 Secret: tokenSecret, 117 Algorithm: "sha1", 118 Period: tokenLifetime, 119 Digits: tokenDigits, 120 } 121 if err := appToken.ValidateCodeWithTime(tokenPasscode, time.Now().Add(-time.Second*time.Duration(appToken.Period)).UTC()); err != nil { 122 respData["success"] = false 123 } else { 124 respData["success"] = true 125 } 126 resp["entry"] = respData 127 return handleAPIProfileResponse(w, rr, http.StatusOK, resp) 128 }