github.com/volatiletech/authboss@v2.4.1+incompatible/defaults/values.go (about)

     1  package defaults
     2  
     3  import (
     4  	"encoding/json"
     5  	"io/ioutil"
     6  	"net/http"
     7  	"net/url"
     8  	"regexp"
     9  
    10  	"github.com/pkg/errors"
    11  	"github.com/volatiletech/authboss"
    12  )
    13  
    14  // FormValue types
    15  const (
    16  	FormValueEmail    = "email"
    17  	FormValuePassword = "password"
    18  	FormValueUsername = "username"
    19  
    20  	FormValueConfirm      = "cnf"
    21  	FormValueToken        = "token"
    22  	FormValueCode         = "code"
    23  	FormValueRecoveryCode = "recovery_code"
    24  	FormValuePhoneNumber  = "phone_number"
    25  )
    26  
    27  // UserValues from the login form
    28  type UserValues struct {
    29  	HTTPFormValidator
    30  
    31  	PID      string
    32  	Password string
    33  
    34  	Arbitrary map[string]string
    35  }
    36  
    37  // GetPID from the values
    38  func (u UserValues) GetPID() string {
    39  	return u.PID
    40  }
    41  
    42  // GetPassword from the values
    43  func (u UserValues) GetPassword() string {
    44  	return u.Password
    45  }
    46  
    47  // GetValues from the form.
    48  func (u UserValues) GetValues() map[string]string {
    49  	return u.Arbitrary
    50  }
    51  
    52  // GetShouldRemember checks the form values for
    53  func (u UserValues) GetShouldRemember() bool {
    54  	rm, ok := u.Values[authboss.CookieRemember]
    55  	return ok && rm == "true"
    56  }
    57  
    58  // ConfirmValues retrieves values on the confirm page.
    59  type ConfirmValues struct {
    60  	HTTPFormValidator
    61  
    62  	Token string
    63  }
    64  
    65  // GetToken from the confirm values
    66  func (c ConfirmValues) GetToken() string {
    67  	return c.Token
    68  }
    69  
    70  // RecoverStartValues for recover_start page
    71  type RecoverStartValues struct {
    72  	HTTPFormValidator
    73  
    74  	PID string
    75  }
    76  
    77  // GetPID for recovery
    78  func (r RecoverStartValues) GetPID() string { return r.PID }
    79  
    80  // RecoverMiddleValues for recover_middle page
    81  type RecoverMiddleValues struct {
    82  	HTTPFormValidator
    83  
    84  	Token string
    85  }
    86  
    87  // GetToken for recovery
    88  func (r RecoverMiddleValues) GetToken() string { return r.Token }
    89  
    90  // RecoverEndValues for recover_end page
    91  type RecoverEndValues struct {
    92  	HTTPFormValidator
    93  
    94  	Token       string
    95  	NewPassword string
    96  }
    97  
    98  // GetToken for recovery
    99  func (r RecoverEndValues) GetToken() string { return r.Token }
   100  
   101  // GetPassword for recovery
   102  func (r RecoverEndValues) GetPassword() string { return r.NewPassword }
   103  
   104  // TwoFA for totp2fa_validate page
   105  type TwoFA struct {
   106  	HTTPFormValidator
   107  
   108  	Code         string
   109  	RecoveryCode string
   110  }
   111  
   112  // GetCode from authenticator
   113  func (t TwoFA) GetCode() string { return t.Code }
   114  
   115  // GetRecoveryCode for authenticator
   116  func (t TwoFA) GetRecoveryCode() string { return t.RecoveryCode }
   117  
   118  // SMSTwoFA for sms2fa_validate page
   119  type SMSTwoFA struct {
   120  	HTTPFormValidator
   121  
   122  	Code         string
   123  	RecoveryCode string
   124  	PhoneNumber  string
   125  }
   126  
   127  // GetCode from sms
   128  func (s SMSTwoFA) GetCode() string { return s.Code }
   129  
   130  // GetRecoveryCode from sms
   131  func (s SMSTwoFA) GetRecoveryCode() string { return s.RecoveryCode }
   132  
   133  // GetPhoneNumber from authenticator
   134  func (s SMSTwoFA) GetPhoneNumber() string { return s.PhoneNumber }
   135  
   136  // HTTPBodyReader reads forms from various pages and decodes
   137  // them.
   138  type HTTPBodyReader struct {
   139  	// ReadJSON if turned on reads json from the http request
   140  	// instead of a encoded form.
   141  	ReadJSON bool
   142  
   143  	// UseUsername instead of e-mail address
   144  	UseUsername bool
   145  
   146  	// Rulesets for each page.
   147  	Rulesets map[string][]Rules
   148  	// Confirm fields for each page.
   149  	Confirms map[string][]string
   150  	// Whitelist values for each page through the html forms
   151  	// this is for security so that we can properly protect the
   152  	// arbitrary user API. In reality this really only needs to be set
   153  	// for the register page since everything else is expecting
   154  	// a hardcoded set of values.
   155  	Whitelist map[string][]string
   156  }
   157  
   158  // NewHTTPBodyReader creates a form reader with default validation rules
   159  // and fields for each page. If no defaults are required, simply construct
   160  // this using the struct members itself for more control.
   161  func NewHTTPBodyReader(readJSON, useUsernameNotEmail bool) *HTTPBodyReader {
   162  	var pid string
   163  	var pidRules Rules
   164  
   165  	if useUsernameNotEmail {
   166  		pid = "username"
   167  		pidRules = Rules{
   168  			FieldName: pid, Required: true,
   169  			MatchError: "Usernames must only start with letters, and contain letters and numbers",
   170  			MustMatch:  regexp.MustCompile(`(?i)[a-z][a-z0-9]?`),
   171  		}
   172  	} else {
   173  		pid = "email"
   174  		pidRules = Rules{
   175  			FieldName: pid, Required: true,
   176  			MatchError: "Must be a valid e-mail address",
   177  			MustMatch:  regexp.MustCompile(`.*@.*\.[a-z]+`),
   178  		}
   179  	}
   180  
   181  	passwordRule := Rules{
   182  		FieldName:  "password",
   183  		MinLength:  8,
   184  		MinNumeric: 1,
   185  		MinSymbols: 1,
   186  		MinUpper:   1,
   187  		MinLower:   1,
   188  	}
   189  
   190  	return &HTTPBodyReader{
   191  		UseUsername: useUsernameNotEmail,
   192  		ReadJSON:    readJSON,
   193  		Rulesets: map[string][]Rules{
   194  			"login":         {pidRules},
   195  			"register":      {pidRules, passwordRule},
   196  			"confirm":       {Rules{FieldName: FormValueConfirm, Required: true}},
   197  			"recover_start": {pidRules},
   198  			"recover_end":   {passwordRule},
   199  
   200  			"twofactor_verify_end": {Rules{FieldName: FormValueToken, Required: true}},
   201  		},
   202  		Confirms: map[string][]string{
   203  			"register":    {FormValuePassword, authboss.ConfirmPrefix + FormValuePassword},
   204  			"recover_end": {FormValuePassword, authboss.ConfirmPrefix + FormValuePassword},
   205  		},
   206  		Whitelist: map[string][]string{
   207  			"register": {FormValueEmail, FormValuePassword},
   208  		},
   209  	}
   210  }
   211  
   212  // Read the form pages
   213  func (h HTTPBodyReader) Read(page string, r *http.Request) (authboss.Validator, error) {
   214  	var values map[string]string
   215  
   216  	if h.ReadJSON {
   217  		b, err := ioutil.ReadAll(r.Body)
   218  		r.Body.Close()
   219  		if err != nil {
   220  			return nil, errors.Wrap(err, "failed to read http body")
   221  		}
   222  
   223  		if err = json.Unmarshal(b, &values); err != nil {
   224  			return nil, errors.Wrap(err, "failed to parse json http body")
   225  		}
   226  	} else {
   227  		if err := r.ParseForm(); err != nil {
   228  			return nil, errors.Wrapf(err, "failed to parse form on page: %s", page)
   229  		}
   230  		values = URLValuesToMap(r.Form)
   231  	}
   232  
   233  	rules := h.Rulesets[page]
   234  	confirms := h.Confirms[page]
   235  	whitelist := h.Whitelist[page]
   236  
   237  	switch page {
   238  	case "confirm":
   239  		return ConfirmValues{
   240  			HTTPFormValidator: HTTPFormValidator{Values: values, Ruleset: rules},
   241  			Token:             values[FormValueConfirm],
   242  		}, nil
   243  	case "login":
   244  		var pid string
   245  		if h.UseUsername {
   246  			pid = values[FormValueUsername]
   247  		} else {
   248  			pid = values[FormValueEmail]
   249  		}
   250  
   251  		return UserValues{
   252  			HTTPFormValidator: HTTPFormValidator{Values: values, Ruleset: rules, ConfirmFields: confirms},
   253  			PID:               pid,
   254  			Password:          values[FormValuePassword],
   255  		}, nil
   256  	case "recover_start":
   257  		var pid string
   258  		if h.UseUsername {
   259  			pid = values[FormValueUsername]
   260  		} else {
   261  			pid = values[FormValueEmail]
   262  		}
   263  
   264  		return RecoverStartValues{
   265  			HTTPFormValidator: HTTPFormValidator{Values: values, Ruleset: rules, ConfirmFields: confirms},
   266  			PID:               pid,
   267  		}, nil
   268  	case "recover_middle":
   269  		return RecoverMiddleValues{
   270  			HTTPFormValidator: HTTPFormValidator{Values: values, Ruleset: rules, ConfirmFields: confirms},
   271  			Token:             values[FormValueToken],
   272  		}, nil
   273  	case "recover_end":
   274  		return RecoverEndValues{
   275  			HTTPFormValidator: HTTPFormValidator{Values: values, Ruleset: rules, ConfirmFields: confirms},
   276  			Token:             values[FormValueToken],
   277  			NewPassword:       values[FormValuePassword],
   278  		}, nil
   279  	case "twofactor_verify_end":
   280  		// Reuse ConfirmValues here, it's the same values we need
   281  		return ConfirmValues{
   282  			HTTPFormValidator: HTTPFormValidator{Values: values, Ruleset: rules, ConfirmFields: confirms},
   283  			Token:             values[FormValueToken],
   284  		}, nil
   285  	case "totp2fa_confirm", "totp2fa_remove", "totp2fa_validate":
   286  		return TwoFA{
   287  			HTTPFormValidator: HTTPFormValidator{Values: values, Ruleset: rules, ConfirmFields: confirms},
   288  			Code:              values[FormValueCode],
   289  			RecoveryCode:      values[FormValueRecoveryCode],
   290  		}, nil
   291  	case "sms2fa_setup", "sms2fa_remove", "sms2fa_confirm", "sms2fa_validate":
   292  		return SMSTwoFA{
   293  			HTTPFormValidator: HTTPFormValidator{Values: values, Ruleset: rules, ConfirmFields: confirms},
   294  			Code:              values[FormValueCode],
   295  			PhoneNumber:       values[FormValuePhoneNumber],
   296  			RecoveryCode:      values[FormValueRecoveryCode],
   297  		}, nil
   298  	case "register":
   299  		arbitrary := make(map[string]string)
   300  
   301  		for k, v := range values {
   302  			for _, w := range whitelist {
   303  				if k == w {
   304  					arbitrary[k] = v
   305  					break
   306  				}
   307  			}
   308  		}
   309  
   310  		var pid string
   311  		if h.UseUsername {
   312  			pid = values[FormValueUsername]
   313  		} else {
   314  			pid = values[FormValueEmail]
   315  		}
   316  
   317  		return UserValues{
   318  			HTTPFormValidator: HTTPFormValidator{Values: values, Ruleset: rules, ConfirmFields: confirms},
   319  			PID:               pid,
   320  			Password:          values[FormValuePassword],
   321  			Arbitrary:         arbitrary,
   322  		}, nil
   323  	default:
   324  		return nil, errors.Errorf("failed to parse unknown page's form: %s", page)
   325  	}
   326  }
   327  
   328  // URLValuesToMap helps create a map from url.Values
   329  func URLValuesToMap(form url.Values) map[string]string {
   330  	values := make(map[string]string)
   331  
   332  	for k, v := range form {
   333  		if len(v) != 0 {
   334  			values[k] = v[0]
   335  		}
   336  	}
   337  
   338  	return values
   339  }