auth
auth/profile.go
Implement UpdateProfileHandler. Implement a handler that will allow users to update their Profiles through the API.
1.1 --- a/profile.go Mon Mar 16 22:36:12 2015 -0400 1.2 +++ b/profile.go Wed Mar 18 17:28:47 2015 -0400 1.3 @@ -164,12 +164,6 @@ 1.4 if c.Iterations != nil && c.Passphrase == nil { 1.5 return ErrMissingPassphrase 1.6 } 1.7 - if c.Passphrase != nil && len(*c.Passphrase) < MinPassphraseLength { 1.8 - return ErrPassphraseTooShort 1.9 - } 1.10 - if c.Passphrase != nil && len(*c.Passphrase) > MaxPassphraseLength { 1.11 - return ErrPassphraseTooLong 1.12 - } 1.13 return nil 1.14 } 1.15 1.16 @@ -426,7 +420,7 @@ 1.17 func RegisterProfileHandlers(r *mux.Router, context Context) { 1.18 r.Handle("/profiles", wrap(context, CreateProfileHandler)).Methods("POST") 1.19 // BUG(paddy): We need to implement a handler that will return information about a profile or set of profiles. 1.20 - // BUG(paddy): We need to implement a handler that will update a profile. 1.21 + r.Handle("/profiles/{id}", wrap(context, UpdateProfileHandler)).Methods("PATCH") 1.22 // BUG(paddy): We need to implement a handler that will delete a profile. What happens to clients/tokens/grants/sessions when a profile is deleted? 1.23 // BUG(paddy): We need to implement a handler that will add a login to a profile. 1.24 // BUG(paddy): We need to implement a handler that will remove a login from a profile. What happens to sessions created with that login? 1.25 @@ -516,3 +510,115 @@ 1.26 encode(w, r, http.StatusCreated, resp) 1.27 // TODO(paddy): should we kick off the email validation flow? 1.28 } 1.29 + 1.30 +func UpdateProfileHandler(w http.ResponseWriter, r *http.Request, context Context) { 1.31 + errors := []requestError{} 1.32 + vars := mux.Vars(r) 1.33 + if vars["id"] == "" { 1.34 + errors = append(errors, requestError{Slug: requestErrMissing, Param: "id"}) 1.35 + encode(w, r, http.StatusBadRequest, response{Errors: errors}) 1.36 + return 1.37 + } 1.38 + id, err := uuid.Parse(vars["id"]) 1.39 + if err != nil { 1.40 + errors = append(errors, requestError{Slug: requestErrAccessDenied}) 1.41 + encode(w, r, http.StatusBadRequest, response{Errors: errors}) 1.42 + return 1.43 + } 1.44 + username, password, ok := r.BasicAuth() 1.45 + if !ok { 1.46 + errors = append(errors, requestError{Slug: requestErrAccessDenied}) 1.47 + encode(w, r, http.StatusUnauthorized, response{Errors: errors}) 1.48 + return 1.49 + } 1.50 + profile, err := authenticate(username, password, context) 1.51 + if err != nil { 1.52 + if isAuthError(err) { 1.53 + errors = append(errors, requestError{Slug: requestErrAccessDenied}) 1.54 + encode(w, r, http.StatusUnauthorized, response{Errors: errors}) 1.55 + } else { 1.56 + errors = append(errors, requestError{Slug: requestErrActOfGod}) 1.57 + encode(w, r, http.StatusInternalServerError, response{Errors: errors}) 1.58 + } 1.59 + return 1.60 + } 1.61 + if !profile.ID.Equal(id) { 1.62 + errors = append(errors, requestError{Slug: requestErrAccessDenied}) 1.63 + encode(w, r, http.StatusForbidden, response{Errors: errors}) 1.64 + return 1.65 + } 1.66 + var req ProfileChange 1.67 + decoder := json.NewDecoder(r.Body) 1.68 + err = decoder.Decode(&req) 1.69 + if err != nil { 1.70 + encode(w, r, http.StatusBadRequest, invalidFormatResponse) 1.71 + return 1.72 + } 1.73 + req.Iterations = nil 1.74 + req.Salt = nil 1.75 + req.PassphraseScheme = nil 1.76 + req.Compromised = nil // BUG(paddy): Need a way for admins to mark accounts as compromised 1.77 + req.LockedUntil = nil 1.78 + req.LastSeen = nil 1.79 + if req.Passphrase != nil { 1.80 + if len(*req.Passphrase) < MinPassphraseLength { 1.81 + errors = append(errors, requestError{Slug: requestErrInsufficient, Field: "/passphrase"}) 1.82 + encode(w, r, http.StatusBadRequest, response{Errors: errors}) 1.83 + return 1.84 + } 1.85 + if len(*req.Passphrase) > MaxPassphraseLength { 1.86 + errors = append(errors, requestError{Slug: requestErrOverflow, Field: "/passphrase"}) 1.87 + encode(w, r, http.StatusBadRequest, response{Errors: errors}) 1.88 + return 1.89 + } 1.90 + iterations := context.config.iterations 1.91 + scheme, ok := passphraseSchemes[CurPassphraseScheme] 1.92 + if !ok { 1.93 + encode(w, r, http.StatusInternalServerError, actOfGodResponse) 1.94 + return 1.95 + } 1.96 + curScheme := CurPassphraseScheme 1.97 + req.PassphraseScheme = &curScheme 1.98 + passphrase, salt, err := scheme.create(*req.Passphrase, iterations) 1.99 + if err != nil { 1.100 + encode(w, r, http.StatusInternalServerError, actOfGodResponse) 1.101 + return 1.102 + } 1.103 + req.Passphrase = &passphrase 1.104 + req.Salt = &salt 1.105 + req.Iterations = &iterations 1.106 + } 1.107 + if req.PassphraseReset != nil { 1.108 + now := time.Now() 1.109 + req.PassphraseResetCreated = &now 1.110 + } 1.111 + err = req.Validate() 1.112 + if err != nil { 1.113 + var status int 1.114 + var resp response 1.115 + switch err { 1.116 + case ErrEmptyChange: 1.117 + resp.Profiles = []Profile{profile} 1.118 + status = http.StatusOK 1.119 + default: 1.120 + errors = append(errors, requestError{Slug: requestErrActOfGod}) 1.121 + resp.Errors = errors 1.122 + status = http.StatusInternalServerError 1.123 + } 1.124 + encode(w, r, status, resp) 1.125 + return 1.126 + } 1.127 + err = context.UpdateProfile(id, req) 1.128 + if err != nil { 1.129 + if err == ErrProfileNotFound { 1.130 + errors = append(errors, requestError{Slug: requestErrNotFound}) 1.131 + encode(w, r, http.StatusNotFound, response{Errors: errors}) 1.132 + return 1.133 + } 1.134 + encode(w, r, http.StatusInternalServerError, actOfGodResponse) 1.135 + return 1.136 + } 1.137 + profile.ApplyChange(req) 1.138 + encode(w, r, http.StatusOK, response{Profiles: []Profile{profile}}) 1.139 + return 1.140 +}