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 }