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