auth

Paddy 2014-12-14 Parent:bc77a315f823 Child:d442523df640

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.

profile.go

     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  }