2018-02-05 04:30:39 +02:00
|
|
|
package defaults
|
|
|
|
|
|
|
|
import (
|
2018-05-09 16:59:19 +02:00
|
|
|
"encoding/json"
|
|
|
|
"io/ioutil"
|
2018-02-05 04:30:39 +02:00
|
|
|
"net/http"
|
|
|
|
"net/url"
|
|
|
|
"regexp"
|
|
|
|
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/volatiletech/authboss"
|
|
|
|
)
|
|
|
|
|
|
|
|
// FormValue types
|
|
|
|
const (
|
|
|
|
FormValueEmail = "email"
|
|
|
|
FormValuePassword = "password"
|
|
|
|
FormValueUsername = "username"
|
2018-05-01 03:18:30 +02:00
|
|
|
|
2018-08-27 00:43:35 +02:00
|
|
|
FormValueConfirm = "cnf"
|
|
|
|
FormValueToken = "token"
|
|
|
|
FormValueCode = "code"
|
|
|
|
FormValueRecoveryCode = "recovery_code"
|
|
|
|
FormValuePhoneNumber = "phone_number"
|
2018-02-05 04:30:39 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
// UserValues from the login form
|
|
|
|
type UserValues struct {
|
|
|
|
HTTPFormValidator
|
|
|
|
|
|
|
|
PID string
|
|
|
|
Password string
|
2018-05-01 03:18:30 +02:00
|
|
|
|
|
|
|
Arbitrary map[string]string
|
2018-02-05 04:30:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// GetPID from the values
|
|
|
|
func (u UserValues) GetPID() string {
|
|
|
|
return u.PID
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetPassword from the values
|
|
|
|
func (u UserValues) GetPassword() string {
|
|
|
|
return u.Password
|
|
|
|
}
|
|
|
|
|
2018-05-01 03:18:30 +02:00
|
|
|
// GetValues from the form.
|
|
|
|
func (u UserValues) GetValues() map[string]string {
|
|
|
|
return u.Arbitrary
|
|
|
|
}
|
|
|
|
|
|
|
|
// ConfirmValues retrieves values on the confirm page.
|
|
|
|
type ConfirmValues struct {
|
|
|
|
HTTPFormValidator
|
|
|
|
|
|
|
|
Token string
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetToken from the confirm values
|
|
|
|
func (c ConfirmValues) GetToken() string {
|
|
|
|
return c.Token
|
|
|
|
}
|
|
|
|
|
|
|
|
// RecoverStartValues for recover_start page
|
|
|
|
type RecoverStartValues struct {
|
|
|
|
HTTPFormValidator
|
|
|
|
|
|
|
|
PID string
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetPID for recovery
|
|
|
|
func (r RecoverStartValues) GetPID() string { return r.PID }
|
|
|
|
|
|
|
|
// RecoverMiddleValues for recover_middle page
|
|
|
|
type RecoverMiddleValues struct {
|
|
|
|
HTTPFormValidator
|
|
|
|
|
|
|
|
Token string
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetToken for recovery
|
|
|
|
func (r RecoverMiddleValues) GetToken() string { return r.Token }
|
|
|
|
|
|
|
|
// RecoverEndValues for recover_end page
|
|
|
|
type RecoverEndValues struct {
|
|
|
|
HTTPFormValidator
|
|
|
|
|
|
|
|
Token string
|
|
|
|
NewPassword string
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetToken for recovery
|
|
|
|
func (r RecoverEndValues) GetToken() string { return r.Token }
|
|
|
|
|
|
|
|
// GetPassword for recovery
|
|
|
|
func (r RecoverEndValues) GetPassword() string { return r.NewPassword }
|
|
|
|
|
2018-08-23 06:34:38 +02:00
|
|
|
// TwoFA for totp2fa_validate page
|
|
|
|
type TwoFA struct {
|
|
|
|
HTTPFormValidator
|
|
|
|
|
2018-08-27 00:43:35 +02:00
|
|
|
Code string
|
|
|
|
RecoveryCode string
|
2018-08-23 06:34:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// GetCode from authenticator
|
2018-08-26 21:54:14 +02:00
|
|
|
func (t TwoFA) GetCode() string { return t.Code }
|
|
|
|
|
2018-08-27 00:43:35 +02:00
|
|
|
// GetRecoveryCode for authenticator
|
|
|
|
func (t TwoFA) GetRecoveryCode() string { return t.RecoveryCode }
|
|
|
|
|
2018-08-26 21:54:14 +02:00
|
|
|
// SMSTwoFA for sms2fa_validate page
|
|
|
|
type SMSTwoFA struct {
|
|
|
|
HTTPFormValidator
|
|
|
|
|
2018-08-27 00:43:35 +02:00
|
|
|
Code string
|
|
|
|
RecoveryCode string
|
|
|
|
PhoneNumber string
|
2018-08-26 21:54:14 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// GetCode from sms
|
|
|
|
func (s SMSTwoFA) GetCode() string { return s.Code }
|
|
|
|
|
2018-08-27 00:43:35 +02:00
|
|
|
// GetRecoveryCode from sms
|
|
|
|
func (s SMSTwoFA) GetRecoveryCode() string { return s.RecoveryCode }
|
|
|
|
|
2018-08-26 21:54:14 +02:00
|
|
|
// GetPhoneNumber from authenticator
|
|
|
|
func (s SMSTwoFA) GetPhoneNumber() string { return s.PhoneNumber }
|
2018-08-23 06:34:38 +02:00
|
|
|
|
2018-05-09 16:59:19 +02:00
|
|
|
// HTTPBodyReader reads forms from various pages and decodes
|
2018-02-05 04:30:39 +02:00
|
|
|
// them.
|
2018-05-09 16:59:19 +02:00
|
|
|
type HTTPBodyReader struct {
|
|
|
|
// ReadJSON if turned on reads json from the http request
|
|
|
|
// instead of a encoded form.
|
|
|
|
ReadJSON bool
|
|
|
|
|
2018-05-01 03:18:30 +02:00
|
|
|
// UseUsername instead of e-mail address
|
2018-02-05 04:30:39 +02:00
|
|
|
UseUsername bool
|
2018-05-01 03:18:30 +02:00
|
|
|
|
|
|
|
// Rulesets for each page.
|
|
|
|
Rulesets map[string][]Rules
|
|
|
|
// Confirm fields for each page.
|
|
|
|
Confirms map[string][]string
|
|
|
|
// Whitelist values for each page through the html forms
|
|
|
|
// this is for security so that we can properly protect the
|
|
|
|
// arbitrary user API. In reality this really only needs to be set
|
|
|
|
// for the register page since everything else is expecting
|
|
|
|
// a hardcoded set of values.
|
|
|
|
Whitelist map[string][]string
|
2018-02-05 04:30:39 +02:00
|
|
|
}
|
|
|
|
|
2018-05-09 16:59:19 +02:00
|
|
|
// NewHTTPBodyReader creates a form reader with default validation rules
|
2018-05-01 03:18:30 +02:00
|
|
|
// and fields for each page. If no defaults are required, simply construct
|
|
|
|
// this using the struct members itself for more control.
|
2018-05-09 16:59:19 +02:00
|
|
|
func NewHTTPBodyReader(readJSON, useUsernameNotEmail bool) *HTTPBodyReader {
|
2018-02-05 04:30:39 +02:00
|
|
|
var pid string
|
|
|
|
var pidRules Rules
|
|
|
|
|
|
|
|
if useUsernameNotEmail {
|
|
|
|
pid = "username"
|
|
|
|
pidRules = Rules{
|
|
|
|
FieldName: pid, Required: true,
|
|
|
|
MatchError: "Usernames must only start with letters, and contain letters and numbers",
|
|
|
|
MustMatch: regexp.MustCompile(`(?i)[a-z][a-z0-9]?`),
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
pid = "email"
|
|
|
|
pidRules = Rules{
|
|
|
|
FieldName: pid, Required: true,
|
|
|
|
MatchError: "Must be a valid e-mail address",
|
|
|
|
MustMatch: regexp.MustCompile(`.*@.*\.[a-z]{1,}`),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
passwordRule := Rules{
|
|
|
|
FieldName: "password",
|
|
|
|
MinLength: 8,
|
|
|
|
MinNumeric: 1,
|
|
|
|
MinSymbols: 1,
|
|
|
|
MinUpper: 1,
|
|
|
|
MinLower: 1,
|
|
|
|
}
|
|
|
|
|
2018-05-09 16:59:19 +02:00
|
|
|
return &HTTPBodyReader{
|
|
|
|
UseUsername: useUsernameNotEmail,
|
|
|
|
ReadJSON: readJSON,
|
2018-02-05 04:30:39 +02:00
|
|
|
Rulesets: map[string][]Rules{
|
2018-05-01 03:18:30 +02:00
|
|
|
"login": {pidRules},
|
|
|
|
"register": {pidRules, passwordRule},
|
|
|
|
"confirm": {Rules{FieldName: FormValueConfirm, Required: true}},
|
|
|
|
"recover_start": {pidRules},
|
|
|
|
"recover_end": {passwordRule},
|
2018-11-02 07:44:52 +02:00
|
|
|
|
|
|
|
"twofactor_verify_end": {Rules{FieldName: FormValueToken, Required: true}},
|
2018-05-01 03:18:30 +02:00
|
|
|
},
|
|
|
|
Confirms: map[string][]string{
|
|
|
|
"register": {FormValuePassword, authboss.ConfirmPrefix + FormValuePassword},
|
|
|
|
"recover_end": {FormValuePassword, authboss.ConfirmPrefix + FormValuePassword},
|
|
|
|
},
|
|
|
|
Whitelist: map[string][]string{
|
|
|
|
"register": []string{FormValueEmail, FormValuePassword},
|
2018-02-05 04:30:39 +02:00
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read the form pages
|
2018-05-09 16:59:19 +02:00
|
|
|
func (h HTTPBodyReader) Read(page string, r *http.Request) (authboss.Validator, error) {
|
|
|
|
var values map[string]string
|
|
|
|
|
|
|
|
if h.ReadJSON {
|
|
|
|
b, err := ioutil.ReadAll(r.Body)
|
|
|
|
r.Body.Close()
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "failed to read http body")
|
|
|
|
}
|
|
|
|
|
|
|
|
if err = json.Unmarshal(b, &values); err != nil {
|
|
|
|
return nil, errors.Wrap(err, "failed to parse json http body")
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if err := r.ParseForm(); err != nil {
|
|
|
|
return nil, errors.Wrapf(err, "failed to parse form on page: %s", page)
|
|
|
|
}
|
|
|
|
values = URLValuesToMap(r.Form)
|
2018-02-05 04:30:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
rules := h.Rulesets[page]
|
2018-05-01 03:18:30 +02:00
|
|
|
confirms := h.Confirms[page]
|
|
|
|
whitelist := h.Whitelist[page]
|
2018-02-05 04:30:39 +02:00
|
|
|
|
|
|
|
switch page {
|
2018-05-01 03:18:30 +02:00
|
|
|
case "confirm":
|
|
|
|
return ConfirmValues{
|
|
|
|
HTTPFormValidator: HTTPFormValidator{Values: values, Ruleset: rules},
|
|
|
|
Token: values[FormValueConfirm],
|
|
|
|
}, nil
|
2018-02-05 04:30:39 +02:00
|
|
|
case "login":
|
|
|
|
var pid string
|
|
|
|
if h.UseUsername {
|
|
|
|
pid = values[FormValueUsername]
|
|
|
|
} else {
|
|
|
|
pid = values[FormValueEmail]
|
|
|
|
}
|
|
|
|
|
2018-05-01 03:18:30 +02:00
|
|
|
return UserValues{
|
|
|
|
HTTPFormValidator: HTTPFormValidator{Values: values, Ruleset: rules, ConfirmFields: confirms},
|
|
|
|
PID: pid,
|
|
|
|
Password: values[FormValuePassword],
|
|
|
|
}, nil
|
|
|
|
case "recover_start":
|
|
|
|
var pid string
|
|
|
|
if h.UseUsername {
|
|
|
|
pid = values[FormValueUsername]
|
|
|
|
} else {
|
|
|
|
pid = values[FormValueEmail]
|
|
|
|
}
|
|
|
|
|
|
|
|
return RecoverStartValues{
|
|
|
|
HTTPFormValidator: HTTPFormValidator{Values: values, Ruleset: rules, ConfirmFields: confirms},
|
|
|
|
PID: pid,
|
|
|
|
}, nil
|
|
|
|
case "recover_middle":
|
|
|
|
return RecoverMiddleValues{
|
|
|
|
HTTPFormValidator: HTTPFormValidator{Values: values, Ruleset: rules, ConfirmFields: confirms},
|
|
|
|
Token: values[FormValueToken],
|
|
|
|
}, nil
|
|
|
|
case "recover_end":
|
|
|
|
return RecoverEndValues{
|
|
|
|
HTTPFormValidator: HTTPFormValidator{Values: values, Ruleset: rules, ConfirmFields: confirms},
|
|
|
|
Token: values[FormValueToken],
|
|
|
|
NewPassword: values[FormValuePassword],
|
|
|
|
}, nil
|
2018-11-02 07:44:52 +02:00
|
|
|
case "twofactor_verify_end":
|
|
|
|
// Reuse ConfirmValues here, it's the same values we need
|
|
|
|
return ConfirmValues{
|
|
|
|
HTTPFormValidator: HTTPFormValidator{Values: values, Ruleset: rules, ConfirmFields: confirms},
|
|
|
|
Token: values[FormValueToken],
|
|
|
|
}, nil
|
2018-09-03 20:57:25 +02:00
|
|
|
case "totp2fa_confirm", "totp2fa_remove", "totp2fa_validate":
|
2018-08-23 06:34:38 +02:00
|
|
|
return TwoFA{
|
|
|
|
HTTPFormValidator: HTTPFormValidator{Values: values, Ruleset: rules, ConfirmFields: confirms},
|
|
|
|
Code: values[FormValueCode],
|
2018-08-27 00:43:35 +02:00
|
|
|
RecoveryCode: values[FormValueRecoveryCode],
|
2018-08-23 06:34:38 +02:00
|
|
|
}, nil
|
2018-09-03 20:57:25 +02:00
|
|
|
case "sms2fa_setup", "sms2fa_remove", "sms2fa_confirm", "sms2fa_validate":
|
2018-08-26 21:54:14 +02:00
|
|
|
return SMSTwoFA{
|
|
|
|
HTTPFormValidator: HTTPFormValidator{Values: values, Ruleset: rules, ConfirmFields: confirms},
|
|
|
|
Code: values[FormValueCode],
|
|
|
|
PhoneNumber: values[FormValuePhoneNumber],
|
2018-08-27 00:43:35 +02:00
|
|
|
RecoveryCode: values[FormValueRecoveryCode],
|
2018-08-26 21:54:14 +02:00
|
|
|
}, nil
|
2018-05-01 03:18:30 +02:00
|
|
|
case "register":
|
|
|
|
arbitrary := make(map[string]string)
|
|
|
|
|
|
|
|
for k, v := range values {
|
|
|
|
for _, w := range whitelist {
|
|
|
|
if k == w {
|
|
|
|
arbitrary[k] = v
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var pid string
|
|
|
|
if h.UseUsername {
|
|
|
|
pid = values[FormValueUsername]
|
|
|
|
} else {
|
|
|
|
pid = values[FormValueEmail]
|
2018-02-05 04:30:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return UserValues{
|
2018-05-01 03:18:30 +02:00
|
|
|
HTTPFormValidator: HTTPFormValidator{Values: values, Ruleset: rules, ConfirmFields: confirms},
|
2018-02-05 04:30:39 +02:00
|
|
|
PID: pid,
|
2018-05-01 03:18:30 +02:00
|
|
|
Password: values[FormValuePassword],
|
|
|
|
Arbitrary: arbitrary,
|
2018-02-05 04:30:39 +02:00
|
|
|
}, nil
|
|
|
|
default:
|
|
|
|
return nil, errors.Errorf("failed to parse unknown page's form: %s", page)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// URLValuesToMap helps create a map from url.Values
|
|
|
|
func URLValuesToMap(form url.Values) map[string]string {
|
|
|
|
values := make(map[string]string)
|
|
|
|
|
|
|
|
for k, v := range form {
|
|
|
|
if len(v) != 0 {
|
|
|
|
values[k] = v[0]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return values
|
|
|
|
}
|