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 := &registerRequest{
    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 := &registerRequest{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 := &registerRequest{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 := &registerRequest{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 := &registerRequest{
   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 := &registerRequest{
   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  }