auth
105:3a1fe5ee17f5 Browse Files
Add JSON tags and profile handler registration. Add JSON tags to all our profile types. Make an invalid email format error return requestErrInvalidFormat, not requestErrInvalidValue. Add a RegisterProfileHandlers to associate the handlers regarding profiles with the specified router. Replace our TODOs to send data down the wire with calls to our encode helper.
1.1 --- a/profile.go Sun Dec 14 17:00:31 2014 -0500 1.2 +++ b/profile.go Sun Dec 14 17:02:23 2014 -0500 1.3 @@ -11,6 +11,7 @@ 1.4 "code.secondbit.org/uuid" 1.5 1.6 "github.com/extemporalgenome/slug" 1.7 + "github.com/gorilla/mux" 1.8 ) 1.9 1.10 const ( 1.11 @@ -70,18 +71,18 @@ 1.12 // including their authentication information, but not 1.13 // including their username or email. 1.14 type Profile struct { 1.15 - ID uuid.ID 1.16 - Name string 1.17 - Passphrase string 1.18 - Iterations int 1.19 - Salt string 1.20 - PassphraseScheme int 1.21 - Compromised bool 1.22 - LockedUntil time.Time 1.23 - PassphraseReset string 1.24 - PassphraseResetCreated time.Time 1.25 - Created time.Time 1.26 - LastSeen time.Time 1.27 + ID uuid.ID `json:"id,omitempty"` 1.28 + Name string `json:"name,omitempty"` 1.29 + Passphrase string `json:"-"` 1.30 + Iterations int `json:"-"` 1.31 + Salt string `json:"-"` 1.32 + PassphraseScheme int `json:"-"` 1.33 + Compromised bool `json:"-"` 1.34 + LockedUntil time.Time `json:"-"` 1.35 + PassphraseReset string `json:"-"` 1.36 + PassphraseResetCreated time.Time `json:"-"` 1.37 + Created time.Time `json:"created,omitempty"` 1.38 + LastSeen time.Time `json:"last_seen,omitempty"` 1.39 } 1.40 1.41 // ApplyChange applies the properties of the passed ProfileChange 1.42 @@ -195,11 +196,11 @@ 1.43 // a given Profile that can be used to log into that Profile. 1.44 // Each Profile may only have one Login for each Type. 1.45 type Login struct { 1.46 - Type string 1.47 - Value string 1.48 - ProfileID uuid.ID 1.49 - Created time.Time 1.50 - LastUsed time.Time 1.51 + Type string `json:"type,omitempty"` 1.52 + Value string `json:"value,omitempty"` 1.53 + ProfileID uuid.ID `json:"profile_id,omitempty"` 1.54 + Created time.Time `json:"created,omitempty"` 1.55 + LastUsed time.Time `json:"last_used,omitempty"` 1.56 } 1.57 1.58 type newProfileRequest struct { 1.59 @@ -253,7 +254,7 @@ 1.60 re := regexp.MustCompile(".+@.+\\..+") 1.61 if !re.Match([]byte(req.Email)) { 1.62 errors = append(errors, requestError{ 1.63 - Slug: requestErrInvalidValue, 1.64 + Slug: requestErrInvalidFormat, 1.65 Field: "/email", 1.66 }) 1.67 } 1.68 @@ -422,11 +423,16 @@ 1.69 return logins, nil 1.70 } 1.71 1.72 +// RegisterProfileHandlers adds handlers to the passed router to handle the profile endpoints, like registration and user retrieval. 1.73 +func RegisterProfileHandlers(r *mux.Router, context Context) { 1.74 + r.Handle("/profiles", wrap(context, CreateProfileHandler)).Methods("POST") 1.75 +} 1.76 + 1.77 // CreateProfileHandler is an HTTP handler for registering new profiles. 1.78 func CreateProfileHandler(w http.ResponseWriter, r *http.Request, context Context) { 1.79 scheme, ok := passphraseSchemes[CurPassphraseScheme] 1.80 if !ok { 1.81 - // TODO(paddy): write error 1.82 + encode(w, r, http.StatusInternalServerError, actOfGodResponse) 1.83 return 1.84 } 1.85 var req newProfileRequest 1.86 @@ -434,17 +440,17 @@ 1.87 decoder := json.NewDecoder(r.Body) 1.88 err := decoder.Decode(&req) 1.89 if err != nil { 1.90 - // TODO(paddy): write error 1.91 + encode(w, r, http.StatusBadRequest, invalidFormatResponse) 1.92 return 1.93 } 1.94 errors = append(errors, validateNewProfileRequest(&req)...) 1.95 if len(errors) > 0 { 1.96 - //TODO(paddy): return errors 1.97 + encode(w, r, http.StatusBadRequest, response{Errors: errors}) 1.98 return 1.99 } 1.100 passphrase, salt, err := scheme.create(req.Passphrase, context.config.iterations) 1.101 if err != nil { 1.102 - // TODO(paddy): write error 1.103 + encode(w, r, http.StatusInternalServerError, actOfGodResponse) 1.104 return 1.105 } 1.106 profile := Profile{ 1.107 @@ -459,7 +465,11 @@ 1.108 } 1.109 err = context.SaveProfile(profile) 1.110 if err != nil { 1.111 - // TODO(paddy): write error 1.112 + if err == ErrProfileAlreadyExists { 1.113 + encode(w, r, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrConflict, Field: "/id"}}}) 1.114 + return 1.115 + } 1.116 + encode(w, r, http.StatusInternalServerError, actOfGodResponse) 1.117 return 1.118 } 1.119 logins := []Login{} 1.120 @@ -472,7 +482,11 @@ 1.121 } 1.122 err = context.AddLogin(login) 1.123 if err != nil { 1.124 - // TODO(paddy): write error 1.125 + if err == ErrLoginAlreadyExists { 1.126 + encode(w, r, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrConflict, Field: "/email"}}}) 1.127 + return 1.128 + } 1.129 + encode(w, r, http.StatusInternalServerError, actOfGodResponse) 1.130 return 1.131 } 1.132 logins = append(logins, login) 1.133 @@ -481,11 +495,19 @@ 1.134 login.Value = req.Username 1.135 err = context.AddLogin(login) 1.136 if err != nil { 1.137 - // TODO(paddy): write error 1.138 + if err == ErrLoginAlreadyExists { 1.139 + encode(w, r, http.StatusBadRequest, response{Errors: []requestError{{Slug: requestErrConflict, Field: "/username"}}}) 1.140 + return 1.141 + } 1.142 + encode(w, r, http.StatusInternalServerError, actOfGodResponse) 1.143 return 1.144 } 1.145 logins = append(logins, login) 1.146 } 1.147 - // TODO(paddy): respond with login(s) and profile that were created 1.148 + resp := response{ 1.149 + Logins: logins, 1.150 + Profiles: []Profile{profile}, 1.151 + } 1.152 + encode(w, r, http.StatusCreated, resp) 1.153 // TODO(paddy): should we kick off the email validation flow? 1.154 }