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 }