auth
auth/request.go
Create interfaces for login verification flow. We needed an interface that we could use to say "send the email to verify the user's login" so that we could verify the emails we have are actually valid. This implements an NSQ version that sends an email_verification event. We'll get listener implementations that pull these messages off NSQ and actually send the emails. This also implements, for testing purposes, a version that just echoes the Login Value and the Verification code to stdout.
| paddy@99 | 1 package auth |
| paddy@99 | 2 |
| paddy@104 | 3 import ( |
| paddy@104 | 4 "encoding/json" |
| paddy@104 | 5 "log" |
| paddy@104 | 6 "net/http" |
| paddy@104 | 7 |
| paddy@104 | 8 "bitbucket.org/ww/goautoneg" |
| paddy@104 | 9 ) |
| paddy@104 | 10 |
| paddy@99 | 11 const ( |
| paddy@99 | 12 requestErrAccessDenied = "access_denied" |
| paddy@99 | 13 requestErrInsufficient = "insufficient" |
| paddy@99 | 14 requestErrOverflow = "overflow" |
| paddy@99 | 15 requestErrInvalidValue = "invalid_value" |
| paddy@99 | 16 requestErrInvalidFormat = "invalid_format" |
| paddy@99 | 17 requestErrMissing = "missing" |
| paddy@99 | 18 requestErrNotFound = "not_found" |
| paddy@104 | 19 requestErrConflict = "conflict" |
| paddy@99 | 20 requestErrActOfGod = "act_of_god" |
| paddy@99 | 21 ) |
| paddy@99 | 22 |
| paddy@104 | 23 var ( |
| paddy@104 | 24 actOfGodResponse = response{Errors: []requestError{requestError{Slug: requestErrActOfGod}}} |
| paddy@104 | 25 invalidFormatResponse = response{Errors: []requestError{requestError{Slug: requestErrInvalidFormat, Field: "/"}}} |
| paddy@104 | 26 |
| paddy@104 | 27 encoders = []string{"application/json"} |
| paddy@104 | 28 ) |
| paddy@104 | 29 |
| paddy@104 | 30 type response struct { |
| paddy@108 | 31 Errors []requestError `json:"errors,omitempty"` |
| paddy@108 | 32 Logins []Login `json:"logins,omitempty"` |
| paddy@108 | 33 Profiles []Profile `json:"profiles,omitempty"` |
| paddy@108 | 34 Clients []Client `json:"clients,omitempty"` |
| paddy@108 | 35 Endpoints []Endpoint `json:"endpoints,omitempty"` |
| paddy@159 | 36 Sessions []Session `json:"sessions,omitempty"` |
| paddy@104 | 37 } |
| paddy@104 | 38 |
| paddy@99 | 39 type requestError struct { |
| paddy@99 | 40 Slug string `json:"error,omitempty"` |
| paddy@99 | 41 Field string `json:"field,omitempty"` |
| paddy@99 | 42 Param string `json:"param,omitempty"` |
| paddy@99 | 43 Header string `json:"header,omitempty"` |
| paddy@99 | 44 } |
| paddy@104 | 45 |
| paddy@104 | 46 func negotiate(h http.Handler) http.Handler { |
| paddy@104 | 47 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| paddy@110 | 48 if r.Header.Get("Accept") != "" { |
| paddy@110 | 49 contentType := goautoneg.Negotiate(r.Header.Get("Accept"), encoders) |
| paddy@110 | 50 if contentType == "" { |
| paddy@110 | 51 w.WriteHeader(http.StatusNotAcceptable) |
| paddy@110 | 52 w.Write([]byte("Unsupported content type requested: " + r.Header.Get("Accept"))) |
| paddy@110 | 53 return |
| paddy@110 | 54 } |
| paddy@104 | 55 } |
| paddy@104 | 56 h.ServeHTTP(w, r) |
| paddy@104 | 57 }) |
| paddy@104 | 58 } |
| paddy@104 | 59 |
| paddy@104 | 60 func encode(w http.ResponseWriter, r *http.Request, status int, resp response) { |
| paddy@104 | 61 contentType := goautoneg.Negotiate(r.Header.Get("Accept"), encoders) |
| paddy@104 | 62 w.Header().Set("content-type", contentType) |
| paddy@104 | 63 w.WriteHeader(status) |
| paddy@104 | 64 var err error |
| paddy@104 | 65 switch contentType { |
| paddy@104 | 66 case "application/json": |
| paddy@104 | 67 enc := json.NewEncoder(w) |
| paddy@104 | 68 err = enc.Encode(resp) |
| paddy@110 | 69 default: |
| paddy@110 | 70 enc := json.NewEncoder(w) |
| paddy@110 | 71 err = enc.Encode(resp) |
| paddy@104 | 72 } |
| paddy@104 | 73 if err != nil { |
| paddy@104 | 74 log.Println(err) |
| paddy@104 | 75 } |
| paddy@104 | 76 } |
| paddy@104 | 77 |
| paddy@133 | 78 func decode(r *http.Request, target interface{}) error { |
| paddy@133 | 79 defer r.Body.Close() |
| paddy@133 | 80 switch r.Header.Get("Content-Type") { |
| paddy@133 | 81 case "application/json": |
| paddy@133 | 82 dec := json.NewDecoder(r.Body) |
| paddy@133 | 83 return dec.Decode(target) |
| paddy@133 | 84 default: |
| paddy@133 | 85 dec := json.NewDecoder(r.Body) |
| paddy@133 | 86 return dec.Decode(target) |
| paddy@133 | 87 } |
| paddy@133 | 88 } |
| paddy@133 | 89 |
| paddy@104 | 90 func wrap(context Context, f func(w http.ResponseWriter, r *http.Request, context Context)) http.Handler { |
| paddy@104 | 91 return negotiate(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| paddy@165 | 92 w.Header().Set("Access-Control-Allow-Origin", "*") |
| paddy@165 | 93 w.Header().Set("Access-Control-Allow-Headers", r.Header.Get("Access-Control-Request-Headers")) |
| paddy@165 | 94 w.Header().Set("Access-Control-Allow-Credentials", "true") |
| paddy@165 | 95 if r.Method == "OPTIONS" { |
| paddy@165 | 96 w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS") |
| paddy@165 | 97 w.Header().Set("Allow", "GET, POST, PUT, DELETE, PATCH, OPTIONS") |
| paddy@165 | 98 w.WriteHeader(http.StatusOK) |
| paddy@165 | 99 return |
| paddy@165 | 100 } |
| paddy@104 | 101 f(w, r, context) |
| paddy@104 | 102 })) |
| paddy@104 | 103 } |