From ca05044541ca1f8f3aa972177cf322d4c4ca53d9 Mon Sep 17 00:00:00 2001 From: "Piotr \"Orange\" Zduniak" Date: Wed, 5 Nov 2014 20:54:04 +0100 Subject: [PATCH 1/8] main.go is now rewritten and well commented --- main.go | 135 +++++++++++++++++++++++++++----------------------------- 1 file changed, 65 insertions(+), 70 deletions(-) diff --git a/main.go b/main.go index e6aaf85..728f453 100644 --- a/main.go +++ b/main.go @@ -8,89 +8,84 @@ import ( "strconv" "time" - "github.com/gorilla/mux" - "github.com/lavab/api/db" - "github.com/lavab/api/utils" - "github.com/stretchr/graceful" + "github.com/Sirupsen/logrus" + "github.com/goji/glogrus" + "github.com/namsral/flag" + "github.com/zenazn/goji" + "github.com/zenazn/goji/graceful" + "github.com/zenazn/goji/web" + "github.com/zenazn/goji/web/middleware" ) // TODO: "Middleware that implements a few quick security wins" // https://github.com/unrolled/secure -const ( - cTlsFilePub = ".tls/pub" - cTlsFilePriv = ".tls/priv" - cTcpPort = 5000 - cApiVersion = "v0" +var ( + bindAddress = flag.String("bind", ":5000", "Network address used to bind") + apiVersion = flag.String("version", "v0", "Shown API version") + logFormatterType = flag.String("log", "text", "Log formatter type. Either \"json\" or \"text\"") ) -var config struct { - Port int - PortString string - Host string - TlsAvailable bool - RootJSON string -} - -func init() { - config.Port = cTcpPort - config.Host = "" - config.TlsAvailable = false - config.RootJSON = rootResponseString() // this avoids an import cycle and also improves perf by caching the response - - if tmp := os.Getenv("API_PORT"); tmp != "" { - tmp2, err := strconv.Atoi(tmp) - if err != nil { - config.Port = tmp2 - } - log.Println("Running on non-default port", config.Port) - } - config.PortString = fmt.Sprintf(":%d", config.Port) - - if utils.FileExists(cTlsFilePub) && utils.FileExists(cTlsFilePriv) { - config.TlsAvailable = true - log.Println("Imported TLS cert/key successfully.") - } else { - log.Printf("TLS cert (%s) and key (%s) not found, serving plain HTTP.\n", cTlsFilePub, cTlsFilePriv) - } - - // Set up RethinkDB - go db.Init() -} - func main() { - setupAndRun() -} + // Parse the flags + flag.Parse() -func setupAndRun() { - r := mux.NewRouter() + // Set up a new logger + log := logrus.New() - if config.TlsAvailable { - r = r.Schemes("https").Subrouter() - } - if tmp := os.Getenv("API_HOST"); tmp != "" { - r = r.Host(tmp).Subrouter() + // Set the formatter depending on the passed flag's value + if *logFormatterType == "text" { + log.Formatter = &logrus.TextFormatter{} + } else if *logFormatterType == "json" { + log.Formatter = &logrus.JSONFormatter{} } - for _, rt := range publicRoutes { - r.HandleFunc(rt.Path, rt.HandleFunc).Methods(rt.Method) + // Create a new goji mux + mux := web.New() + + // Include the most basic middlewares: + // - RequestID assigns an unique ID for each request in order to identify errors. + // - Glogrus logs each request + // - Recoverer prevents panics from crashing the API + // - AutomaticOptions + mux.Use(middleware.RequestID) + mux.Use(glogrus.NewGlogrus(log, "api")) + mux.Use(middleware.Recoverer) + mux.Use(middleware.AutomaticOptions) + + // Compile the routes + mux.Compile() + + // Make the mux handle every request + http.Handle("/", DefaultMux) + + // Log that we're starting the server + log.WithFields(logrus.Fields{ + "address": *bindAddress, + }).Info("Starting the HTTP server") + + // Initialize the goroutine listening to signals passed to the app + graceful.HandleSignals() + + // Pre-graceful shutdown event + graceful.PreHook(func() { + log.Info("Received a singnal, stopping the application") + }) + + // Post-shutdown event + graceful.PostHook(func() { + log.Info("Stopped the application") + }) + + // Start the listening + err := graceful.Serve(listener, http.DefaultServeMux) + if err != nil { + // Don't use .Fatal! We need the code to shut down properly. + log.Error(err) } - for _, rt := range authRoutes { - r.HandleFunc(rt.Path, AuthWrapper(rt.HandleFunc)).Methods(rt.Method) - } + // If code reaches this place, it means that it was forcefully closed. - srv := &graceful.Server{ - Timeout: 10 * time.Second, - Server: &http.Server{ - Addr: config.PortString, - Handler: r, - }, - } - - if config.TlsAvailable { - log.Fatal(srv.ListenAndServeTLS(cTlsFilePub, cTlsFilePriv)) - } else { - log.Fatal(srv.ListenAndServe()) - } + // Wait until open connections close. + graceful.Wait() } From 7c4eb939d05784c0004936bb7c160548f366cbb3 Mon Sep 17 00:00:00 2001 From: "Piotr \"Orange\" Zduniak" Date: Wed, 5 Nov 2014 21:22:30 +0100 Subject: [PATCH 2/8] Added routes to main.go --- main.go | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/main.go b/main.go index 728f453..a8f58bd 100644 --- a/main.go +++ b/main.go @@ -53,6 +53,60 @@ func main() { mux.Use(middleware.Recoverer) mux.Use(middleware.AutomaticOptions) + // Set up an auth'd mux + auth := web.New() + mux.Use(models.AuthMiddleware) + + // Index route + mux.Get("/", routes.Hello) + + // Accounts + auth.Get("/accounts", routes.AccountsList) + mux.Post("/accounts", routes.AccountsCreate) + auth.Get("/accounts/:id", routes.AccountsGet) + auth.Put("/accounts/:id", routes.AccountsUpdate) + auth.Delete("/accounts/:id", routes.AccountsDelete) + auth.Post("/accounts/:id/wipe-user-data", routes.AccountsWipeUserData) + + // Tokens + auth.Get("/token", routes.TokenGet) + auth.Post("/token", routes.TokensCreate) + auth.Delete("/token", routes.TokensDelete) + + // Threads + auth.Get("/threads", routes.ThreadsList) + auth.Get("/threads/:id", routes.ThreadsGet) + auth.Put("/threads/:id", routes.ThreadsUpdate) + + // Emails + auth.Get("/emails", routes.EmailsList) + auth.Post("/emails", routes.EmailsCreate) + auth.Get("/emails/:id", routes.EmailsGet) + auth.Put("/emails/:id", routes.EmailsUpdate) + auth.Delete("/emails/:id", routes.EmailsDelete) + + // Labels + auth.Get("/labels", routes.LabelsList) + auth.Post("/labels", routes.LabelsCreate) + auth.Get("/labels/:id", routes.LabelsGet) + auth.Put("/labels/:id", routes.LabelsUpdate) + auth.Delete("/labels/:id", routes.LabelsDelete) + + // Contacts + auth.Get("/contacts", routes.ContactsList) + auth.Post("/contacts", routes.ContactsCreate) + auth.Get("/contacts/:id", routes.ContactsGet) + auth.Put("/contacts/:id", routes.ContactsUpdate) + auth.Delete("/contacts/:id", routes.ContactsDelete) + + // Keys + auth.Post("/keys", routes.KeysCreate) + mux.Get("/keys/:id", routes.KeysGet) + auth.Post("/keys/:id/vote", routes.KeysVote) + + // Merge the muxes + mux.Handle("/", auth) + // Compile the routes mux.Compile() From de447097193ff9f9dad42e6d15ca697a697c2e9c Mon Sep 17 00:00:00 2001 From: "Piotr \"Orange\" Zduniak" Date: Wed, 5 Nov 2014 21:51:20 +0100 Subject: [PATCH 3/8] Added an environment object, modified JSON utility response methods, added Hello route handler as an example --- env/config.go | 7 ++++ env/env.go | 12 ++++++ main.go | 14 ++++++- routes.go | 75 ----------------------------------- routes/{me.go => accounts.go} | 0 routes/hello.go | 22 ++++++++++ utils/json.go | 10 ----- utils/responses.go | 29 ++++++++++++-- 8 files changed, 79 insertions(+), 90 deletions(-) create mode 100644 env/config.go create mode 100644 env/env.go delete mode 100644 routes.go rename routes/{me.go => accounts.go} (100%) create mode 100644 routes/hello.go diff --git a/env/config.go b/env/config.go new file mode 100644 index 0000000..d60fb38 --- /dev/null +++ b/env/config.go @@ -0,0 +1,7 @@ +package env + +type Config struct { + BindAddress string + APIVersion string + LogFormatterType string +} diff --git a/env/env.go b/env/env.go new file mode 100644 index 0000000..29bfce7 --- /dev/null +++ b/env/env.go @@ -0,0 +1,12 @@ +package env + +import ( + "github.com/Sirupsen/logrus" +) + +type Environment struct { + Log *logrus.Logger + Config *Config +} + +var G *Environment diff --git a/main.go b/main.go index a8f58bd..3ecc96f 100644 --- a/main.go +++ b/main.go @@ -15,6 +15,8 @@ import ( "github.com/zenazn/goji/graceful" "github.com/zenazn/goji/web" "github.com/zenazn/goji/web/middleware" + + "github.com/lavab/api/env" ) // TODO: "Middleware that implements a few quick security wins" @@ -47,7 +49,7 @@ func main() { // - RequestID assigns an unique ID for each request in order to identify errors. // - Glogrus logs each request // - Recoverer prevents panics from crashing the API - // - AutomaticOptions + // - AutomaticOptions automatically responds to OPTIONS requests mux.Use(middleware.RequestID) mux.Use(glogrus.NewGlogrus(log, "api")) mux.Use(middleware.Recoverer) @@ -113,6 +115,16 @@ func main() { // Make the mux handle every request http.Handle("/", DefaultMux) + // Set up a new environment object + env.G = &env.Environment{ + Log: log, + Config: &env.Config{ + BindAddress: *bindAddress, + APIVersion: *apiVersion, + LogFormatterType: *logFormatterType, + }, + } + // Log that we're starting the server log.WithFields(logrus.Fields{ "address": *bindAddress, diff --git a/routes.go b/routes.go deleted file mode 100644 index b958a8d..0000000 --- a/routes.go +++ /dev/null @@ -1,75 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "log" - "net/http" - - "github.com/lavab/api/routes" -) - -type handleFunc func(w http.ResponseWriter, r *http.Request) - -type route struct { - Path string `json:"path"` - HandleFunc handleFunc `json:"-"` - Method string `json:"method"` - Description string `json:"desc"` -} - -var publicRoutes = []route{ - route{"/", listRoutes, "GET", "List of public and auth methods"}, - route{"/keys/{id}", routes.Key, "GET", ""}, - route{"/login", routes.Login, "POST", ""}, - route{"/signup", routes.Signup, "POST", ""}, -} - -var authRoutes = []route{ - route{"/logout", routes.Logout, "DELETE", "Destroys the current session"}, - route{"/me", routes.Me, "GET", "Fetch profile data for the current user"}, - route{"/me", routes.UpdateMe, "PUT", "Update data for the current user (settings, billing data, password, etc.)"}, - route{"/me/sessions", routes.Sessions, "GET", "Lists all the active sessions for the current user"}, - route{"/me/wipe-user-data", routes.WipeUserData, "DELETE", "Deletes all personal data of the user, except for basic profile information and billing status"}, - route{"/me/delete-account", routes.DeleteAccount, "DELETE", "Permanently deletes the user account"}, - route{"/threads", routes.Threads, "GET", "List email threads for the current user"}, - route{"/threads/{id}", routes.Thread, "GET", "Fetch a specific email thread"}, - route{"/threads/{id}", routes.UpdateThread, "PUT", "Update an email thread"}, - route{"/emails", routes.Emails, "GET", "List all emails for the current user"}, - route{"/emails", routes.CreateEmail, "POST", "Create and send an email"}, - route{"/emails/{id}", routes.Email, "GET", "Fetch a specific email"}, - route{"/emails/{id}", routes.UpdateEmail, "PUT", "Update a specific email (label, archive, etc)"}, - route{"/emails/{id}", routes.DeleteEmail, "DELETE", "Delete an email"}, - route{"/labels", routes.Labels, "GET", "List labels for the current user"}, - route{"/labels", routes.CreateLabel, "POST", "Create a new label"}, - route{"/labels/{id}", routes.Label, "GET", "Fetch a specific label"}, - route{"/labels/{id}", routes.UpdateLabel, "PUT", "Update a label"}, - route{"/labels/{id}", routes.DeleteLabel, "DELETE", "Delete a label"}, - route{"/contacts", routes.Contacts, "GET", "List all contacts for the current user"}, - route{"/contacts", routes.CreateContact, "POST", "Create a new contact"}, - route{"/contacts/{id}", routes.Contact, "GET", "Fetch a specific contact"}, - route{"/contacts/{id}", routes.UpdateContact, "PUT", "Update a contact"}, - route{"/contacts/{id}", routes.DeleteContact, "DELETE", "Delete a contact"}, - route{"/keys", routes.SubmitKey, "POST", "Submit a key to the Lavaboom private server"}, - route{"/keys/{id}", routes.VoteKey, "POST", "Vote or flag a key"}, -} - -func listRoutes(w http.ResponseWriter, r *http.Request) { - fmt.Fprint(w, config.RootJSON) -} - -func rootResponseString() string { - tmp, err := json.Marshal( - map[string]interface{}{ - "message": "Lavaboom API", - "docs_url": "http://lavaboom.readme.io/", - "version": cApiVersion, - "routes": map[string]interface{}{ - "public": publicRoutes, - "auth": authRoutes, - }}) - if err != nil { - log.Fatalln("Error! Couldn't marshal JSON.", err) - } - return string(tmp) -} diff --git a/routes/me.go b/routes/accounts.go similarity index 100% rename from routes/me.go rename to routes/accounts.go diff --git a/routes/hello.go b/routes/hello.go new file mode 100644 index 0000000..4e33c0f --- /dev/null +++ b/routes/hello.go @@ -0,0 +1,22 @@ +package routes + +import ( + "net/http" + + "github.com/lavab/api/env" + "github.com/lavab/api/utils" +) + +type HelloResponse struct { + Message string `json:"message"` + DocsURL string `json:"docs_url"` + Version string `json:"version"` +} + +func Hello(w http.ResponseWriter, r *http.Request) { + utils.JSONResponse(w, 200, &HelloResponse{ + Message: "Lavaboom API", + DocsURL: "http://lavaboom.readme.io/", + Version: env.G.Config.APIVersion, + }) +} diff --git a/utils/json.go b/utils/json.go index 52bdedd..cc58f91 100644 --- a/utils/json.go +++ b/utils/json.go @@ -6,16 +6,6 @@ import ( "log" ) -// MakeJSON is a wrapper over json.Marshal that returns an error message if an error occured -func MakeJSON(data map[string]interface{}) string { - res, err := json.Marshal(data) - if err != nil { - log.Fatalln("Error marshalling the response body.", err) - return "{\"status\":500,\"message\":\"Error occured while marshalling the response body\"}" - } - return string(res) -} - // ReadJSON reads a JSON string as a generic map string func ReadJSON(r io.Reader) (map[string]interface{}, error) { decoder := json.NewDecoder(r) diff --git a/utils/responses.go b/utils/responses.go index 31c1c15..c1649bc 100644 --- a/utils/responses.go +++ b/utils/responses.go @@ -1,17 +1,38 @@ package utils import ( - "fmt" "net/http" + + "github.com/lavab/api/env" ) +// marshalJSON is a wrapper over json.Marshal that returns an error message if an error occured +func marshalJSON(data interface{}) ([]byte, error) { + result, err := json.Marshal(data) + if err != nil { + return `{"status":500,"message":"Error occured while marshalling the response body"}`, err + } + return result, nil +} + // JSONResponse writes JSON to an http.ResponseWriter with the corresponding status code -func JSONResponse(w http.ResponseWriter, status int, data map[string]interface{}) { +func JSONResponse(w http.ResponseWriter, status int, data interface{}) { + // Get rid of the invalid status codes if status < 100 || status > 599 { status = 200 } + + // Try to marshal the input + result, err := marshalJSON(data) + if err != nil { + env.G.Log.WithFields(logrus.Fields{ + "error": err, + }).Error("Unable to marshal a message") + } + + // Write the result w.WriteHeader(status) - fmt.Fprint(w, MakeJSON(data)) + w.Write(result) } // ErrorResponse TODO @@ -25,7 +46,7 @@ func ErrorResponse(w http.ResponseWriter, code int, message string, debug string if debug == "" { delete(out, "debug") } - fmt.Fprint(w, MakeJSON(out)) + w.Write(marshalJSON(out)) } // FormatNotRecognizedResponse TODO From 86585fc3031087009300c35178d4933424b2c71b Mon Sep 17 00:00:00 2001 From: "Piotr \"Orange\" Zduniak" Date: Thu, 6 Nov 2014 22:33:46 +0100 Subject: [PATCH 4/8] routes/accounts.go rewritten, various small changes --- db/setup.go | 2 +- main.go | 3 +- routes/accounts.go | 219 ++++++++++++++++++++++++++++++++++++++++----- utils/requests.go | 85 ++++++++++++++++++ utils/responses.go | 55 ------------ 5 files changed, 287 insertions(+), 77 deletions(-) create mode 100644 utils/requests.go delete mode 100644 utils/responses.go diff --git a/db/setup.go b/db/setup.go index 2fcbfb0..5156c86 100644 --- a/db/setup.go +++ b/db/setup.go @@ -33,7 +33,7 @@ var tablesAndIndexes = map[string][]string{ "keys": []string{}, } -func Init() { +func init() { config.Url = "localhost:28015" config.AuthKey = "" config.Db = "dev" diff --git a/main.go b/main.go index 3ecc96f..148d60d 100644 --- a/main.go +++ b/main.go @@ -68,7 +68,8 @@ func main() { auth.Get("/accounts/:id", routes.AccountsGet) auth.Put("/accounts/:id", routes.AccountsUpdate) auth.Delete("/accounts/:id", routes.AccountsDelete) - auth.Post("/accounts/:id/wipe-user-data", routes.AccountsWipeUserData) + auth.Post("/accounts/:id/wipe-data", routes.AccountsWipeUserData) + auth.Get("/accounts/:id/sessions", routes.AccountsSessionsList) // Tokens auth.Get("/token", routes.TokenGet) diff --git a/routes/accounts.go b/routes/accounts.go index b25ea6c..ebad3ec 100644 --- a/routes/accounts.go +++ b/routes/accounts.go @@ -6,41 +6,220 @@ import ( "log" "net/http" + "github.com/Sirupsen/logrus" + "github.com/zenazn/goji/web" + "github.com/lavab/api/db" "github.com/lavab/api/dbutils" + "github.com/lavab/api/env" "github.com/lavab/api/models" "github.com/lavab/api/utils" ) -// Me returns information about the current user (more exactly, a JSONized models.User) -func Me(w http.ResponseWriter, r *http.Request) { +// Accounts list +type AccountsListResponse struct { + Success bool `json:"success"` + Message string `json:"message"` +} + +func AccountsList(w http.ResponseWriter, r *http.Request) { + utils.JSONResponse(w, 501, &AccountsListResponse{ + Success: false, + Message: "Method not implemented", + }) +} + +// Account registration +type AccountsCreateRequest struct { + Username string `json:"username" schema:"username"` + Password string `json:"password" schema:"password"` +} + +type AccountsCreateResponse struct { + Success bool `json:"success"` + Message string `json:"message"` + User *models.User `json:"data,omitempty"` +} + +func AccountsCreate(w http.ResponseWriter, r *http.Request) { + // Decode the request + var input AccountsCreateRequest + err := utils.ParseRequest(r, input) + if err != nil { + env.G.Log.WithFields(logrus.Fields{ + "error": err, + }).Warning("Unable to decode a request") + + utils.JSONResponse(w, 409, &AccountsCreateResponse{ + Success: false, + Message: "Invalid input format", + }) + return + } + + // Ensure that the user with requested username doesn't exist + if _, ok := dbutils.FindUserByName(username); ok { + utils.JSONResponse(w, 409, &AccountsCreateResponse{ + Success: false, + Message: "Username already exists", + }) + return + } + + // Try to hash the password + hash, err := utils.BcryptHash(password) + if err != nil { + utils.JSONResponse(w, 500, &AccountsCreateResponse{ + Success: false, + Message: "Internal server error - AC/CR/01", + }) + + env.G.Log.WithFields(logrus.Fields{ + "error": err, + }).Error("Unable to hash a password") + return + } + + // TODO: sanitize user name (i.e. remove caps, periods) + + // Create a new user object + user := &models.User{ + Resource: base.MakeResource(utils.UUID(), username), + Password: string(hash), + } + + // Try to save it in the database + if err := db.Insert("users", user); err != nil { + utils.JSONResponse(w, 500, &AccountsCreateResponse{ + Success: false, + Message: "Internal server error - AC/CR/02", + }) + + env.G.Log.WithFields(logrus.Fields{ + "error": err, + }).Error("Could not insert an user to the database") + return + } + + utils.JSONResponse(w, 201, &AccountsCreateResponse{ + Success: true, + Message: "A new account was successfully created", + User: user, + }) +} + +// AccountsGet returns the information about the specified account +type AccountsGetResponse struct { + Success bool `json:"success"` + Message string `json:"message,omitempty"` + User *models.User `json:"user,omitempty"` +} + +func AccountsGet(c *web.C, w http.ResponseWriter, r *http.Request) { + // Get the account ID from the request + id, ok := c.URLParams["id"] + if !ok { + utils.JSONResponse(409, &AccountsGetResponse{ + Success: false, + Message: "Invalid user ID", + }) + return + } + + // Right now we only support "me" as the ID + if id != "me" { + utils.JSONResponse(501, &AccountsGetResponse{ + Success: false, + Message: `Only the "me" user is implemented`, + }) + return + } + + // Fetch the current session from the database session := models.CurrentSession(r) + + // Fetch the user object from the database user, ok := dbutils.GetUser(session.UserID) if !ok { - debug := fmt.Sprintf("Session %s was deleted", session.ID) + // The session refers to a non-existing user + env.G.Log.WithFields(logrus.Fields{ + "id": session.ID, + }).Warning("Valid session referred to a removed account") + + // Try to remove the orphaned session if err := db.Delete("sessions", session.ID); err != nil { - debug = "Error when trying to delete session associated with inactive account" - log.Println("[routes.Me]", debug, err) + env.G.Log.WithFields(logrus.Fields{ + "id": session.ID, + "error": err, + }).Error("Unable to remove an orphaned session") + } else { + env.G.Log.WithFields(logrus.Fields{ + "id": session.ID, + }).Info("Removed an orphaned session") } - utils.ErrorResponse(w, 410, "Account deactivated", debug) - return - } - str, err := json.Marshal(user) - if err != nil { - debug := fmt.Sprint("Failed to marshal models.User:", user) - log.Println("[routes.Me]", debug) - utils.ErrorResponse(w, 500, "Internal server error", debug) + + utils.JSONResponse(410, &AccountsGetResponse{ + Success: false, + Message: "Account disabled", + }) return } - fmt.Fprint(w, string(str)) + + // Return the user struct + utils.JSONResponse(200, &AccountsGetResponse{ + Success: true, + User: user, + }) +} + +// AccountsUpdate TODO +type AccountsUpdateResponse struct { + Success bool `json:"success"` + Message string `json:"message"` +} + +func AccountsUpdate(w http.ResponseWriter, r *http.Request) { + utils.JSONResponse(501, &AccountsUpdateResponse{ + Success: false, + Message: `Sorry, not implemented yet`, + }) +} + +// AccountsDelete TODO +type AccountsDeleteResponse struct { + Success bool `json:"success"` + Message string `json:"message"` +} + +func AccountsDelete(w http.ResponseWriter, r *http.Request) { + utils.JSONResponse(501, &AccountsDeleteResponse{ + Success: false, + Message: `Sorry, not implemented yet`, + }) +} + +// AccountsWipeData TODO +type AccountsWipeDataResponse struct { + Success bool `json:"success"` + Message string `json:"message"` +} + +func AccountsWipeData(w http.ResponseWriter, r *http.Request) { + utils.JSONResponse(501, &AccountsWipeDataResponse{ + Success: false, + Message: `Sorry, not implemented yet`, + }) } -// UpdateMe TODO -func UpdateMe(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, "{\"success\":false,\"message\":\"Sorry, not implemented yet\"}") +// AccountsSessionsList TODO +type AccountsSessionsListResponse struct { + Success bool `json:"success"` + Message string `json:"message"` } -// Sessions lists all active sessions for current user -func Sessions(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, "{\"success\":false,\"message\":\"Sorry, not implemented yet\"}") +func AccountsSessionsList(w http.ResponseWriter, r *http.Request) { + utils.JSONResponse(501, &AccountsSessionsListResponse{ + Success: false, + Message: `Sorry, not implemented yet`, + }) } diff --git a/utils/requests.go b/utils/requests.go new file mode 100644 index 0000000..b1e5d2e --- /dev/null +++ b/utils/requests.go @@ -0,0 +1,85 @@ +package utils + +import ( + "encoding/json" + "errors" + "io/ioutil" + "net/http" + "strings" + + "github.com/gorilla/schema" + + "github.com/lavab/api/env" +) + +var ( + // Error declarations + ErrInvalidContentType = errors.New("Invalid request content type") + + // gorilla/schema decoder is a shared object, as it caches information about structs + decoder = schema.NewDecoder() +) + +// JSONResponse writes JSON to an http.ResponseWriter with the corresponding status code +func JSONResponse(w http.ResponseWriter, status int, data interface{}) { + // Get rid of the invalid status codes + if status < 100 || status > 599 { + status = 200 + } + + // Try to marshal the input + result, err := json.Marshal(data) + if err != nil { + // Log the error + env.G.Log.WithFields(logrus.Fields{ + "error": err, + }).Error("Unable to marshal a message") + + // Set the result to the default value to prevent empty responses + result = []byte(`{"status":500,"message":"Error occured while marshalling the response body"}`) + } + + // Write the result + w.WriteHeader(status) + w.Write(result) +} + +func ParseRequest(r *http.Request, data interface{}) error { + // Get the contentType for comparsions + contentType = r.Header.Get("Content-Type") + + // Deterimine the passed ContentType + if strings.Contains(contentType, "application/json") { + // It's JSON, so read the body into a variable + body, err := ioutil.ReadAll(r.Body) + if err != nil { + return err + } + + // And then unmarshal it into the passed interface + err = json.Unmarshal(body, data) + if err != nil { + return err + } + + return nil + } else if contentType == "" || + strings.Contains(contentType, "application/x-www-form-urlencoded") || + strings.Contains(contentType, "multipart/form-data") { + // net/http should be capable of parsing the form data + err := r.ParseForm() + if err != nil { + return err + } + + // Unmarshal them into the passed interface + err = decoder.Decode(data, r.PostForm) + if err != nil { + return err + } + + return nil + } + + return ErrInvalidContentType +} diff --git a/utils/responses.go b/utils/responses.go deleted file mode 100644 index c1649bc..0000000 --- a/utils/responses.go +++ /dev/null @@ -1,55 +0,0 @@ -package utils - -import ( - "net/http" - - "github.com/lavab/api/env" -) - -// marshalJSON is a wrapper over json.Marshal that returns an error message if an error occured -func marshalJSON(data interface{}) ([]byte, error) { - result, err := json.Marshal(data) - if err != nil { - return `{"status":500,"message":"Error occured while marshalling the response body"}`, err - } - return result, nil -} - -// JSONResponse writes JSON to an http.ResponseWriter with the corresponding status code -func JSONResponse(w http.ResponseWriter, status int, data interface{}) { - // Get rid of the invalid status codes - if status < 100 || status > 599 { - status = 200 - } - - // Try to marshal the input - result, err := marshalJSON(data) - if err != nil { - env.G.Log.WithFields(logrus.Fields{ - "error": err, - }).Error("Unable to marshal a message") - } - - // Write the result - w.WriteHeader(status) - w.Write(result) -} - -// ErrorResponse TODO -func ErrorResponse(w http.ResponseWriter, code int, message string, debug string) { - out := map[string]interface{}{ - "debug": debug, - "message": message, - "status": code, - "success": false, - } - if debug == "" { - delete(out, "debug") - } - w.Write(marshalJSON(out)) -} - -// FormatNotRecognizedResponse TODO -func FormatNotRecognizedResponse(w http.ResponseWriter, err error) { - ErrorResponse(w, 400, "Format not recognized", err.Error()) -} From 5db30c40bb04135417113aa528ed4863159cdc8f Mon Sep 17 00:00:00 2001 From: "Piotr \"Orange\" Zduniak" Date: Thu, 6 Nov 2014 23:04:45 +0100 Subject: [PATCH 5/8] Rewritten tokens.go --- env/config.go | 1 + main.go | 8 ++- models/base/expiring.go | 8 +-- routes/actions.go | 16 ------ routes/sessions.go | 91 ----------------------------- routes/tokens.go | 124 ++++++++++++++++++++++++++++++++++++++++ utils/json.go | 18 ------ 7 files changed, 134 insertions(+), 132 deletions(-) delete mode 100644 routes/actions.go delete mode 100644 routes/sessions.go create mode 100644 routes/tokens.go delete mode 100644 utils/json.go diff --git a/env/config.go b/env/config.go index d60fb38..e0d4bdd 100644 --- a/env/config.go +++ b/env/config.go @@ -4,4 +4,5 @@ type Config struct { BindAddress string APIVersion string LogFormatterType string + SessionDuration int } diff --git a/main.go b/main.go index 148d60d..43df403 100644 --- a/main.go +++ b/main.go @@ -26,6 +26,7 @@ var ( bindAddress = flag.String("bind", ":5000", "Network address used to bind") apiVersion = flag.String("version", "v0", "Shown API version") logFormatterType = flag.String("log", "text", "Log formatter type. Either \"json\" or \"text\"") + sessionDuration = flag.Int("session_duration", 72, "Session duration expressed in hours") ) func main() { @@ -72,9 +73,9 @@ func main() { auth.Get("/accounts/:id/sessions", routes.AccountsSessionsList) // Tokens - auth.Get("/token", routes.TokenGet) - auth.Post("/token", routes.TokensCreate) - auth.Delete("/token", routes.TokensDelete) + auth.Get("/tokens", routes.TokenGet) + auth.Post("/tokens", routes.TokensCreate) + auth.Delete("/tokens", routes.TokensDelete) // Threads auth.Get("/threads", routes.ThreadsList) @@ -123,6 +124,7 @@ func main() { BindAddress: *bindAddress, APIVersion: *apiVersion, LogFormatterType: *logFormatterType, + SessionDuration: *sessionDuration, }, } diff --git a/models/base/expiring.go b/models/base/expiring.go index c25c1f6..f1bc4d6 100644 --- a/models/base/expiring.go +++ b/models/base/expiring.go @@ -7,13 +7,13 @@ import ( // Expiring is a base struct for resources that expires e.g. sessions. type Expiring struct { - // ExpDate is the RFC3339-encoded time when the resource will expire. - ExpDate string `json:"exp_date" gorethink:"exp_date"` + // ExpirationDate is the RFC3339-encoded time when the resource will expire. + ExpirationDate string `json:"exp_date" gorethink:"exp_date"` } -// HasExpired returns true if the resource has expired (or if the ExpDate string is badly formatted) +// HasExpired returns true if the resource has expired (or if the ExpirationDate string is badly formatted) func (e *Expiring) HasExpired() bool { - t, err := time.Parse(time.RFC3339, e.ExpDate) + t, err := time.Parse(time.RFC3339, e.ExpirationDate) if err != nil { log.Println("Bad format! The expiry date is not RFC3339-formatted.", err) return true diff --git a/routes/actions.go b/routes/actions.go deleted file mode 100644 index 232d3a1..0000000 --- a/routes/actions.go +++ /dev/null @@ -1,16 +0,0 @@ -package routes - -import ( - "fmt" - "net/http" -) - -// WipeUserData TODO -func WipeUserData(w http.ResponseWriter, r *http.Request) { - -} - -// DeleteAccount TODO -func DeleteAccount(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, "{\"success\":false,\"message\":\"Sorry, not implemented yet\"}") -} diff --git a/routes/sessions.go b/routes/sessions.go deleted file mode 100644 index 29b2566..0000000 --- a/routes/sessions.go +++ /dev/null @@ -1,91 +0,0 @@ -package routes - -import ( - "fmt" - "log" - "net/http" - - "github.com/gorilla/context" - "github.com/lavab/api/db" - "github.com/lavab/api/dbutils" - "github.com/lavab/api/models" - "github.com/lavab/api/models/base" - "github.com/lavab/api/utils" -) - -const SessionDurationInHours = 72 - -// Login gets a username and password and returns a session token on success -func Login(w http.ResponseWriter, r *http.Request) { - username, password := r.FormValue("username"), r.FormValue("password") - user, ok := dbutils.FindUserByName(username) - if !ok || user == nil || !utils.BcryptVerify(user.Password, password) { - utils.ErrorResponse(w, 403, "Wrong username or password", - fmt.Sprintf("user: %+v", user)) - return - } - - // TODO check number of sessions for the current user here - session := models.Session{ - Expiring: base.Expiring{utils.HoursFromNowString(SessionDurationInHours)}, - Resource: base.MakeResource(user.ID, ""), - } - session.Name = fmt.Sprintf("Auth session expiring on %s", session.ExpDate) - db.Insert("sessions", session) - - utils.JSONResponse(w, 200, map[string]interface{}{ - "message": "Authentication successful", - "success": true, - "session": session, - }) -} - -// Signup gets a username and password and creates a user account on success -func Signup(w http.ResponseWriter, r *http.Request) { - username, password := r.FormValue("username"), r.FormValue("password") - // regt := r.FormValue("reg_token") - - if _, ok := dbutils.FindUserByName(username); ok { - utils.ErrorResponse(w, 409, "Username already exists", "") - return - } - - hash, err := utils.BcryptHash(password) - if err != nil { - msg := "Bcrypt hashing has failed" - utils.ErrorResponse(w, 500, "Internal server error", msg) - log.Fatalln(msg) - } - - // TODO: sanitize user name (i.e. remove caps, periods) - - user := models.User{ - Resource: base.MakeResource(utils.UUID(), username), - Password: string(hash), - } - - if err := db.Insert("users", user); err != nil { - utils.ErrorResponse(w, 500, "Internal server error", - fmt.Sprintf("Couldn't insert %+v to database", user)) - } - - utils.JSONResponse(w, 201, map[string]interface{}{ - "message": "Signup successful", - "success": true, - "data": user, - }) -} - -// Logout destroys the current session token -func Logout(w http.ResponseWriter, r *http.Request) { - session := context.Get(r, "session").(*models.Session) - if err := db.Delete("sessions", session.ID); err != nil { - utils.ErrorResponse(w, 500, "Internal server error", - fmt.Sprint("Couldn't delete session %v. %v", session, err)) - } - utils.JSONResponse(w, 410, map[string]interface{}{ - "message": fmt.Sprintf("Successfully logged out", session.UserID), - "success": true, - "deleted": session.ID, - }) -} diff --git a/routes/tokens.go b/routes/tokens.go new file mode 100644 index 0000000..8acfe7b --- /dev/null +++ b/routes/tokens.go @@ -0,0 +1,124 @@ +package routes + +import ( + "fmt" + "log" + "net/http" + "time" + + "github.com/gorilla/context" + "github.com/zenazn/goji/web" + + "github.com/lavab/api/db" + "github.com/lavab/api/dbutils" + "github.com/lavab/api/env" + "github.com/lavab/api/models" + "github.com/lavab/api/models/base" + "github.com/lavab/api/utils" +) + +type TokensGetResponse struct { + Success bool `json:"success"` + Message string `json:"message,omitempty"` + Created string `json:"created,omitempty"` + Expires string `json:"expires,omitempty"` +} + +func TokensGet(w http.ResponseWriter, r *http.Request) { + // Fetch the current session from the database + session := models.CurrentSession(r) + + // Respond with the token information + utils.JSONResponse(200, &TokensGetResponse{ + Success: true, + Created: session.DateCreated, + Expires: session.ExpirationDate, + }) +} + +type TokensCreateRequest struct { + Username string `json:"username" schema:"username"` + Password string `json:"password" schema:"password"` +} + +type TokensCreateResponse struct { + Success bool `json:"success"` + Message string `json:"message,omitempty"` + Token *models.Session `json:"token,omitempty"` +} + +func TokensCreate(w http.ResponseWriter, r *http.Request) { + // Decode the request + var input TokensCreateRequest + err := utils.ParseRequest(r, input) + if err != nil { + env.G.Log.WithFields(logrus.Fields{ + "error": err, + }).Warning("Unable to decode a request") + + utils.JSONResponse(w, 409, &TokensCreateResponse{ + Success: false, + Message: "Invalid input format", + }) + return + } + + // Authenticate the user + user, ok := dbutils.FindUserByName(username) + if !ok || user == nil || !utils.BcryptVerify(user.Password, password) { + utils.JSONResponse(w, 403, &TokensCreateResponse{ + Success: false, + Message: "Wrong username or password", + }) + return + } + + // Calculate the expiration date + expDate := utils.HoursFromNowString(env.G.Config.SessionDuration) + + // Create a new token + token := &models.Session{ + Expiring: base.Expiring{expDate}, + Resource: base.MakeResource(user.ID, ""), + Name: "Auth token expiring on " + expDate, + } + + // Insert int into the database + db.Insert("sessions", token) + + // Respond with the freshly created token + utils.JSONResponse(w, 201, &TokensCreateResponse{ + Success: true, + Message: "Authentication successful", + Token: token, + }) +} + +// Logout destroys the current session token +type TokensDeleteResponse struct { + Success bool `json:"success"` + Message string `json:"message"` +} + +func TokensDelete(c *web.C, w http.ResponseWriter, r *http.Request) { + // Get the session from the middleware + session := c.Env["session"].(*models.Session) + + // Delete it from the database + if err := db.Delete("sessions", session.ID); err != nil { + env.G.Log.WithFields(logrus.Fields{ + "error": err, + }).Error("Unable to delete a session") + + utils.JSONResponse(w, 500, &TokensDeleteResponse{ + Success: true, + Message: "Internal server error - TO/DE/01", + }) + return + } + + utils.JSONResponse(w, 200, &TokensDeleteResponse{ + Success: true, + Message: "Successfully logged out", + }) +} diff --git a/utils/json.go b/utils/json.go deleted file mode 100644 index cc58f91..0000000 --- a/utils/json.go +++ /dev/null @@ -1,18 +0,0 @@ -package utils - -import ( - "encoding/json" - "io" - "log" -) - -// ReadJSON reads a JSON string as a generic map string -func ReadJSON(r io.Reader) (map[string]interface{}, error) { - decoder := json.NewDecoder(r) - out := map[string]interface{}{} - err := decoder.Decode(&out) - if err != nil { - return out, err - } - return out, nil -} From 8fd532d94fc2c2295314eae9d57668bac5ae732c Mon Sep 17 00:00:00 2001 From: "Piotr \"Orange\" Zduniak" Date: Thu, 6 Nov 2014 23:13:32 +0100 Subject: [PATCH 6/8] golint changes on the rewritten files --- routes/accounts.go | 22 +++++++++++++++------- routes/tokens.go | 8 +++++++- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/routes/accounts.go b/routes/accounts.go index ebad3ec..ec72f21 100644 --- a/routes/accounts.go +++ b/routes/accounts.go @@ -16,12 +16,13 @@ import ( "github.com/lavab/api/utils" ) -// Accounts list +// AccountsListResponse contains the result of the AccountsList request. type AccountsListResponse struct { Success bool `json:"success"` Message string `json:"message"` } +// AccountsList returns a list of accounts visible to an user func AccountsList(w http.ResponseWriter, r *http.Request) { utils.JSONResponse(w, 501, &AccountsListResponse{ Success: false, @@ -29,18 +30,20 @@ func AccountsList(w http.ResponseWriter, r *http.Request) { }) } -// Account registration +// AccountsCreateRequest contains the input for the AccountsCreate endpoint. type AccountsCreateRequest struct { Username string `json:"username" schema:"username"` Password string `json:"password" schema:"password"` } +// AccountsCreateResponse contains the output of the AccountsCreate request. type AccountsCreateResponse struct { Success bool `json:"success"` Message string `json:"message"` User *models.User `json:"data,omitempty"` } +// AccountsCreate creates a new account in the system. func AccountsCreate(w http.ResponseWriter, r *http.Request) { // Decode the request var input AccountsCreateRequest @@ -108,13 +111,14 @@ func AccountsCreate(w http.ResponseWriter, r *http.Request) { }) } -// AccountsGet returns the information about the specified account +// AccountsGetResponse contains the result of the AccountsGet request. type AccountsGetResponse struct { Success bool `json:"success"` Message string `json:"message,omitempty"` User *models.User `json:"user,omitempty"` } +// AccountsGet returns the information about the specified account func AccountsGet(c *web.C, w http.ResponseWriter, r *http.Request) { // Get the account ID from the request id, ok := c.URLParams["id"] @@ -172,12 +176,13 @@ func AccountsGet(c *web.C, w http.ResponseWriter, r *http.Request) { }) } -// AccountsUpdate TODO +// AccountsUpdateResponse contains the result of the AccountsUpdate request. type AccountsUpdateResponse struct { Success bool `json:"success"` Message string `json:"message"` } +// AccountsUpdate allows changing the account's information (password etc.) func AccountsUpdate(w http.ResponseWriter, r *http.Request) { utils.JSONResponse(501, &AccountsUpdateResponse{ Success: false, @@ -185,12 +190,13 @@ func AccountsUpdate(w http.ResponseWriter, r *http.Request) { }) } -// AccountsDelete TODO +// AccountsDeleteResponse contains the result of the AccountsDelete request. type AccountsDeleteResponse struct { Success bool `json:"success"` Message string `json:"message"` } +// AccountsDelete allows deleting an account. func AccountsDelete(w http.ResponseWriter, r *http.Request) { utils.JSONResponse(501, &AccountsDeleteResponse{ Success: false, @@ -198,12 +204,13 @@ func AccountsDelete(w http.ResponseWriter, r *http.Request) { }) } -// AccountsWipeData TODO +// AccountsWipeDataResponse contains the result of the AccountsWipeData request. type AccountsWipeDataResponse struct { Success bool `json:"success"` Message string `json:"message"` } +// AccountsWipeData allows getting rid of the all data related to the account. func AccountsWipeData(w http.ResponseWriter, r *http.Request) { utils.JSONResponse(501, &AccountsWipeDataResponse{ Success: false, @@ -211,12 +218,13 @@ func AccountsWipeData(w http.ResponseWriter, r *http.Request) { }) } -// AccountsSessionsList TODO +// AccountsSessionsListResponse contains the result of the AccountsSessionsList request. type AccountsSessionsListResponse struct { Success bool `json:"success"` Message string `json:"message"` } +// AccountsSessionsList returns a list of all opened sessions. func AccountsSessionsList(w http.ResponseWriter, r *http.Request) { utils.JSONResponse(501, &AccountsSessionsListResponse{ Success: false, diff --git a/routes/tokens.go b/routes/tokens.go index 8acfe7b..1ff6392 100644 --- a/routes/tokens.go +++ b/routes/tokens.go @@ -17,6 +17,7 @@ import ( "github.com/lavab/api/utils" ) +// TokensGetResponse contains the result of the TokensGet request. type TokensGetResponse struct { Success bool `json:"success"` Message string `json:"message,omitempty"` @@ -24,6 +25,7 @@ type TokensGetResponse struct { Expires string `json:"expires,omitempty"` } +// TokensGet returns information about the current token. func TokensGet(w http.ResponseWriter, r *http.Request) { // Fetch the current session from the database session := models.CurrentSession(r) @@ -36,17 +38,20 @@ func TokensGet(w http.ResponseWriter, r *http.Request) { }) } +// TokensCreateRequest contains the input for the TokensCreate endpoint. type TokensCreateRequest struct { Username string `json:"username" schema:"username"` Password string `json:"password" schema:"password"` } +// TokensCreateResponse contains the result of the TokensCreate request. type TokensCreateResponse struct { Success bool `json:"success"` Message string `json:"message,omitempty"` Token *models.Session `json:"token,omitempty"` } +// TokensCreate allows logging in to an account. func TokensCreate(w http.ResponseWriter, r *http.Request) { // Decode the request var input TokensCreateRequest @@ -94,12 +99,13 @@ func TokensCreate(w http.ResponseWriter, r *http.Request) { }) } -// Logout destroys the current session token +// TokensDeleteResponse contains the result of the TokensDelete request. type TokensDeleteResponse struct { Success bool `json:"success"` Message string `json:"message"` } +// TokensDelete destroys the current session token. func TokensDelete(c *web.C, w http.ResponseWriter, r *http.Request) { // Get the session from the middleware session := c.Env["session"].(*models.Session) From 6f181a0306aa062540138c802d95b090051af051 Mon Sep 17 00:00:00 2001 From: "Piotr \"Orange\" Zduniak" Date: Thu, 6 Nov 2014 23:36:57 +0100 Subject: [PATCH 7/8] Rewrote other routes --- routes/accounts.go | 16 +++---- routes/contacts.go | 80 +++++++++++++++++++++++-------- routes/emails.go | 114 +++++++++++++++++++++++++++++---------------- routes/hello.go | 2 + routes/keys.go | 46 +++++++++++++----- routes/labels.go | 75 +++++++++++++++++++++++------ routes/threads.go | 48 +++++++++++++++---- routes/tokens.go | 2 +- 8 files changed, 276 insertions(+), 107 deletions(-) diff --git a/routes/accounts.go b/routes/accounts.go index ec72f21..18543b3 100644 --- a/routes/accounts.go +++ b/routes/accounts.go @@ -123,7 +123,7 @@ func AccountsGet(c *web.C, w http.ResponseWriter, r *http.Request) { // Get the account ID from the request id, ok := c.URLParams["id"] if !ok { - utils.JSONResponse(409, &AccountsGetResponse{ + utils.JSONResponse(w, 409, &AccountsGetResponse{ Success: false, Message: "Invalid user ID", }) @@ -132,7 +132,7 @@ func AccountsGet(c *web.C, w http.ResponseWriter, r *http.Request) { // Right now we only support "me" as the ID if id != "me" { - utils.JSONResponse(501, &AccountsGetResponse{ + utils.JSONResponse(w, 501, &AccountsGetResponse{ Success: false, Message: `Only the "me" user is implemented`, }) @@ -162,7 +162,7 @@ func AccountsGet(c *web.C, w http.ResponseWriter, r *http.Request) { }).Info("Removed an orphaned session") } - utils.JSONResponse(410, &AccountsGetResponse{ + utils.JSONResponse(w, 410, &AccountsGetResponse{ Success: false, Message: "Account disabled", }) @@ -170,7 +170,7 @@ func AccountsGet(c *web.C, w http.ResponseWriter, r *http.Request) { } // Return the user struct - utils.JSONResponse(200, &AccountsGetResponse{ + utils.JSONResponse(w, 200, &AccountsGetResponse{ Success: true, User: user, }) @@ -184,7 +184,7 @@ type AccountsUpdateResponse struct { // AccountsUpdate allows changing the account's information (password etc.) func AccountsUpdate(w http.ResponseWriter, r *http.Request) { - utils.JSONResponse(501, &AccountsUpdateResponse{ + utils.JSONResponse(w, 501, &AccountsUpdateResponse{ Success: false, Message: `Sorry, not implemented yet`, }) @@ -198,7 +198,7 @@ type AccountsDeleteResponse struct { // AccountsDelete allows deleting an account. func AccountsDelete(w http.ResponseWriter, r *http.Request) { - utils.JSONResponse(501, &AccountsDeleteResponse{ + utils.JSONResponse(w, 501, &AccountsDeleteResponse{ Success: false, Message: `Sorry, not implemented yet`, }) @@ -212,7 +212,7 @@ type AccountsWipeDataResponse struct { // AccountsWipeData allows getting rid of the all data related to the account. func AccountsWipeData(w http.ResponseWriter, r *http.Request) { - utils.JSONResponse(501, &AccountsWipeDataResponse{ + utils.JSONResponse(w, 501, &AccountsWipeDataResponse{ Success: false, Message: `Sorry, not implemented yet`, }) @@ -226,7 +226,7 @@ type AccountsSessionsListResponse struct { // AccountsSessionsList returns a list of all opened sessions. func AccountsSessionsList(w http.ResponseWriter, r *http.Request) { - utils.JSONResponse(501, &AccountsSessionsListResponse{ + utils.JSONResponse(w, 501, &AccountsSessionsListResponse{ Success: false, Message: `Sorry, not implemented yet`, }) diff --git a/routes/contacts.go b/routes/contacts.go index b9b8da2..21a24d9 100644 --- a/routes/contacts.go +++ b/routes/contacts.go @@ -7,32 +7,72 @@ import ( "github.com/lavab/api/utils" ) -// Contacts TODO -func Contacts(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, "{\"success\":false,\"message\":\"Sorry, not implemented yet\"}") +// ContactsListResponse contains the result of the ContactsList request. +type ContactsListResponse struct { + Success bool `json:"success"` + Message string `json:"message"` } -// CreateContact TODO -func CreateContact(w http.ResponseWriter, r *http.Request) { - reqData, err := utils.ReadJSON(r.Body) - if err == nil { - utils.FormatNotRecognizedResponse(w, err) - return - } - fmt.Println("TODO", reqData) +// ContactsList does *something* - TODO +func ContactsList(w http.ResponseWriter, r *http.Request) { + utils.JSONResponse(w, 501, &ContactsListResponse{ + Success: false, + Message: "Sorry, not implemented yet", + }) } -// Contact TODO -func Contact(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, "{\"success\":false,\"message\":\"Sorry, not implemented yet\"}") +// ContactsCreateResponse contains the result of the ContactsCreate request. +type ContactsCreateResponse struct { + Success bool `json:"success"` + Message string `json:"message"` } -// UpdateContact TODO -func UpdateContact(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, "{\"success\":false,\"message\":\"Sorry, not implemented yet\"}") +// ContactsCreate does *something* - TODO +func ContactsCreate(w http.ResponseWriter, r *http.Request) { + utils.JSONResponse(w, 501, &ContactsCreateResponse{ + Success: false, + Message: "Sorry, not implemented yet", + }) } -// DeleteContact TODO -func DeleteContact(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, "{\"success\":false,\"message\":\"Sorry, not implemented yet\"}") +// ContactsGetResponse contains the result of the ContactsGet request. +type ContactsGetResponse struct { + Success bool `json:"success"` + Message string `json:"message"` +} + +// ContactsGet does *something* - TODO +func ContactsGet(w http.ResponseWriter, r *http.Request) { + utils.JSONResponse(w, 501, &ContactsGetResponse{ + Success: false, + Message: "Sorry, not implemented yet", + }) +} + +// ContactsUpdateResponse contains the result of the ContactsUpdate request. +type ContactsUpdateResponse struct { + Success bool `json:"success"` + Message string `json:"message"` +} + +// ContactsUpdate does *something* - TODO +func ContactsUpdate(w http.ResponseWriter, r *http.Request) { + utils.JSONResponse(w, 501, &ContactsUpdateResponse{ + Success: false, + Message: "Sorry, not implemented yet", + }) +} + +// ContactsDeleteResponse contains the result of the ContactsDelete request. +type ContactsDeleteResponse struct { + Success bool `json:"success"` + Message string `json:"message"` +} + +// ContactsDelete does *something* - TODO +func ContactsDelete(w http.ResponseWriter, r *http.Request) { + utils.JSONResponse(w, 501, &ContactsDeleteResponse{ + Success: false, + Message: "Sorry, not implemented yet", + }) } diff --git a/routes/emails.go b/routes/emails.go index 48ab67e..fe7d458 100644 --- a/routes/emails.go +++ b/routes/emails.go @@ -8,45 +8,77 @@ import ( "github.com/lavab/api/utils" ) -// Emails TODO -func Emails(w http.ResponseWriter, r *http.Request) { - // fmt.Fprintln(w, "{\"success\":false,\"message\":\"Sorry, not implemented yet\"}") - mock := map[string]interface{}{ - "n_items": 1, - "emails": []models.Email{}, - } - utils.JSONResponse(w, 200, mock) -} - -// CreateEmail TODO -func CreateEmail(w http.ResponseWriter, r *http.Request) { - // reqData, err := utils.ReadJSON(r.Body) - // if err != nil { - // utils.ErrorResponse(w, 400, "Couldn't parse the request body", err.Error()) - // } - // log.Println(reqData) - mock := map[string]interface{}{ - "success": true, - "created": []string{utils.UUID()}, - } - utils.JSONResponse(w, 200, mock) -} - -// Email TODO -func Email(w http.ResponseWriter, r *http.Request) { - // fmt.Fprintln(w, "{\"success\":false,\"message\":\"Sorry, not implemented yet\"}") - mock := map[string]interface{}{ - "status": "sending", - } - utils.JSONResponse(w, 200, mock) -} - -// UpdateEmail TODO -func UpdateEmail(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, "{\"success\":false,\"message\":\"Sorry, not implemented yet\"}") -} - -// DeleteEmail TODO -func DeleteEmail(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, "{\"success\":false,\"message\":\"Sorry, not implemented yet\"}") +// EmailsListResponse contains the result of the EmailsList request. +type EmailsListResponse struct { + Success bool `json:"success"` + Message string `json:"message,omitempty"` + ItemsCount int `json:"items_count,omitempty"` + Emails []*models.Email `json:"emails,omitempty"` +} + +// EmailsList sends a list of the emails in the inbox. +func EmailsList(w http.ResponseWriter, r *http.Request) { + utils.JSONResponse(w, 200, &EmailsListResponse{ + Success: true, + ItemCount: 1, + Emails: []*models.Email{}, + }) +} + +// EmailsCreateResponse contains the result of the EmailsCreate request. +type EmailsCreateResponse struct { + Success bool `json:"success"` + Message string `json:"message,omitempty"` + Created []string `json:"created,omitempty"` +} + +// EmailsCreate sends a new email +func EmailsCreate(w http.ResponseWriter, r *http.Request) { + utils.JSONResponse(w, 200, &EmailsCreateResponse{ + Success: true, + Created: []string{utils.UUID()}, + }) +} + +// EmailsGetResponse contains the result of the EmailsGet request. +type EmailsGetResponse struct { + Success bool `json:"success"` + Message string `json:"message,omitempty"` + Status string `json:"status,omitempty"` +} + +// EmailsGet responds with a single email message +func EmailsGet(w http.ResponseWriter, r *http.Request) { + utils.JSONResponse(w, 200, &EmailsGetResponse{ + Success: true, + Status: "sending", + }) +} + +// EmailsUpdateResponse contains the result of the EmailsUpdate request. +type EmailsUpdateResponse struct { + Success bool `json:"success"` + Message string `json:"message"` +} + +// EmailsUpdate does *something* - TODO +func EmailsUpdate(w http.ResponseWriter, r *http.Request) { + utils.JSONResponse(w, 501, &EmailsUpdateResponse{ + Success: false, + Message: "Sorry, not implemented yet", + }) +} + +// EmailsDeleteResponse contains the result of the EmailsDelete request. +type EmailsDeleteResponse struct { + Success bool `json:"success"` + Message string `json:"message"` +} + +// EmailsDelete remvoes an email from the system +func EmailsDelete(w http.ResponseWriter, r *http.Request) { + utils.JSONResponse(w, 501, &EmailsDeleteResponse{ + Success: false, + Message: "Sorry, not implemented yet", + }) } diff --git a/routes/hello.go b/routes/hello.go index 4e33c0f..7594175 100644 --- a/routes/hello.go +++ b/routes/hello.go @@ -7,12 +7,14 @@ import ( "github.com/lavab/api/utils" ) +// HelloResponse contains the result of the Hello request. type HelloResponse struct { Message string `json:"message"` DocsURL string `json:"docs_url"` Version string `json:"version"` } +// Hello shows basic information about the API on its frontpage. func Hello(w http.ResponseWriter, r *http.Request) { utils.JSONResponse(w, 200, &HelloResponse{ Message: "Lavaboom API", diff --git a/routes/keys.go b/routes/keys.go index 5106401..12d3f68 100644 --- a/routes/keys.go +++ b/routes/keys.go @@ -5,22 +5,44 @@ import ( "net/http" ) -// Keys TODO -func Keys(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, "{\"success\":false,\"message\":\"Sorry, not implemented yet\"}") +// KeysCreateResponse contains the result of the KeysCreate request. +type KeysCreateResponse struct { + Success bool `json:"success"` + Message string `json:"message"` } -// SubmitKey TODO -func SubmitKey(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, "{\"success\":false,\"message\":\"Sorry, not implemented yet\"}") +// KeysCreate does *something* - TODO +func KeysCreate(w http.ResponseWriter, r *http.Request) { + utils.JSONResponse(w, 501, &KeysCreateResponse{ + Success: false, + Message: "Sorry, not implemented yet", + }) } -// Key TODO -func Key(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, "{\"success\":false,\"message\":\"Sorry, not implemented yet\"}") +// KeysGetResponse contains the result of the KeysGet request. +type KeysGetResponse struct { + Success bool `json:"success"` + Message string `json:"message"` } -// VoteKey TODO -func VoteKey(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, "{\"success\":false,\"message\":\"Sorry, not implemented yet\"}") +// KeysGet does *something* - TODO +func KeysGet(w http.ResponseWriter, r *http.Request) { + utils.JSONResponse(w, 501, &KeysGetResponse{ + Success: false, + Message: "Sorry, not implemented yet", + }) +} + +// KeysVoteResponse contains the result of the KeysVote request. +type KeysVoteResponse struct { + Success bool `json:"success"` + Message string `json:"message"` +} + +// KeysVote does *something* - TODO +func KeysVote(w http.ResponseWriter, r *http.Request) { + utils.JSONResponse(w, 501, &KeysVoteResponse{ + Success: false, + Message: "Sorry, not implemented yet", + }) } diff --git a/routes/labels.go b/routes/labels.go index 5a2e837..4e4dda5 100644 --- a/routes/labels.go +++ b/routes/labels.go @@ -5,27 +5,72 @@ import ( "net/http" ) -// Labels TODO -func Labels(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, "{\"success\":false,\"message\":\"Sorry, not implemented yet\"}") +// LabelsListResponse contains the result of the LabelsList request. +type LabelsListResponse struct { + Success bool `json:"success"` + Message string `json:"message"` } -// CreateLabel TODO -func CreateLabel(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, "{\"success\":false,\"message\":\"Sorry, not implemented yet\"}") +// LabelsList does *something* - TODO +func LabelsList(w http.ResponseWriter, r *http.Request) { + utils.JSONResponse(w, 501, &LabelsListResponse{ + Success: false, + Message: "Sorry, not implemented yet", + }) } -// Label TODO -func Label(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, "{\"success\":false,\"message\":\"Sorry, not implemented yet\"}") +// LabelsCreateResponse contains the result of the LabelsCreate request. +type LabelsCreateResponse struct { + Success bool `json:"success"` + Message string `json:"message"` } -// UpdateLabel TODO -func UpdateLabel(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, "{\"success\":false,\"message\":\"Sorry, not implemented yet\"}") +// LabelsCreate does *something* - TODO +func LabelsCreate(w http.ResponseWriter, r *http.Request) { + utils.JSONResponse(w, 501, &LabelsCreateResponse{ + Success: false, + Message: "Sorry, not implemented yet", + }) } -// Label TODO -func DeleteLabel(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, "{\"success\":false,\"message\":\"Sorry, not implemented yet\"}") +// LabelsGetResponse contains the result of the LabelsGet request. +type LabelsGetResponse struct { + Success bool `json:"success"` + Message string `json:"message"` +} + +// LabelsGet does *something* - TODO +func LabelsGet(w http.ResponseWriter, r *http.Request) { + utils.JSONResponse(w, 501, &LabelsGetResponse{ + Success: false, + Message: "Sorry, not implemented yet", + }) +} + +// LabelsUpdateResponse contains the result of the LabelsUpdate request. +type LabelsUpdateResponse struct { + Success bool `json:"success"` + Message string `json:"message"` +} + +// LabelsUpdate does *something* - TODO +func LabelsUpdate(w http.ResponseWriter, r *http.Request) { + utils.JSONResponse(w, 501, &LabelsUpdateResponse{ + Success: false, + Message: "Sorry, not implemented yet", + }) +} + +// LabelsDeleteResponse contains the result of the LabelsDelete request. +type LabelsDeleteResponse struct { + Success bool `json:"success"` + Message string `json:"message"` +} + +// LabelsDelete does *something* - TODO +func LabelsDelete(w http.ResponseWriter, r *http.Request) { + utils.JSONResponse(w, 501, &LabelsDeleteResponse{ + Success: false, + Message: "Sorry, not implemented yet", + }) } diff --git a/routes/threads.go b/routes/threads.go index ed4bd5a..74907cc 100644 --- a/routes/threads.go +++ b/routes/threads.go @@ -1,21 +1,49 @@ package routes import ( - "fmt" "net/http" + + "github.com/lavab/api/utils" ) -// Threads TODO -func Threads(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, "{\"success\":false,\"message\":\"Sorry, not implemented yet\"}") +// ThreadsListResponse contains the result of the ThreadsList request. +type ThreadsListResponse struct { + Success bool `json:"success"` + Message string `json:"message"` +} + +// ThreadsList shows all threads +func ThreadsList(w http.ResponseWriter, r *http.Request) { + utils.JSONResponse(w, 501, &ThreadsListResponse{ + Success: false, + Message: "Sorry, not implemented yet", + }) +} + +// ThreadsGetResponse contains the result of the ThreadsGet request. +type ThreadsGetResponse struct { + Success bool `json:"success"` + Message string `json:"message"` +} + +// ThreadsGet returns information about a single thread. +func ThreadsGet(w http.ResponseWriter, r *http.Request) { + utils.JSONResponse(w, 501, &ThreadsGetResponse{ + Success: false, + Message: "Sorry, not implemented yet", + }) } -// Thread TODO -func Thread(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, "{\"success\":false,\"message\":\"Sorry, not implemented yet\"}") +// ThreadsUpdateResponse contains the result of the ThreadsUpdate request. +type ThreadsUpdateResponse struct { + Success bool `json:"success"` + Message string `json:"message"` } -// UpdateThread TODO -func UpdateThread(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, "{\"success\":false,\"message\":\"Sorry, not implemented yet\"}") +// ThreadsUpdate does *something* with a thread. +func ThreadsUpdate(w http.ResponseWriter, r *http.Request) { + utils.JSONResponse(w, 501, &ThreadsUpdateResponse{ + Success: false, + Message: "Sorry, not implemented yet", + }) } diff --git a/routes/tokens.go b/routes/tokens.go index 1ff6392..62797c4 100644 --- a/routes/tokens.go +++ b/routes/tokens.go @@ -31,7 +31,7 @@ func TokensGet(w http.ResponseWriter, r *http.Request) { session := models.CurrentSession(r) // Respond with the token information - utils.JSONResponse(200, &TokensGetResponse{ + utils.JSONResponse(w, 200, &TokensGetResponse{ Success: true, Created: session.DateCreated, Expires: session.ExpirationDate, From 96f5dcb40125e56e107927e50561de8f22b4d73e Mon Sep 17 00:00:00 2001 From: "Piotr \"Orange\" Zduniak" Date: Fri, 7 Nov 2014 21:44:39 +0100 Subject: [PATCH 8/8] Rewrote the authentication middleware, made the code compilable --- .gitignore | 1 + auth.go | 35 ------------------------ main.go | 27 ++++++++++-------- routes/accounts.go | 14 ++++------ routes/contacts.go | 1 - routes/emails.go | 7 ++--- routes/keys.go | 3 +- routes/labels.go | 3 +- routes/middleware.go | 65 ++++++++++++++++++++++++++++++++++++++++++++ routes/tokens.go | 14 ++++------ utils/requests.go | 3 +- 11 files changed, 102 insertions(+), 71 deletions(-) delete mode 100644 auth.go create mode 100644 routes/middleware.go diff --git a/.gitignore b/.gitignore index b25c15b..627d99b 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ *~ +*.exe \ No newline at end of file diff --git a/auth.go b/auth.go deleted file mode 100644 index 4d6079d..0000000 --- a/auth.go +++ /dev/null @@ -1,35 +0,0 @@ -package main - -import ( - "net/http" - - "github.com/gorilla/context" - "github.com/lavab/api/db" - "github.com/lavab/api/dbutils" - "github.com/lavab/api/utils" -) - -// AuthWrapper is an auth middleware using the "Auth" header -// The session object gets saved in the gorilla/context map, use context.Get("session") to fetch it -func AuthWrapper(next handleFunc) handleFunc { - return func(w http.ResponseWriter, r *http.Request) { - authToken := r.Header.Get("Auth") - if authToken == "" { - utils.ErrorResponse(w, 401, "Missing auth token", "") - return - } - session, ok := dbutils.GetSession(authToken) - if !ok { - utils.ErrorResponse(w, 401, "Invalid auth token", "") - return - } - if session.HasExpired() { - utils.ErrorResponse(w, 419, "Authentication token has expired", "Session has expired on "+session.ExpDate) - db.Delete("sessions", session.ID) - return - } - - context.Set(r, "session", session) - next(w, r) - } -} diff --git a/main.go b/main.go index 43df403..d767746 100644 --- a/main.go +++ b/main.go @@ -1,22 +1,18 @@ package main import ( - "fmt" - "log" + "net" "net/http" - "os" - "strconv" - "time" "github.com/Sirupsen/logrus" "github.com/goji/glogrus" "github.com/namsral/flag" - "github.com/zenazn/goji" "github.com/zenazn/goji/graceful" "github.com/zenazn/goji/web" "github.com/zenazn/goji/web/middleware" "github.com/lavab/api/env" + "github.com/lavab/api/routes" ) // TODO: "Middleware that implements a few quick security wins" @@ -58,7 +54,7 @@ func main() { // Set up an auth'd mux auth := web.New() - mux.Use(models.AuthMiddleware) + mux.Use(routes.AuthMiddleware) // Index route mux.Get("/", routes.Hello) @@ -69,11 +65,11 @@ func main() { auth.Get("/accounts/:id", routes.AccountsGet) auth.Put("/accounts/:id", routes.AccountsUpdate) auth.Delete("/accounts/:id", routes.AccountsDelete) - auth.Post("/accounts/:id/wipe-data", routes.AccountsWipeUserData) + auth.Post("/accounts/:id/wipe-data", routes.AccountsWipeData) auth.Get("/accounts/:id/sessions", routes.AccountsSessionsList) // Tokens - auth.Get("/tokens", routes.TokenGet) + auth.Get("/tokens", routes.TokensGet) auth.Post("/tokens", routes.TokensCreate) auth.Delete("/tokens", routes.TokensDelete) @@ -115,7 +111,7 @@ func main() { mux.Compile() // Make the mux handle every request - http.Handle("/", DefaultMux) + http.Handle("/", mux) // Set up a new environment object env.G = &env.Environment{ @@ -146,8 +142,17 @@ func main() { log.Info("Stopped the application") }) + // Listen to the passed address + listener, err := net.Listen("tcp", *bindAddress) + if err != nil { + log.WithFields(logrus.Fields{ + "error": err, + "address": *bindAddress, + }).Fatal("Cannot set up a TCP listener") + } + // Start the listening - err := graceful.Serve(listener, http.DefaultServeMux) + err = graceful.Serve(listener, http.DefaultServeMux) if err != nil { // Don't use .Fatal! We need the code to shut down properly. log.Error(err) diff --git a/routes/accounts.go b/routes/accounts.go index 18543b3..29aa83d 100644 --- a/routes/accounts.go +++ b/routes/accounts.go @@ -1,9 +1,6 @@ package routes import ( - "encoding/json" - "fmt" - "log" "net/http" "github.com/Sirupsen/logrus" @@ -13,6 +10,7 @@ import ( "github.com/lavab/api/dbutils" "github.com/lavab/api/env" "github.com/lavab/api/models" + "github.com/lavab/api/models/base" "github.com/lavab/api/utils" ) @@ -51,7 +49,7 @@ func AccountsCreate(w http.ResponseWriter, r *http.Request) { if err != nil { env.G.Log.WithFields(logrus.Fields{ "error": err, - }).Warning("Unable to decode a request") + }).Warn("Unable to decode a request") utils.JSONResponse(w, 409, &AccountsCreateResponse{ Success: false, @@ -61,7 +59,7 @@ func AccountsCreate(w http.ResponseWriter, r *http.Request) { } // Ensure that the user with requested username doesn't exist - if _, ok := dbutils.FindUserByName(username); ok { + if _, ok := dbutils.FindUserByName(input.Username); ok { utils.JSONResponse(w, 409, &AccountsCreateResponse{ Success: false, Message: "Username already exists", @@ -70,7 +68,7 @@ func AccountsCreate(w http.ResponseWriter, r *http.Request) { } // Try to hash the password - hash, err := utils.BcryptHash(password) + hash, err := utils.BcryptHash(input.Password) if err != nil { utils.JSONResponse(w, 500, &AccountsCreateResponse{ Success: false, @@ -87,7 +85,7 @@ func AccountsCreate(w http.ResponseWriter, r *http.Request) { // Create a new user object user := &models.User{ - Resource: base.MakeResource(utils.UUID(), username), + Resource: base.MakeResource(utils.UUID(), input.Username), Password: string(hash), } @@ -148,7 +146,7 @@ func AccountsGet(c *web.C, w http.ResponseWriter, r *http.Request) { // The session refers to a non-existing user env.G.Log.WithFields(logrus.Fields{ "id": session.ID, - }).Warning("Valid session referred to a removed account") + }).Warn("Valid session referred to a removed account") // Try to remove the orphaned session if err := db.Delete("sessions", session.ID); err != nil { diff --git a/routes/contacts.go b/routes/contacts.go index 21a24d9..d0effbf 100644 --- a/routes/contacts.go +++ b/routes/contacts.go @@ -1,7 +1,6 @@ package routes import ( - "fmt" "net/http" "github.com/lavab/api/utils" diff --git a/routes/emails.go b/routes/emails.go index fe7d458..44c3305 100644 --- a/routes/emails.go +++ b/routes/emails.go @@ -1,7 +1,6 @@ package routes import ( - "fmt" "net/http" "github.com/lavab/api/models" @@ -19,9 +18,9 @@ type EmailsListResponse struct { // EmailsList sends a list of the emails in the inbox. func EmailsList(w http.ResponseWriter, r *http.Request) { utils.JSONResponse(w, 200, &EmailsListResponse{ - Success: true, - ItemCount: 1, - Emails: []*models.Email{}, + Success: true, + ItemsCount: 1, + Emails: []*models.Email{}, }) } diff --git a/routes/keys.go b/routes/keys.go index 12d3f68..e22732a 100644 --- a/routes/keys.go +++ b/routes/keys.go @@ -1,8 +1,9 @@ package routes import ( - "fmt" "net/http" + + "github.com/lavab/api/utils" ) // KeysCreateResponse contains the result of the KeysCreate request. diff --git a/routes/labels.go b/routes/labels.go index 4e4dda5..45d0e97 100644 --- a/routes/labels.go +++ b/routes/labels.go @@ -1,8 +1,9 @@ package routes import ( - "fmt" "net/http" + + "github.com/lavab/api/utils" ) // LabelsListResponse contains the result of the LabelsList request. diff --git a/routes/middleware.go b/routes/middleware.go new file mode 100644 index 0000000..d886fb9 --- /dev/null +++ b/routes/middleware.go @@ -0,0 +1,65 @@ +package routes + +import ( + "net/http" + "strings" + + "github.com/zenazn/goji/web" + + "github.com/lavab/api/db" + "github.com/lavab/api/dbutils" + "github.com/lavab/api/utils" +) + +type AuthMiddlewareResponse struct { + Success bool `json:"success"` + Message string `json:"message"` +} + +func AuthMiddleware(c *web.C, h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Read the Authorization header + header := r.Header.Get("Authorization") + if header == "" { + utils.JSONResponse(w, 401, &AuthMiddlewareResponse{ + Success: false, + Message: "Missing auth token", + }) + return + } + + // Split it into two parts + headerParts := strings.Split(header, " ") + if len(headerParts) != 2 || headerParts[0] != "Bearer" { + utils.JSONResponse(w, 401, &AuthMiddlewareResponse{ + Success: false, + Message: "Invalid authorization header", + }) + return + } + + // Get the session from the database + session, ok := dbutils.GetSession(headerParts[1]) + if !ok { + utils.JSONResponse(w, 401, &AuthMiddlewareResponse{ + Success: false, + Message: "Invalid authorization token", + }) + return + } + + // Check if it's expired + if session.HasExpired() { + utils.JSONResponse(w, 419, &AuthMiddlewareResponse{ + Success: false, + Message: "Authorization token has expired", + }) + db.Delete("sessions", session.ID) + return + } + + // Continue to the next middleware/route + c.Env["session"] = session + h.ServeHTTP(w, r) + }) +} diff --git a/routes/tokens.go b/routes/tokens.go index 62797c4..fe3b0e5 100644 --- a/routes/tokens.go +++ b/routes/tokens.go @@ -1,12 +1,9 @@ package routes import ( - "fmt" - "log" "net/http" - "time" - "github.com/gorilla/context" + "github.com/Sirupsen/logrus" "github.com/zenazn/goji/web" "github.com/lavab/api/db" @@ -59,7 +56,7 @@ func TokensCreate(w http.ResponseWriter, r *http.Request) { if err != nil { env.G.Log.WithFields(logrus.Fields{ "error": err, - }).Warning("Unable to decode a request") + }).Warn("Unable to decode a request") utils.JSONResponse(w, 409, &TokensCreateResponse{ Success: false, @@ -69,8 +66,8 @@ func TokensCreate(w http.ResponseWriter, r *http.Request) { } // Authenticate the user - user, ok := dbutils.FindUserByName(username) - if !ok || user == nil || !utils.BcryptVerify(user.Password, password) { + user, ok := dbutils.FindUserByName(input.Username) + if !ok || user == nil || !utils.BcryptVerify(user.Password, input.Password) { utils.JSONResponse(w, 403, &TokensCreateResponse{ Success: false, Message: "Wrong username or password", @@ -84,8 +81,7 @@ func TokensCreate(w http.ResponseWriter, r *http.Request) { // Create a new token token := &models.Session{ Expiring: base.Expiring{expDate}, - Resource: base.MakeResource(user.ID, ""), - Name: "Auth token expiring on " + expDate, + Resource: base.MakeResource(user.ID, "Auth token expiring on "+expDate), } // Insert int into the database diff --git a/utils/requests.go b/utils/requests.go index b1e5d2e..83fe3d9 100644 --- a/utils/requests.go +++ b/utils/requests.go @@ -7,6 +7,7 @@ import ( "net/http" "strings" + "github.com/Sirupsen/logrus" "github.com/gorilla/schema" "github.com/lavab/api/env" @@ -46,7 +47,7 @@ func JSONResponse(w http.ResponseWriter, status int, data interface{}) { func ParseRequest(r *http.Request, data interface{}) error { // Get the contentType for comparsions - contentType = r.Header.Get("Content-Type") + contentType := r.Header.Get("Content-Type") // Deterimine the passed ContentType if strings.Contains(contentType, "application/json") {