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