auth
auth/request.go
Support CORS. Add support for the OPTIONS method on certain endpoints. Automatically add headers (and respond to) OPTIONS requests. Note that these headers are full of deceit and mendacity, and we should switch to trout soon so we can actually be honest about what methods we support.
| 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 } |