github.com/greenpau/go-authcrunch@v1.1.4/pkg/authn/handle_register.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 "net/http" 20 "path" 21 "strings" 22 "time" 23 24 "github.com/greenpau/go-authcrunch/pkg/authn/validators" 25 "github.com/greenpau/go-authcrunch/pkg/requests" 26 "github.com/greenpau/go-authcrunch/pkg/util" 27 addrutil "github.com/greenpau/go-authcrunch/pkg/util/addr" 28 "go.uber.org/zap" 29 ) 30 31 type registerRequest struct { 32 view string 33 message string 34 registrationID string 35 } 36 37 func (p *Portal) handleHTTPRegister(ctx context.Context, w http.ResponseWriter, r *http.Request, rr *requests.Request) error { 38 p.disableClientCache(w) 39 if rr.Response.Authenticated { 40 // Authenticated users are not allowed to register. 41 return p.handleHTTPRedirect(ctx, w, r, rr, "/portal") 42 } 43 44 if strings.Contains(r.URL.Path, "/register/ack/") { 45 if r.Method != "POST" { 46 // Handle registration acknowledgement. 47 return p.handleHTTPRegisterAck(ctx, w, r, rr) 48 } 49 // Handle registration acknowledgement page. 50 return p.handleHTTPRegisterAckRequest(ctx, w, r, rr) 51 } 52 53 if r.Method != "POST" { 54 // Handle registration landing page. 55 return p.handleHTTPRegisterScreen(ctx, w, r, rr) 56 } 57 // Handle registration request. 58 return p.handleHTTPRegisterRequest(ctx, w, r, rr) 59 } 60 61 func (p *Portal) handleHTTPRegisterScreen(ctx context.Context, w http.ResponseWriter, r *http.Request, rr *requests.Request) error { 62 reg := ®isterRequest{ 63 view: "register", 64 } 65 return p.handleHTTPRegisterScreenWithMessage(ctx, w, r, rr, reg) 66 } 67 68 func (p *Portal) handleHTTPRegisterScreenWithMessage(ctx context.Context, w http.ResponseWriter, r *http.Request, rr *requests.Request, reg *registerRequest) error { 69 if len(p.config.UserRegistries) < 1 { 70 return p.handleHTTPError(ctx, w, r, rr, http.StatusServiceUnavailable) 71 } 72 73 resp := p.ui.GetArgs() 74 resp.BaseURL(rr.Upstream.BasePath) 75 resp.Data["view"] = reg.view 76 77 switch reg.view { 78 case "register": 79 resp.PageTitle = p.userRegistry.GetTitle() 80 if p.userRegistry.GetRequireAcceptTerms() { 81 resp.Data["require_accept_terms"] = true 82 } 83 84 if p.userRegistry.GetCode() != "" { 85 resp.Data["require_registration_code"] = true 86 } 87 88 if p.userRegistry.GetTermsConditionsLink() != "" { 89 resp.Data["terms_conditions_link"] = p.userRegistry.GetTermsConditionsLink() 90 } else { 91 resp.Data["terms_conditions_link"] = path.Join(rr.Upstream.BasePath, "/terms-and-conditions") 92 } 93 94 if p.userRegistry.GetPrivacyPolicyLink() != "" { 95 resp.Data["privacy_policy_link"] = p.userRegistry.GetPrivacyPolicyLink() 96 } else { 97 resp.Data["privacy_policy_link"] = path.Join(rr.Upstream.BasePath, "/privacy-policy") 98 } 99 100 resp.Data["username_validate_pattern"] = p.userRegistry.GetUsernamePolicyRegex() 101 resp.Data["username_validate_title"] = p.userRegistry.GetUsernamePolicySummary() 102 resp.Data["password_validate_pattern"] = p.userRegistry.GetPasswordPolicyRegex() 103 resp.Data["password_validate_title"] = p.userRegistry.GetPasswordPolicySummary() 104 if reg.message != "" { 105 resp.Message = reg.message 106 } 107 case "registered": 108 resp.PageTitle = "Thank you!" 109 case "ackfail": 110 resp.PageTitle = "Registration" 111 resp.Data["message"] = reg.message 112 case "ack": 113 resp.PageTitle = "Registration" 114 resp.Data["registration_id"] = reg.registrationID 115 case "acked": 116 resp.PageTitle = "Registration" 117 } 118 119 content, err := p.ui.Render("register", resp) 120 if err != nil { 121 return p.handleHTTPRenderError(ctx, w, r, rr, err) 122 } 123 return p.handleHTTPRenderHTML(ctx, w, http.StatusOK, content.Bytes()) 124 } 125 126 func (p *Portal) handleHTTPRegisterRequest(ctx context.Context, w http.ResponseWriter, r *http.Request, rr *requests.Request) error { 127 var message string 128 var maxBytesLimit int64 = 1000 129 var minBytesLimit int64 = 15 130 var userHandle, userMail, userSecret, userCode string 131 var violations []string 132 var userAccept, validUserRegistration bool 133 validUserRegistration = true 134 135 if r.ContentLength > maxBytesLimit || r.ContentLength < minBytesLimit { 136 violations = append(violations, "payload size") 137 } 138 if r.Header.Get("Content-Type") != "application/x-www-form-urlencoded" { 139 violations = append(violations, "content type") 140 } 141 142 if len(violations) > 0 { 143 message = "Registration request is non compliant" 144 p.logger.Warn( 145 message, 146 zap.String("session_id", rr.Upstream.SessionID), 147 zap.String("request_id", rr.ID), 148 zap.Int64("min_size", minBytesLimit), 149 zap.Int64("max_size", maxBytesLimit), 150 zap.String("content_type", r.Header.Get("Content-Type")), 151 zap.Int64("size", r.ContentLength), 152 zap.Strings("violations", violations), 153 ) 154 reg := ®isterRequest{view: "register", message: message} 155 return p.handleHTTPRegisterScreenWithMessage(ctx, w, r, rr, reg) 156 } 157 158 if err := r.ParseForm(); err != nil { 159 p.logger.Warn( 160 "failed parsing submitted registration form", 161 zap.String("session_id", rr.Upstream.SessionID), 162 zap.String("request_id", rr.ID), 163 zap.String("src_ip", addrutil.GetSourceAddress(r)), 164 zap.String("src_conn_ip", addrutil.GetSourceConnAddress(r)), 165 zap.String("error", err.Error()), 166 ) 167 message = "Failed processing the registration form" 168 validUserRegistration = false 169 } else { 170 for k, v := range r.Form { 171 switch k { 172 case "registrant": 173 userHandle = v[0] 174 case "registrant_password": 175 userSecret = v[0] 176 case "registrant_email": 177 userMail = v[0] 178 case "registrant_code": 179 userCode = v[0] 180 case "accept_terms": 181 if v[0] == "on" { 182 userAccept = true 183 } 184 } 185 } 186 } 187 188 if validUserRegistration { 189 // Inspect registration values. 190 if p.userRegistry.GetCode() != "" { 191 if userCode != p.userRegistry.GetCode() { 192 validUserRegistration = false 193 message = "Failed processing the registration form due to invalid verification code" 194 } 195 } 196 197 if p.userRegistry.GetRequireAcceptTerms() { 198 if !userAccept { 199 validUserRegistration = false 200 message = "Failed processing the registration form due to the failure to accept terms and conditions" 201 } 202 } 203 204 for _, k := range []string{"username", "password", "email"} { 205 if !validUserRegistration { 206 break 207 } 208 switch k { 209 case "username": 210 handleOpts := make(map[string]interface{}) 211 if err := validators.ValidateUserInput("handle", userHandle, handleOpts); err != nil { 212 validUserRegistration = false 213 message = "Failed processing the registration form due " + err.Error() 214 } 215 case "password": 216 secretOpts := make(map[string]interface{}) 217 if err := validators.ValidateUserInput("secret", userSecret, secretOpts); err != nil { 218 validUserRegistration = false 219 message = "Failed processing the registration form due " + err.Error() 220 } 221 case "email": 222 emailOpts := make(map[string]interface{}) 223 if p.userRegistry.GetRequireDomainMailRecord() { 224 emailOpts["check_domain_mx"] = true 225 } 226 if err := validators.ValidateUserInput(k, userMail, emailOpts); err != nil { 227 validUserRegistration = false 228 message = "Failed processing the registration form due " + err.Error() 229 } 230 } 231 } 232 } 233 234 if validUserRegistration { 235 registrationID := util.GetRandomStringFromRange(64, 96) 236 registrationCode := util.GetRandomStringFromRange(6, 8) 237 cachedEntry := map[string]string{ 238 "username": userHandle, 239 "password": userSecret, 240 "email": userMail, 241 "registration_code": registrationCode, 242 } 243 if err := p.userRegistry.AddRegistrationEntry(registrationID, cachedEntry); err != nil { 244 p.logger.Warn( 245 "failed adding a record to registration cache", 246 zap.String("session_id", rr.Upstream.SessionID), 247 zap.String("request_id", rr.ID), 248 zap.Error(err), 249 ) 250 message = "Internal registration error" 251 validUserRegistration = false 252 } else { 253 p.logger.Debug( 254 "Created registration cache entry", 255 zap.String("session_id", rr.Upstream.SessionID), 256 zap.String("request_id", rr.ID), 257 zap.String("registration_id", registrationID), 258 ) 259 260 // Send notification about registration. 261 regData := map[string]string{ 262 "template": "registration_confirmation", 263 "session_id": rr.Upstream.SessionID, 264 "request_id": rr.ID, 265 "registration_id": registrationID, 266 "registration_code": registrationCode, 267 "username": userHandle, 268 "email": userMail, 269 } 270 271 regURL, err := addrutil.GetCurrentURLWithSuffix(r, "/register") 272 if err != nil { 273 p.logger.Warn( 274 "Detected malformed request headers", 275 zap.String("session_id", rr.Upstream.SessionID), 276 zap.String("request_id", rr.ID), 277 zap.Error(err), 278 ) 279 } 280 regData["registration_url"] = regURL 281 282 regData["src_ip"] = addrutil.GetSourceAddress(r) 283 regData["src_conn_ip"] = addrutil.GetSourceConnAddress(r) 284 regData["timestamp"] = time.Now().UTC().Format(time.UnixDate) 285 if err := p.userRegistry.Notify(regData); err != nil { 286 p.logger.Warn( 287 "Failed to send notification", 288 zap.String("session_id", rr.Upstream.SessionID), 289 zap.String("request_id", rr.ID), 290 zap.String("registration_id", registrationID), 291 zap.String("registration_type", "registration_confirmation"), 292 zap.Error(err), 293 ) 294 p.userRegistry.DeleteRegistrationEntry(registrationID) 295 message = "Internal registration messaging error" 296 validUserRegistration = false 297 } 298 } 299 } 300 301 if !validUserRegistration { 302 p.logger.Warn( 303 "failed registration", 304 zap.String("session_id", rr.Upstream.SessionID), 305 zap.String("request_id", rr.ID), 306 zap.String("src_ip", addrutil.GetSourceAddress(r)), 307 zap.String("src_conn_ip", addrutil.GetSourceConnAddress(r)), 308 zap.String("error", message), 309 ) 310 reg := ®isterRequest{view: "register", message: message} 311 return p.handleHTTPRegisterScreenWithMessage(ctx, w, r, rr, reg) 312 } 313 314 p.logger.Info("Successful user registration", 315 zap.String("session_id", rr.Upstream.SessionID), 316 zap.String("request_id", rr.ID), 317 zap.String("username", userHandle), 318 zap.String("email", userMail), 319 zap.String("src_ip", addrutil.GetSourceAddress(r)), 320 zap.String("src_conn_ip", addrutil.GetSourceConnAddress(r)), 321 ) 322 reg := ®isterRequest{view: "registered"} 323 return p.handleHTTPRegisterScreenWithMessage(ctx, w, r, rr, reg) 324 } 325 326 func (p *Portal) handleHTTPRegisterAck(ctx context.Context, w http.ResponseWriter, r *http.Request, rr *requests.Request) error { 327 reg := ®isterRequest{ 328 view: "ackfail", 329 } 330 registrationID, err := getEndpointKeyID(r.URL.Path, "/register/ack/") 331 if err != nil { 332 reg.message = "Malformed registration acknowledgement request" 333 return p.handleHTTPRegisterScreenWithMessage(ctx, w, r, rr, reg) 334 } 335 336 if _, err := p.userRegistry.GetRegistrationEntry(registrationID); err != nil { 337 reg.message = "Registration identifier not found" 338 return p.handleHTTPRegisterScreenWithMessage(ctx, w, r, rr, reg) 339 } 340 341 reg.view = "ack" 342 reg.registrationID = registrationID 343 344 return p.handleHTTPRegisterScreenWithMessage(ctx, w, r, rr, reg) 345 } 346 347 func (p *Portal) handleHTTPRegisterAckRequest(ctx context.Context, w http.ResponseWriter, r *http.Request, rr *requests.Request) error { 348 reg := ®isterRequest{ 349 view: "ackfail", 350 } 351 352 if err := r.ParseForm(); err != nil { 353 p.logger.Warn( 354 "failed parsing registration acknowledgement form", 355 zap.String("session_id", rr.Upstream.SessionID), 356 zap.String("request_id", rr.ID), 357 zap.String("src_ip", addrutil.GetSourceAddress(r)), 358 zap.String("src_conn_ip", addrutil.GetSourceConnAddress(r)), 359 zap.String("error", err.Error()), 360 ) 361 reg.message = "Failed processing the registration acknowledgement form" 362 return p.handleHTTPRegisterScreenWithMessage(ctx, w, r, rr, reg) 363 } 364 365 registrationCode := strings.TrimSpace(r.FormValue("registration_code")) 366 367 registrationID, err := getEndpointKeyID(r.URL.Path, "/register/ack/") 368 if err != nil { 369 reg.message = "Malformed registration acknowledgement request" 370 return p.handleHTTPRegisterScreenWithMessage(ctx, w, r, rr, reg) 371 } 372 373 usr, err := p.userRegistry.GetRegistrationEntry(registrationID) 374 if err != nil { 375 reg.message = "Registration identifier not found" 376 return p.handleHTTPRegisterScreenWithMessage(ctx, w, r, rr, reg) 377 } 378 379 if usr["registration_code"] != registrationCode { 380 p.logger.Warn( 381 "failed registration acknowledgement due to registration code mismatch", 382 zap.String("session_id", rr.Upstream.SessionID), 383 zap.String("request_id", rr.ID), 384 zap.String("src_ip", addrutil.GetSourceAddress(r)), 385 zap.String("src_conn_ip", addrutil.GetSourceConnAddress(r)), 386 ) 387 reg.message = "Registration identifier mismatch" 388 return p.handleHTTPRegisterScreenWithMessage(ctx, w, r, rr, reg) 389 } 390 391 // Build registration commit request. 392 req := &requests.Request{ 393 User: requests.User{ 394 Username: usr["username"], 395 Password: usr["password"], 396 Email: usr["email"], 397 Roles: []string{defaultUserRoleName}, 398 }, 399 Query: requests.Query{ 400 ID: registrationID, 401 }, 402 } 403 404 if err := p.userRegistry.DeleteRegistrationEntry(registrationID); err != nil { 405 reg.message = "Registration session terminated" 406 return p.handleHTTPRegisterScreenWithMessage(ctx, w, r, rr, reg) 407 } 408 409 if err := p.userRegistry.AddUser(req); err != nil { 410 p.logger.Warn( 411 "registration request backend erred", 412 zap.String("session_id", rr.Upstream.SessionID), 413 zap.String("request_id", rr.ID), 414 zap.Error(err), 415 ) 416 reg.message = "Registration session is no longer valid" 417 return p.handleHTTPRegisterScreenWithMessage(ctx, w, r, rr, reg) 418 } 419 420 // Send a notification to admins. 421 regData := map[string]string{ 422 "template": "registration_ready", 423 "session_id": rr.Upstream.SessionID, 424 "request_id": rr.ID, 425 "registration_id": registrationID, 426 "username": req.User.Username, 427 "email": req.User.Email, 428 } 429 430 regURL, err := addrutil.GetCurrentURLWithSuffix(r, "/register") 431 if err != nil { 432 p.logger.Warn( 433 "Detected malformed request headers", 434 zap.String("session_id", rr.Upstream.SessionID), 435 zap.String("request_id", rr.ID), 436 zap.Error(err), 437 ) 438 } 439 regData["registration_url"] = regURL 440 441 regData["src_ip"] = addrutil.GetSourceAddress(r) 442 regData["src_conn_ip"] = addrutil.GetSourceConnAddress(r) 443 regData["timestamp"] = time.Now().UTC().Format(time.UnixDate) 444 445 if err := p.userRegistry.Notify(regData); err != nil { 446 p.logger.Warn( 447 "Failed to send notification", 448 zap.String("session_id", rr.Upstream.SessionID), 449 zap.String("request_id", rr.ID), 450 zap.String("registration_id", registrationID), 451 zap.String("registration_type", "registration_ready"), 452 zap.Error(err), 453 ) 454 } 455 456 reg.view = "acked" 457 return p.handleHTTPRegisterScreenWithMessage(ctx, w, r, rr, reg) 458 }