From 836a964fe9a2f492b7af539da629f34aa6194f1d Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Fri, 14 Nov 2014 12:05:21 +0100 Subject: [PATCH 01/11] Implemented PUT /accounts/me --- models/account.go | 2 + routes/accounts.go | 124 +++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 121 insertions(+), 5 deletions(-) diff --git a/models/account.go b/models/account.go index d30200d..8a07090 100644 --- a/models/account.go +++ b/models/account.go @@ -37,6 +37,8 @@ type Account struct { // * premium: premium account // * superuser: Lavaboom staff Type string `json:"type" gorethink:"type"` + + Email string `json:"email" gorethink:"email"` } // SetPassword changes the account's password diff --git a/routes/accounts.go b/routes/accounts.go index d57ee33..9d6a863 100644 --- a/routes/accounts.go +++ b/routes/accounts.go @@ -170,17 +170,130 @@ func AccountsGet(c web.C, w http.ResponseWriter, r *http.Request) { }) } +// AccountsUpdateRequest contains the input for the AccountsUpdate endpoint. +type AccountsUpdateRequest struct { + Type string `json:"type" schema:"type"` + Email string `json:"email" schema:"email"` + CurrentPassword string `json:"current_password" schema:"current_password"` + NewPassword string `json:"new_password" schema:"new_password"` +} + // AccountsUpdateResponse contains the result of the AccountsUpdate request. type AccountsUpdateResponse struct { - Success bool `json:"success"` - Message string `json:"message"` + Success bool `json:"success"` + Message string `json:"message"` + Account *models.Account `json:"account"` } // AccountsUpdate allows changing the account's information (password etc.) -func AccountsUpdate(w http.ResponseWriter, r *http.Request) { - utils.JSONResponse(w, 501, &AccountsUpdateResponse{ +func AccountsUpdate(c web.C, w http.ResponseWriter, r *http.Request) { + // Decode the request + var input AccountsUpdateRequest + err := utils.ParseRequest(r, &input) + if err != nil { + env.Log.WithFields(logrus.Fields{ + "error": err, + }).Warn("Unable to decode a request") + + utils.JSONResponse(w, 409, &AccountsUpdateResponse{ + Success: false, + Message: "Invalid input format", + }) + return + } + + // Get the account ID from the request + id, ok := c.URLParams["id"] + if !ok { + utils.JSONResponse(w, 409, &AccountsUpdateResponse{ + Success: false, + Message: "Invalid user ID", + }) + return + } + + // Right now we only support "me" as the ID + if id != "me" { + utils.JSONResponse(w, 501, &AccountsUpdateResponse{ + Success: false, + Message: `Only the "me" user is implemented`, + }) + return + } + + // Fetch the current session from the database + session := c.Env["session"].(*models.Token) + + // Fetch the user object from the database + user, err := env.Accounts.GetAccount(session.Owner) + if err != nil { + // The session refers to a non-existing user + env.Log.WithFields(logrus.Fields{ + "id": session.ID, + "error": err, + }).Warn("Valid session referred to a removed account") + + // Try to remove the orphaned session + if err := env.Tokens.DeleteID(session.ID); err != nil { + env.Log.WithFields(logrus.Fields{ + "id": session.ID, + "error": err, + }).Error("Unable to remove an orphaned session") + } else { + env.Log.WithFields(logrus.Fields{ + "id": session.ID, + }).Info("Removed an orphaned session") + } + + utils.JSONResponse(w, 410, &AccountsUpdateResponse{ + Success: false, + Message: "Account disabled", + }) + return + } + + if valid, _, err := user.VerifyPassword(input.CurrentPassword); err != nil || !valid { + utils.JSONResponse(w, 409, &AccountsUpdateResponse{ + Success: false, + Message: "Invalid current password", + }) + return + } + + err = user.SetPassword(input.NewPassword) + if err != nil { + env.Log.WithFields(logrus.Fields{ + "error": err, + }).Error("Unable to hash a password") + + utils.JSONResponse(w, 500, &AccountsUpdateResponse{ + Success: false, + Message: "Internal error (code AC/UP/01)", + }) + return + } + + if input.Email != "" { + user.Email = input.Email + } + + err = env.Accounts.UpdateID(session.Owner, user) + if err != nil { + env.Log.WithFields(logrus.Fields{ + "error": err, + }).Error("Unable to update an account") + + utils.JSONResponse(w, 500, &AccountsUpdateResponse{ + Success: false, + Message: "Internal error (code AC/UP/02)", + }) + return + } + + utils.JSONResponse(w, 200, &AccountsUpdateResponse{ Success: false, - Message: `Sorry, not implemented yet`, + Message: "Your account has been successfully updated", + Account: user, }) } @@ -218,6 +331,7 @@ type AccountsSessionsListResponse struct { Message string `json:"message"` } +// // AccountsSessionsList returns a list of all opened sessions. func AccountsSessionsList(w http.ResponseWriter, r *http.Request) { utils.JSONResponse(w, 501, &AccountsSessionsListResponse{ From b1a35e8f3d3b7144c2aadb8f53582d0f5413adbd Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Fri, 14 Nov 2014 12:07:36 +0100 Subject: [PATCH 02/11] Removed SessionsList (replaced with GET /tokens) --- routes/accounts.go | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/routes/accounts.go b/routes/accounts.go index 9d6a863..570dbec 100644 --- a/routes/accounts.go +++ b/routes/accounts.go @@ -324,18 +324,3 @@ func AccountsWipeData(w http.ResponseWriter, r *http.Request) { Message: `Sorry, not implemented yet`, }) } - -// 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(w, 501, &AccountsSessionsListResponse{ - Success: false, - Message: `Sorry, not implemented yet`, - }) -} From 423cbad62c313ecc221b9f9eb337b664e8336ebb Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Fri, 14 Nov 2014 12:19:06 +0100 Subject: [PATCH 03/11] Added DELETE /accounts/me --- models/account.go | 2 ++ routes/accounts.go | 72 ++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 71 insertions(+), 3 deletions(-) diff --git a/models/account.go b/models/account.go index 8a07090..8426d86 100644 --- a/models/account.go +++ b/models/account.go @@ -39,6 +39,8 @@ type Account struct { Type string `json:"type" gorethink:"type"` Email string `json:"email" gorethink:"email"` + + Status string `json:"status" gorethink:"status"` } // SetPassword changes the account's password diff --git a/routes/accounts.go b/routes/accounts.go index 570dbec..0af5d9b 100644 --- a/routes/accounts.go +++ b/routes/accounts.go @@ -304,10 +304,76 @@ type AccountsDeleteResponse struct { } // AccountsDelete allows deleting an account. -func AccountsDelete(w http.ResponseWriter, r *http.Request) { - utils.JSONResponse(w, 501, &AccountsDeleteResponse{ +func AccountsDelete(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(w, 409, &AccountsDeleteResponse{ + Success: false, + Message: "Invalid user ID", + }) + return + } + + // Right now we only support "me" as the ID + if id != "me" { + utils.JSONResponse(w, 501, &AccountsDeleteResponse{ + Success: false, + Message: `Only the "me" user is implemented`, + }) + return + } + + // Fetch the current session from the database + session := c.Env["session"].(*models.Token) + + // Fetch the user object from the database + user, err := env.Accounts.GetAccount(session.Owner) + if err != nil { + // The session refers to a non-existing user + env.Log.WithFields(logrus.Fields{ + "id": session.ID, + "error": err, + }).Warn("Valid session referred to a removed account") + + // Try to remove the orphaned session + if err := env.Tokens.DeleteID(session.ID); err != nil { + env.Log.WithFields(logrus.Fields{ + "id": session.ID, + "error": err, + }).Error("Unable to remove an orphaned session") + } else { + env.Log.WithFields(logrus.Fields{ + "id": session.ID, + }).Info("Removed an orphaned session") + } + + utils.JSONResponse(w, 410, &AccountsDeleteResponse{ + Success: false, + Message: "Account disabled", + }) + return + } + + user.Status = "suspended" + + err = env.Accounts.UpdateID(session.Owner, user) + if err != nil { + env.Log.WithFields(logrus.Fields{ + "error": err, + }).Error("Unable to update an account") + + utils.JSONResponse(w, 500, &AccountsUpdateResponse{ + Success: false, + Message: "Internal error (code AC/UP/02)", + }) + return + } + + utils.JSONResponse(w, 200, &AccountsUpdateResponse{ Success: false, - Message: `Sorry, not implemented yet`, + Message: "Your account has been successfully updated", + Account: user, }) } From aa358061e5a1d3ee2b5e0edffd116d564ba88e92 Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Fri, 14 Nov 2014 14:15:45 +0100 Subject: [PATCH 04/11] Added data wiping --- main.go | 1 - routes/accounts.go | 81 ++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 74 insertions(+), 8 deletions(-) diff --git a/main.go b/main.go index 10733c5..d0d22ef 100644 --- a/main.go +++ b/main.go @@ -150,7 +150,6 @@ func main() { auth.Put("/accounts/:id", routes.AccountsUpdate) auth.Delete("/accounts/:id", routes.AccountsDelete) auth.Post("/accounts/:id/wipe-data", routes.AccountsWipeData) - auth.Get("/accounts/:id/sessions", routes.AccountsSessionsList) // Tokens auth.Get("/tokens", routes.TokensGet) diff --git a/routes/accounts.go b/routes/accounts.go index 0af5d9b..709d99c 100644 --- a/routes/accounts.go +++ b/routes/accounts.go @@ -299,8 +299,9 @@ func AccountsUpdate(c web.C, w http.ResponseWriter, r *http.Request) { // AccountsDeleteResponse contains the result of the AccountsDelete request. type AccountsDeleteResponse struct { - Success bool `json:"success"` - Message string `json:"message"` + Success bool `json:"success"` + Message string `json:"message"` + Account *models.Account `json:"account"` } // AccountsDelete allows deleting an account. @@ -363,14 +364,14 @@ func AccountsDelete(c web.C, w http.ResponseWriter, r *http.Request) { "error": err, }).Error("Unable to update an account") - utils.JSONResponse(w, 500, &AccountsUpdateResponse{ + utils.JSONResponse(w, 500, &AccountsDeleteResponse{ Success: false, Message: "Internal error (code AC/UP/02)", }) return } - utils.JSONResponse(w, 200, &AccountsUpdateResponse{ + utils.JSONResponse(w, 200, &AccountsDeleteResponse{ Success: false, Message: "Your account has been successfully updated", Account: user, @@ -384,9 +385,75 @@ 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(w, 501, &AccountsWipeDataResponse{ +func AccountsWipeData(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(w, 409, &AccountsWipeDataResponse{ + Success: false, + Message: "Invalid user ID", + }) + return + } + + // Right now we only support "me" as the ID + if id != "me" { + utils.JSONResponse(w, 501, &AccountsWipeDataResponse{ + Success: false, + Message: `Only the "me" user is implemented`, + }) + return + } + + // Fetch the current session from the database + session := c.Env["session"].(*models.Token) + + // Fetch the user object from the database + user, err := env.Accounts.GetAccount(session.Owner) + if err != nil { + // The session refers to a non-existing user + env.Log.WithFields(logrus.Fields{ + "id": session.ID, + "error": err, + }).Warn("Valid session referred to a removed account") + + // Try to remove the orphaned session + if err := env.Tokens.DeleteID(session.ID); err != nil { + env.Log.WithFields(logrus.Fields{ + "id": session.ID, + "error": err, + }).Error("Unable to remove an orphaned session") + } else { + env.Log.WithFields(logrus.Fields{ + "id": session.ID, + }).Info("Removed an orphaned session") + } + + utils.JSONResponse(w, 410, &AccountsWipeDataResponse{ + Success: false, + Message: "Account disabled", + }) + return + } + + user.Status = "delete" + + err = env.Accounts.UpdateID(session.Owner, user) + if err != nil { + env.Log.WithFields(logrus.Fields{ + "error": err, + }).Error("Unable to update an account") + + utils.JSONResponse(w, 500, &AccountsWipeDataResponse{ + Success: false, + Message: "Internal error (code AC/UP/02)", + }) + return + } + + utils.JSONResponse(w, 200, &AccountsDeleteResponse{ Success: false, - Message: `Sorry, not implemented yet`, + Message: "Your account has been successfully updated", + Account: user, }) } From 7a425fc628b67601589ee8c42197ca1478311905 Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Sun, 16 Nov 2014 13:49:51 +0100 Subject: [PATCH 05/11] Updated POST /accounts --- models/account.go | 2 +- routes/accounts.go | 130 ++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 118 insertions(+), 14 deletions(-) diff --git a/models/account.go b/models/account.go index 8426d86..aa834f1 100644 --- a/models/account.go +++ b/models/account.go @@ -38,7 +38,7 @@ type Account struct { // * superuser: Lavaboom staff Type string `json:"type" gorethink:"type"` - Email string `json:"email" gorethink:"email"` + AltEmail string `json:"alt_email" gorethink:"alt_email"` Status string `json:"status" gorethink:"status"` } diff --git a/routes/accounts.go b/routes/accounts.go index 709d99c..44e2f54 100644 --- a/routes/accounts.go +++ b/routes/accounts.go @@ -27,8 +27,10 @@ func AccountsList(w http.ResponseWriter, r *http.Request) { // AccountsCreateRequest contains the input for the AccountsCreate endpoint. type AccountsCreateRequest struct { + Token string `json:"token" schema:"token"` Username string `json:"username" schema:"username"` Password string `json:"password" schema:"password"` + AltEmail string `json:"alt_email" schema:"alt_email"` } // AccountsCreateResponse contains the output of the AccountsCreate request. @@ -48,14 +50,83 @@ func AccountsCreate(w http.ResponseWriter, r *http.Request) { "error": err, }).Warn("Unable to decode a request") - utils.JSONResponse(w, 409, &AccountsCreateResponse{ + utils.JSONResponse(w, 400, &AccountsCreateResponse{ Success: false, Message: "Invalid input format", }) return } - // Ensure that the user with requested username doesn't exist + // Detect the request type + // 1) username + token + password - invite + // 2) username + password + alt_email - register with confirmation + // 3) alt_email only - register for beta (add to queue) + requestType := "unknown" + if input.AltEmail == "" && input.Username != "" && input.Password != "" && input.Token != "" { + requestType = "invited" + } else if input.AltEmail != "" && input.Username != "" && input.Password != "" && input.Token != "" { + requestType = "classic" + } else if input.AltEmail != "" && input.Username == "" && input.Password == "" && input.Token == "" { + requestType = "queue" + } + + // "unknown" requests are empty and invalid + if requestType == "invalid" { + utils.JSONResponse(w, 400, &AccountsCreateResponse{ + Success: false, + Message: "Invalid request", + }) + return + } + + // Adding to queue will be implemented soon + if requestType == "queue" { + // Implementation awaits https://trello.com/c/SLM0qK1O/91-account-registration-queue + utils.JSONResponse(w, 501, &AccountsCreateResponse{ + Success: false, + Message: "Sorry, not implemented yet", + }) + return + } + + // Check "invited" for token validity + if requestType == "invited" { + // Fetch the token from the database + token, err := env.Tokens.GetToken(input.Token) + if err != nil { + env.Log.WithFields(logrus.Fields{ + "error": err, + }).Warn("Unable to fetch a registration token from the database") + + utils.JSONResponse(w, 400, &AccountsCreateResponse{ + Success: false, + Message: "Invalid invitation token", + }) + return + } + + // Ensure that the token's type is valid + if token.Type != "invite" { + utils.JSONResponse(w, 400, &AccountsCreateResponse{ + Success: false, + Message: "Invalid invitation token", + }) + return + } + + // Check if it's expired + if token.Expired() { + utils.JSONResponse(w, 400, &AccountsCreateResponse{ + Success: false, + Message: "Expired invitation token", + }) + return + } + } + + // TODO: sanitize user name (i.e. remove caps, periods) + + // Both invited and classic require an unique username, so ensure that the user with requested username isn't already used if _, err := env.Accounts.FindAccountByName(input.Username); err == nil { utils.JSONResponse(w, 409, &AccountsCreateResponse{ Success: false, @@ -64,13 +135,12 @@ func AccountsCreate(w http.ResponseWriter, r *http.Request) { return } - // TODO: sanitize user name (i.e. remove caps, periods) - - // Create a new user object + // Both username and password are filled, so we can create a new account. account := &models.Account{ Resource: models.MakeResource("", input.Username), } + // Set the password err = account.SetPassword(input.Password) if err != nil { utils.JSONResponse(w, 500, &AccountsCreateResponse{ @@ -84,6 +154,16 @@ func AccountsCreate(w http.ResponseWriter, r *http.Request) { return } + // User won't be able to log in until the account gets verified + if requestType == "classic" { + account.Status = "unverified" + } + + // Set the status to invited, because of stats + if requestType == "invited" { + account.Status = "invited" + } + // Try to save it in the database if err := env.Accounts.Insert(account); err != nil { utils.JSONResponse(w, 500, &AccountsCreateResponse{ @@ -97,11 +177,35 @@ func AccountsCreate(w http.ResponseWriter, r *http.Request) { return } - utils.JSONResponse(w, 201, &AccountsCreateResponse{ - Success: true, - Message: "A new account was successfully created", - Account: account, - }) + // Send the email if classic and return a response + if requestType == "classic" { + // TODO: Send emails + + utils.JSONResponse(w, 201, &AccountsCreateResponse{ + Success: true, + Message: "A new account was successfully created, you should receive a confirmation email soon™.", + Account: account, + }) + return + } + + // Remove the token and return a response + if requestType == "invited" { + err := env.Tokens.DeleteID(input.Token) + if err != nil { + env.Log.WithFields(logrus.Fields{ + "error": err, + "id": input.Token, + }).Error("Could not remove token from database") + } + + utils.JSONResponse(w, 201, &AccountsCreateResponse{ + Success: true, + Message: "A new account was successfully created", + Account: account, + }) + return + } } // AccountsGetResponse contains the result of the AccountsGet request. @@ -173,7 +277,7 @@ func AccountsGet(c web.C, w http.ResponseWriter, r *http.Request) { // AccountsUpdateRequest contains the input for the AccountsUpdate endpoint. type AccountsUpdateRequest struct { Type string `json:"type" schema:"type"` - Email string `json:"email" schema:"email"` + AltEmail string `json:"alt_email" schema:"alt_email"` CurrentPassword string `json:"current_password" schema:"current_password"` NewPassword string `json:"new_password" schema:"new_password"` } @@ -273,8 +377,8 @@ func AccountsUpdate(c web.C, w http.ResponseWriter, r *http.Request) { return } - if input.Email != "" { - user.Email = input.Email + if input.AltEmail != "" { + user.AltEmail = input.AltEmail } err = env.Accounts.UpdateID(session.Owner, user) From f769da505f6a966ff2f318e991e8391d56c64dcf Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Sun, 16 Nov 2014 13:53:54 +0100 Subject: [PATCH 06/11] Added a classic registration toggle --- env/config.go | 9 +++++---- main.go | 11 +++++++---- routes/accounts.go | 9 +++++++++ 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/env/config.go b/env/config.go index 4944b29..40d8551 100644 --- a/env/config.go +++ b/env/config.go @@ -2,8 +2,9 @@ package env // Flags contains values of flags which are important in the whole API type Flags struct { - BindAddress string - APIVersion string - LogFormatterType string - SessionDuration int + BindAddress string + APIVersion string + LogFormatterType string + SessionDuration int + ClassicRegistration bool } diff --git a/main.go b/main.go index d0d22ef..a48f7e1 100644 --- a/main.go +++ b/main.go @@ -29,6 +29,8 @@ var ( logFormatterType = flag.String("log", "text", "Log formatter type. Either \"json\" or \"text\"") sessionDuration = flag.Int("session_duration", 72, "Session duration expressed in hours") forceColors = flag.Bool("force_colors", false, "Force colored prompt?") + // Registration settings + classicRegistration = flag.Bool("classic_registartion", false, "Classic registration enabled?") // Database-related flags rethinkdbURL = flag.String("rethinkdb_url", func() string { address := os.Getenv("RETHINKDB_PORT_28015_TCP_ADDR") @@ -53,10 +55,11 @@ func main() { // Put config into the environment package env.Config = &env.Flags{ - BindAddress: *bindAddress, - APIVersion: *apiVersion, - LogFormatterType: *logFormatterType, - SessionDuration: *sessionDuration, + BindAddress: *bindAddress, + APIVersion: *apiVersion, + LogFormatterType: *logFormatterType, + SessionDuration: *sessionDuration, + ClassicRegistration: *classicRegistration, } // Set up a new logger diff --git a/routes/accounts.go b/routes/accounts.go index 44e2f54..9fbad1c 100644 --- a/routes/accounts.go +++ b/routes/accounts.go @@ -89,6 +89,15 @@ func AccountsCreate(w http.ResponseWriter, r *http.Request) { return } + // Check if classic registration is enabled + if requestType == "classic" && !env.Config.ClassicRegistration { + utils.JSONResponse(w, 403, &AccountsCreateResponse{ + Success: false, + Message: "Classic registration is disabled", + }) + return + } + // Check "invited" for token validity if requestType == "invited" { // Fetch the token from the database From 0b247dacbe2586eafff49d4289c62c72793fe82d Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Sun, 16 Nov 2014 14:04:44 +0100 Subject: [PATCH 07/11] Fixed a typo in a flag's name, added a new section to README.md --- README.md | 44 ++++++++++++++++++++++++++++++++++++++++++++ main.go | 2 +- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5a7b457..923460f 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,50 @@ curl --data "username=abc&password=def" localhost:5000/login curl --header "Auth: " localhost:5000/me ``` +## Configuration variables + +You can use either commandline flags: +``` +{ api } master » ./api -help +Usage of api: + -bind=":5000": Network address used to bind + -classic_registration=false: Classic registration enabled? + -force_colors=false: Force colored prompt? + -log="text": Log formatter type. Either "json" or "text" + -rethinkdb_db="dev": Database name on the RethinkDB server + -rethinkdb_key="": Authentication key of the RethinkDB database + -rethinkdb_url="localhost:28015": Address of the RethinkDB database + -session_duration=72: Session duration expressed in hours + -version="v0": Shown API version +``` + +Or environment variables: +``` +{ api } master » BIND=127.0.0.1:5000 CLASSIC_REGISTRATION=false \ +FORCE_COLORS=false LOG=text RETHINKDB_DB=dev RETHINKDB_KEY="" \ +RETHINKDB_URL=localhost:28015 SESSION_DURATION=72 VERSION=v0 ./api +``` + +Or configuration files: +``` +{ api } master » cat api.conf +# lines beggining with a "#" character are treated as comments +bind :5000 +classic_registration false +force_colors false +log text + +rethinkdb_db dev +# configuration values can be empty +rethinkdb_key +# Keys and values can be also seperated by the "=" character +rethinkdb_url=localhost:28015 + +session_duration=72 +version=v0 +{ api } master » ./api -config api.conf +``` + ## Build status: - `master` - [![Build Status](https://magnum.travis-ci.com/lavab/api.svg?token=kJbppXeTxzqpCVvt4t5X&branch=master)](https://magnum.travis-ci.com/lavab/api) diff --git a/main.go b/main.go index a48f7e1..ac10261 100644 --- a/main.go +++ b/main.go @@ -30,7 +30,7 @@ var ( sessionDuration = flag.Int("session_duration", 72, "Session duration expressed in hours") forceColors = flag.Bool("force_colors", false, "Force colored prompt?") // Registration settings - classicRegistration = flag.Bool("classic_registartion", false, "Classic registration enabled?") + classicRegistration = flag.Bool("classic_registration", false, "Classic registration enabled?") // Database-related flags rethinkdbURL = flag.String("rethinkdb_url", func() string { address := os.Getenv("RETHINKDB_PORT_28015_TCP_ADDR") From 0c2158fd801696ba94352c56ebe13a202dd5b78b Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Sun, 16 Nov 2014 14:33:19 +0100 Subject: [PATCH 08/11] Setting the default account type to beta --- routes/accounts.go | 1 + 1 file changed, 1 insertion(+) diff --git a/routes/accounts.go b/routes/accounts.go index 9fbad1c..b105cb6 100644 --- a/routes/accounts.go +++ b/routes/accounts.go @@ -147,6 +147,7 @@ func AccountsCreate(w http.ResponseWriter, r *http.Request) { // Both username and password are filled, so we can create a new account. account := &models.Account{ Resource: models.MakeResource("", input.Username), + Type: "beta", } // Set the password From 24ea2051774e9ce696be0e24df813d9129b39a08 Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Sun, 16 Nov 2014 16:16:00 +0100 Subject: [PATCH 09/11] Added parts of account deletion and wipes --- db/table_tokens.go | 7 +++++ routes/accounts.go | 67 ++++++++++++++++++++++++++++++++-------------- 2 files changed, 54 insertions(+), 20 deletions(-) diff --git a/db/table_tokens.go b/db/table_tokens.go index ddb9cd5..83f54f0 100644 --- a/db/table_tokens.go +++ b/db/table_tokens.go @@ -19,3 +19,10 @@ func (t *TokensTable) GetToken(id string) (*models.Token, error) { return &result, nil } + +// DeleteByOwner deletes all tokens owned by id +func (t *TokensTable) DeleteByOwner(id string) error { + return t.Delete(map[string]interface{}{ + "owner": id, + }) +} diff --git a/routes/accounts.go b/routes/accounts.go index b105cb6..7b77b64 100644 --- a/routes/accounts.go +++ b/routes/accounts.go @@ -413,12 +413,11 @@ func AccountsUpdate(c web.C, w http.ResponseWriter, r *http.Request) { // AccountsDeleteResponse contains the result of the AccountsDelete request. type AccountsDeleteResponse struct { - Success bool `json:"success"` - Message string `json:"message"` - Account *models.Account `json:"account"` + Success bool `json:"success"` + Message string `json:"message"` } -// AccountsDelete allows deleting an account. +// AccountsDelete deletes an account and everything related to it. func AccountsDelete(c web.C, w http.ResponseWriter, r *http.Request) { // Get the account ID from the request id, ok := c.URLParams["id"] @@ -470,25 +469,46 @@ func AccountsDelete(c web.C, w http.ResponseWriter, r *http.Request) { return } - user.Status = "suspended" + // TODO: Delete contacts - err = env.Accounts.UpdateID(session.Owner, user) + // TODO: Delete emails + + // TODO: Delete labels + + // TODO: Delete threads + + // Delete tokens + err = env.Tokens.DeleteByOwner(user.ID) if err != nil { env.Log.WithFields(logrus.Fields{ + "id": user.ID, "error": err, - }).Error("Unable to update an account") + }).Error("Unable to remove account's tokens") utils.JSONResponse(w, 500, &AccountsDeleteResponse{ Success: false, - Message: "Internal error (code AC/UP/02)", + Message: "Internal error (code AC/DE/05)", + }) + return + } + + // Delete account + err = env.Accounts.DeleteID(user.ID) + if err != nil { + env.Log.WithFields(logrus.Fields{ + "error": err, + }).Error("Unable to delete an account") + + utils.JSONResponse(w, 500, &AccountsDeleteResponse{ + Success: false, + Message: "Internal error (code AC/DE/06)", }) return } utils.JSONResponse(w, 200, &AccountsDeleteResponse{ - Success: false, - Message: "Your account has been successfully updated", - Account: user, + Success: true, + Message: "Your account has been successfully deleted", }) } @@ -498,7 +518,7 @@ type AccountsWipeDataResponse struct { Message string `json:"message"` } -// AccountsWipeData allows getting rid of the all data related to the account. +// AccountsWipeData wipes all data except the actual account and billing info. func AccountsWipeData(c web.C, w http.ResponseWriter, r *http.Request) { // Get the account ID from the request id, ok := c.URLParams["id"] @@ -550,24 +570,31 @@ func AccountsWipeData(c web.C, w http.ResponseWriter, r *http.Request) { return } - user.Status = "delete" + // TODO: Delete contacts - err = env.Accounts.UpdateID(session.Owner, user) + // TODO: Delete emails + + // TODO: Delete labels + + // TODO: Delete threads + + // Delete tokens + err = env.Tokens.DeleteByOwner(user.ID) if err != nil { env.Log.WithFields(logrus.Fields{ + "id": user.ID, "error": err, - }).Error("Unable to update an account") + }).Error("Unable to remove account's tokens") utils.JSONResponse(w, 500, &AccountsWipeDataResponse{ Success: false, - Message: "Internal error (code AC/UP/02)", + Message: "Internal error (code AC/WD/05)", }) return } - utils.JSONResponse(w, 200, &AccountsDeleteResponse{ - Success: false, - Message: "Your account has been successfully updated", - Account: user, + utils.JSONResponse(w, 200, &AccountsWipeDataResponse{ + Success: true, + Message: "Your account has been successfully wiped", }) } From d4f288ef95c81ac6b13930cbd993d5ac4af5d8a7 Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Sun, 16 Nov 2014 16:39:40 +0100 Subject: [PATCH 10/11] Added DELETE /tokens/:id --- main.go | 1 + routes/tokens.go | 34 ++++++++++++++++++++++++++++------ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/main.go b/main.go index ac10261..9cbcef4 100644 --- a/main.go +++ b/main.go @@ -158,6 +158,7 @@ func main() { auth.Get("/tokens", routes.TokensGet) mux.Post("/tokens", routes.TokensCreate) auth.Delete("/tokens", routes.TokensDelete) + auth.Delete("/tokens/:id", routes.TokensDelete) // Threads auth.Get("/threads", routes.ThreadsList) diff --git a/routes/tokens.go b/routes/tokens.go index 346da3c..912c7ae 100644 --- a/routes/tokens.go +++ b/routes/tokens.go @@ -131,20 +131,42 @@ type TokensDeleteResponse struct { Message string `json:"message"` } -// TokensDelete destroys the current session token. +// TokensDelete destroys either the current auth token or the one passed as an URL param func TokensDelete(c web.C, w http.ResponseWriter, r *http.Request) { - // Get the session from the middleware - session := c.Env["session"].(*models.Token) + // Initialize + var ( + token *models.Token + err error + ) + + id, ok := c.URLParams["id"] + if !ok || id == "" { + // Get the token from the middleware + token = c.Env["session"].(*models.Token) + } else { + token, err = env.Tokens.GetToken(id) + if err != nil { + env.Log.WithFields(logrus.Fields{ + "error": err, + "id": id, + }).Warn("Unable to find the token") + + utils.JSONResponse(w, 500, &TokensDeleteResponse{ + Success: true, + Message: "Internal server error - TO/DE/01", + }) + } + } // Delete it from the database - if err := env.Tokens.DeleteID(session.ID); err != nil { + if err := env.Tokens.DeleteID(token.ID); err != nil { env.Log.WithFields(logrus.Fields{ "error": err, - }).Error("Unable to delete a session") + }).Error("Unable to delete a token") utils.JSONResponse(w, 500, &TokensDeleteResponse{ Success: true, - Message: "Internal server error - TO/DE/01", + Message: "Internal server error - TO/DE/02", }) return } From 244f190ba83c2f9d9afb40c1c2ea3250cfa865af Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Sun, 16 Nov 2014 16:43:56 +0100 Subject: [PATCH 11/11] Forgot about a return --- routes/tokens.go | 1 + 1 file changed, 1 insertion(+) diff --git a/routes/tokens.go b/routes/tokens.go index 912c7ae..bfc1e8a 100644 --- a/routes/tokens.go +++ b/routes/tokens.go @@ -155,6 +155,7 @@ func TokensDelete(c web.C, w http.ResponseWriter, r *http.Request) { Success: true, Message: "Internal server error - TO/DE/01", }) + return } }