// LabelsCreate does *something* - TODO func LabelsCreate(c web.C, w http.ResponseWriter, req *http.Request) { // Decode the request var input LabelsCreateRequest err := utils.ParseRequest(req, &input) if err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), }).Warn("Unable to decode a request") utils.JSONResponse(w, 400, &LabelsCreateResponse{ Success: false, Message: "Invalid input format", }) return } // Fetch the current session from the middleware session := c.Env["token"].(*models.Token) // Ensure that the input data isn't empty if input.Name == "" { utils.JSONResponse(w, 400, &LabelsCreateResponse{ Success: false, Message: "Invalid request", }) return } if _, err := env.Labels.GetLabelByNameAndOwner(session.Owner, input.Name); err == nil { utils.JSONResponse(w, 409, &LabelsCreateResponse{ Success: false, Message: "Label with such name already exists", }) return } // Create a new label struct label := &models.Label{ Resource: models.MakeResource(session.Owner, input.Name), Builtin: false, } // Insert the label into the database if err := env.Labels.Insert(label); err != nil { utils.JSONResponse(w, 500, &LabelsCreateResponse{ Success: false, Message: "internal server error - LA/CR/01", }) env.Log.WithFields(logrus.Fields{ "error": err.Error(), }).Error("Could not insert a label into the database") return } utils.JSONResponse(w, 201, &LabelsCreateResponse{ Success: true, Label: label, }) }
// FilesCreate creates a new file func FilesCreate(c web.C, w http.ResponseWriter, r *http.Request) { // Decode the request var input FilesCreateRequest err := utils.ParseRequest(r, &input) if err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), }).Warn("Unable to decode a request") utils.JSONResponse(w, 400, &FilesCreateResponse{ Success: false, Message: "Invalid input format", }) return } // Fetch the current session from the middleware session := c.Env["token"].(*models.Token) // Ensure that the input data isn't empty if input.Data == "" || input.Name == "" || input.Encoding == "" { utils.JSONResponse(w, 400, &FilesCreateResponse{ Success: false, Message: "Invalid request", }) return } // Create a new file struct file := &models.File{ Encrypted: models.Encrypted{ Encoding: input.Encoding, Data: input.Data, Schema: "file", VersionMajor: input.VersionMajor, VersionMinor: input.VersionMinor, PGPFingerprints: input.PGPFingerprints, }, Resource: models.MakeResource(session.Owner, input.Name), } // Insert the file into the database if err := env.Files.Insert(file); err != nil { utils.JSONResponse(w, 500, &FilesCreateResponse{ Success: false, Message: "internal server error - FI/CR/01", }) env.Log.WithFields(logrus.Fields{ "error": err.Error(), }).Error("Could not insert a file into the database") return } utils.JSONResponse(w, 201, &FilesCreateResponse{ Success: true, Message: "A new file was successfully created", File: file, }) }
func TestContactsRoute(t *testing.T) { Convey("Given a working account", t, func() { account := &models.Account{ Resource: models.MakeResource("", "johnorange2"), Status: "complete", AltEmail: "*****@*****.**", } err := account.SetPassword("fruityloops") So(err, ShouldBeNil) err = env.Accounts.Insert(account) So(err, ShouldBeNil) result, err := goreq.Request{ Method: "POST", Uri: server.URL + "/tokens", ContentType: "application/json", Body: `{ "type": "auth", "username": "******", "password": "******" }`, }.Do() So(err, ShouldBeNil) var response routes.TokensCreateResponse err = result.Body.FromJsonTo(&response) So(err, ShouldBeNil) So(response.Success, ShouldBeTrue) authToken := response.Token Convey("Creating a contact with missing parts should fail", func() { request := goreq.Request{ Method: "POST", Uri: server.URL + "/contacts", ContentType: "application/json", Body: `{ "data": "` + uniuri.NewLen(64) + `", "encoding": "json", "version_major": 1, "version_minor": 0, "pgp_fingerprints": ["` + uniuri.New() + `"] }`, } request.AddHeader("Authorization", "Bearer "+authToken.ID) result, err := request.Do() So(err, ShouldBeNil) var response routes.ContactsCreateResponse err = result.Body.FromJsonTo(&response) So(err, ShouldBeNil) So(response.Message, ShouldEqual, "Invalid request") So(response.Success, ShouldBeFalse) }) Convey("Creating a contact with invalid input data should fail", func() { request := goreq.Request{ Method: "POST", Uri: server.URL + "/contacts", ContentType: "application/json", Body: "!@#!@#!@#", } request.AddHeader("Authorization", "Bearer "+authToken.ID) result, err := request.Do() So(err, ShouldBeNil) var response routes.ContactsCreateResponse err = result.Body.FromJsonTo(&response) So(err, ShouldBeNil) So(response.Message, ShouldEqual, "Invalid input format") So(response.Success, ShouldBeFalse) }) Convey("Getting a non-owned contact should fail", func() { contact := &models.Contact{ Encrypted: models.Encrypted{ Encoding: "json", Data: uniuri.NewLen(64), Schema: "contact", VersionMajor: 1, VersionMinor: 0, }, Resource: models.MakeResource("not", uniuri.New()), } err := env.Contacts.Insert(contact) So(err, ShouldBeNil) request := goreq.Request{ Method: "GET", Uri: server.URL + "/contacts/" + contact.ID, } request.AddHeader("Authorization", "Bearer "+authToken.ID) result, err := request.Do() So(err, ShouldBeNil) var response routes.ContactsGetResponse err = result.Body.FromJsonTo(&response) So(err, ShouldBeNil) So(response.Success, ShouldBeFalse) So(response.Message, ShouldEqual, "Contact not found") Convey("Update of a not-owned contact should fail", func() { request := goreq.Request{ Method: "PUT", Uri: server.URL + "/contacts/" + contact.ID, ContentType: "application/json", Body: `{ "data": "` + uniuri.NewLen(64) + `", "name": "` + uniuri.New() + `", "encoding": "xml", "version_major": 8, "version_minor": 3 }`, } request.AddHeader("Authorization", "Bearer "+authToken.ID) result, err := request.Do() So(err, ShouldBeNil) var response routes.ContactsUpdateResponse err = result.Body.FromJsonTo(&response) So(err, ShouldBeNil) So(response.Success, ShouldBeFalse) So(response.Message, ShouldEqual, "Contact not found") }) Convey("Deleting it should fail", func() { request := goreq.Request{ Method: "DELETE", Uri: server.URL + "/contacts/" + contact.ID, } request.AddHeader("Authorization", "Bearer "+authToken.ID) result, err := request.Do() So(err, ShouldBeNil) var response routes.ContactsDeleteResponse err = result.Body.FromJsonTo(&response) So(err, ShouldBeNil) So(response.Success, ShouldBeFalse) So(response.Message, ShouldEqual, "Contact not found") }) }) Convey("Getting a non-existing contact should fail", func() { request := goreq.Request{ Method: "GET", Uri: server.URL + "/contacts/" + uniuri.New(), } request.AddHeader("Authorization", "Bearer "+authToken.ID) result, err := request.Do() So(err, ShouldBeNil) var response routes.ContactsGetResponse err = result.Body.FromJsonTo(&response) So(err, ShouldBeNil) So(response.Success, ShouldBeFalse) So(response.Message, ShouldEqual, "Contact not found") }) Convey("Creating a contact should succeed", func() { request := goreq.Request{ Method: "POST", Uri: server.URL + "/contacts", ContentType: "application/json", Body: `{ "data": "` + uniuri.NewLen(64) + `", "name": "` + uniuri.New() + `", "encoding": "json", "version_major": 1, "version_minor": 0, "pgp_fingerprints": ["` + uniuri.New() + `"] }`, } request.AddHeader("Authorization", "Bearer "+authToken.ID) result, err := request.Do() So(err, ShouldBeNil) var response routes.ContactsCreateResponse err = result.Body.FromJsonTo(&response) So(err, ShouldBeNil) So(response.Message, ShouldEqual, "A new contact was successfully created") So(response.Success, ShouldBeTrue) So(response.Contact.ID, ShouldNotBeNil) contact := response.Contact Convey("The contact should be visible on the list", func() { request := goreq.Request{ Method: "GET", Uri: server.URL + "/contacts", } request.AddHeader("Authorization", "Bearer "+authToken.ID) result, err := request.Do() So(err, ShouldBeNil) var response routes.ContactsListResponse err = result.Body.FromJsonTo(&response) So(err, ShouldBeNil) So(len(*response.Contacts), ShouldBeGreaterThan, 0) So(response.Success, ShouldBeTrue) found := false for _, c := range *response.Contacts { if c.ID == contact.ID { found = true break } } So(found, ShouldBeTrue) }) Convey("Getting that contact should succeed", func() { request := goreq.Request{ Method: "GET", Uri: server.URL + "/contacts/" + contact.ID, } request.AddHeader("Authorization", "Bearer "+authToken.ID) result, err := request.Do() So(err, ShouldBeNil) var response routes.ContactsGetResponse err = result.Body.FromJsonTo(&response) So(err, ShouldBeNil) So(response.Success, ShouldBeTrue) So(response.Contact.Name, ShouldEqual, contact.Name) }) Convey("Updating that contact should succeed", func() { newName := uniuri.New() request := goreq.Request{ Method: "PUT", Uri: server.URL + "/contacts/" + contact.ID, ContentType: "application/json", Body: `{ "data": "` + uniuri.NewLen(64) + `", "name": "` + newName + `", "encoding": "xml", "version_major": 8, "version_minor": 3 }`, } request.AddHeader("Authorization", "Bearer "+authToken.ID) result, err := request.Do() So(err, ShouldBeNil) var response routes.ContactsUpdateResponse err = result.Body.FromJsonTo(&response) So(err, ShouldBeNil) So(response.Success, ShouldBeTrue) So(response.Contact.Name, ShouldEqual, newName) }) Convey("Deleting that contact should succeed", func() { request := goreq.Request{ Method: "DELETE", Uri: server.URL + "/contacts/" + contact.ID, } request.AddHeader("Authorization", "Bearer "+authToken.ID) result, err := request.Do() So(err, ShouldBeNil) var response routes.ContactsDeleteResponse err = result.Body.FromJsonTo(&response) So(err, ShouldBeNil) So(response.Success, ShouldBeTrue) So(response.Message, ShouldEqual, "Contact successfully removed") }) }) Convey("Update with invalid input should fail", func() { request := goreq.Request{ Method: "PUT", Uri: server.URL + "/contacts/" + uniuri.New(), ContentType: "application/json", Body: "123123!@#!@#", } request.AddHeader("Authorization", "Bearer "+authToken.ID) result, err := request.Do() So(err, ShouldBeNil) var response routes.ContactsUpdateResponse err = result.Body.FromJsonTo(&response) So(err, ShouldBeNil) So(response.Success, ShouldBeFalse) So(response.Message, ShouldEqual, "Invalid input format") }) Convey("Update of a non-existing contact should fail", func() { request := goreq.Request{ Method: "PUT", Uri: server.URL + "/contacts/gibberish", ContentType: "application/json", Body: `{ "data": "` + uniuri.NewLen(64) + `", "name": "` + uniuri.New() + `", "encoding": "xml", "version_major": 8, "version_minor": 3 }`, } request.AddHeader("Authorization", "Bearer "+authToken.ID) result, err := request.Do() So(err, ShouldBeNil) var response routes.ContactsUpdateResponse err = result.Body.FromJsonTo(&response) So(err, ShouldBeNil) So(response.Success, ShouldBeFalse) So(response.Message, ShouldEqual, "Contact not found") }) Convey("Deleting a non-existing contact should fail", func() { request := goreq.Request{ Method: "DELETE", Uri: server.URL + "/contacts/gibberish", } request.AddHeader("Authorization", "Bearer "+authToken.ID) result, err := request.Do() So(err, ShouldBeNil) var response routes.ContactsDeleteResponse err = result.Body.FromJsonTo(&response) So(err, ShouldBeNil) So(response.Success, ShouldBeFalse) So(response.Message, ShouldEqual, "Contact not found") }) }) }
func TestKeysRoute(t *testing.T) { Convey("Given a working account", t, func() { account := &models.Account{ Resource: models.MakeResource("", "johnorange4"), Status: "complete", AltEmail: "*****@*****.**", } err := account.SetPassword("fruityloops") So(err, ShouldBeNil) err = env.Accounts.Insert(account) So(err, ShouldBeNil) result, err := goreq.Request{ Method: "POST", Uri: server.URL + "/tokens", ContentType: "application/json", Body: `{ "type": "auth", "username": "******", "password": "******" }`, }.Do() So(err, ShouldBeNil) var response routes.TokensCreateResponse err = result.Body.FromJsonTo(&response) So(err, ShouldBeNil) So(response.Success, ShouldBeTrue) authToken := response.Token Convey("Uploading a new key using an invalid JSON format should fail", func() { request := goreq.Request{ Method: "POST", Uri: server.URL + "/keys", ContentType: "application/json", Body: "!@#!@!@#", } request.AddHeader("Authorization", "Bearer "+authToken.ID) result, err := request.Do() So(err, ShouldBeNil) var response routes.KeysCreateResponse err = result.Body.FromJsonTo(&response) So(err, ShouldBeNil) So(response.Message, ShouldEqual, "Invalid input format") So(response.Success, ShouldBeFalse) }) Convey("Uploading an invalid key should fail", func() { request := goreq.Request{ Method: "POST", Uri: server.URL + "/keys", ContentType: "application/json", Body: `{ "key": "hbnjmvnbhvm nbhm jhbjmnghnbgjvgbhvf bgvmj gvhnft" }`, } request.AddHeader("Authorization", "Bearer "+authToken.ID) result, err := request.Do() So(err, ShouldBeNil) var response routes.KeysCreateResponse err = result.Body.FromJsonTo(&response) So(err, ShouldBeNil) So(response.Message, ShouldEqual, "Invalid key format") So(response.Success, ShouldBeFalse) }) Convey("Uploading a key should succeed", func() { request := goreq.Request{ Method: "POST", Uri: server.URL + "/keys", ContentType: "application/json", Body: `{ "key": "` + strings.Join(strings.Split(`-----BEGIN PGP PUBLIC KEY BLOCK----- Version: GnuPG v1 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 =r/yh -----END PGP PUBLIC KEY BLOCK-----`, "\n"), "\\n") + `" }`, } request.AddHeader("Authorization", "Bearer "+authToken.ID) result, err := request.Do() So(err, ShouldBeNil) var response routes.KeysCreateResponse err = result.Body.FromJsonTo(&response) So(err, ShouldBeNil) So(response.Message, ShouldEqual, "A new key has been successfully inserted") So(response.Success, ShouldBeTrue) So(response.Key.ID, ShouldNotBeNil) key := response.Key Convey("Key should be visible on the user's key list", func() { request := goreq.Request{ Method: "GET", Uri: server.URL + "/keys?user=johnorange4", } request.AddHeader("Authorization", "Bearer "+authToken.ID) result, err := request.Do() So(err, ShouldBeNil) var response routes.KeysListResponse err = result.Body.FromJsonTo(&response) So(err, ShouldBeNil) So(response.Success, ShouldBeTrue) So(len(*response.Keys), ShouldBeGreaterThan, 0) }) Convey("Getting that key should succeed", func() { request := goreq.Request{ Method: "GET", Uri: server.URL + "/keys/" + key.ID, } request.AddHeader("Authorization", "Bearer "+authToken.ID) result, err := request.Do() So(err, ShouldBeNil) var response routes.KeysGetResponse err = result.Body.FromJsonTo(&response) So(err, ShouldBeNil) So(response.Success, ShouldBeTrue) So(response.Key.ID, ShouldEqual, key.ID) }) }) Convey("Listing keys without passing a username should fail", func() { request := goreq.Request{ Method: "GET", Uri: server.URL + "/keys", } request.AddHeader("Authorization", "Bearer "+authToken.ID) result, err := request.Do() So(err, ShouldBeNil) var response routes.KeysListResponse err = result.Body.FromJsonTo(&response) So(err, ShouldBeNil) So(response.Success, ShouldBeFalse) So(response.Message, ShouldEqual, "Invalid username") }) Convey("Getting a non-existing key should fail", func() { request := goreq.Request{ Method: "GET", Uri: server.URL + "/keys/123", } request.AddHeader("Authorization", "Bearer "+authToken.ID) result, err := request.Do() So(err, ShouldBeNil) var response routes.KeysGetResponse err = result.Body.FromJsonTo(&response) So(err, ShouldBeNil) So(response.Success, ShouldBeFalse) So(response.Message, ShouldEqual, "Requested key does not exist on our server") }) }) }
// EmailsCreate sends a new email func EmailsCreate(c web.C, w http.ResponseWriter, r *http.Request) { // Decode the request var input EmailsCreateRequest err := utils.ParseRequest(r, &input) if err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), }).Warn("Unable to decode a request") utils.JSONResponse(w, 400, &EmailsCreateResponse{ Success: false, Message: "Invalid input format", }) return } // Fetch the current session from the middleware session := c.Env["token"].(*models.Token) // Ensure that the kind is valid if input.Kind != "raw" && input.Kind != "manifest" && input.Kind != "pgpmime" { utils.JSONResponse(w, 400, &EmailsCreateResponse{ Success: false, Message: "Invalid email encryption kind", }) return } // Ensure that there's at least one recipient and that there's body if len(input.To) == 0 || input.Body == "" { utils.JSONResponse(w, 400, &EmailsCreateResponse{ Success: false, Message: "Invalid email", }) return } if input.Files != nil && len(input.Files) > 0 { // Check rights to files files, err := env.Files.GetFiles(input.Files...) if err != nil { utils.JSONResponse(w, 500, &EmailsCreateResponse{ Success: false, Message: "Unable to fetch emails", }) return } for _, file := range files { if file.Owner != session.Owner { utils.JSONResponse(w, 403, &EmailsCreateResponse{ Success: false, Message: "You are not the owner of file " + file.ID, }) return } } } // Create an email resource resource := models.MakeResource(session.Owner, input.Subject) // Generate metadata for manifests if input.Kind == "manifest" { resource.Name = "Encrypted message (" + resource.ID + ")" } // Fetch the user object from the database account, err := env.Accounts.GetTokenOwner(c.Env["token"].(*models.Token)) if err != nil { // The session refers to a non-existing user env.Log.WithFields(logrus.Fields{ "id": session.ID, "error": err.Error(), }).Warn("Valid session referred to a removed account") utils.JSONResponse(w, 410, &EmailsCreateResponse{ Success: false, Message: "Account disabled", }) return } // Get the "Sent" label's ID var label *models.Label err = env.Labels.WhereAndFetchOne(map[string]interface{}{ "name": "Sent", "builtin": true, "owner": account.ID, }, &label) if err != nil { env.Log.WithFields(logrus.Fields{ "id": account.ID, "error": err.Error(), }).Warn("Account has no sent label") utils.JSONResponse(w, 410, &EmailsCreateResponse{ Success: false, Message: "Misconfigured account", }) return } if input.From != "" { // Parse the from field from, err := mail.ParseAddress(input.From) if err != nil { utils.JSONResponse(w, 400, &EmailsCreateResponse{ Success: false, Message: "Invalid email.From", }) return } // We have a specified address if from.Address != "" { parts := strings.SplitN(from.Address, "@", 2) if parts[1] != env.Config.EmailDomain { utils.JSONResponse(w, 400, &EmailsCreateResponse{ Success: false, Message: "Invalid email.From (invalid domain)", }) return } address, err := env.Addresses.GetAddress(parts[0]) if err != nil { utils.JSONResponse(w, 400, &EmailsCreateResponse{ Success: false, Message: "Invalid email.From (invalid username)", }) return } if address.Owner != account.ID { utils.JSONResponse(w, 400, &EmailsCreateResponse{ Success: false, Message: "Invalid email.From (address not owned)", }) return } } } else { displayName := "" if x, ok := account.Settings.(map[string]interface{}); ok { if y, ok := x["displayName"]; ok { if z, ok := y.(string); ok { displayName = z } } } addr := &mail.Address{ Name: displayName, Address: account.StyledName + "@" + env.Config.EmailDomain, } input.From = addr.String() } // Check if Thread is set if input.Thread != "" { // todo: make it an actual exists check to reduce lan bandwidth thread, err := env.Threads.GetThread(input.Thread) if err != nil { env.Log.WithFields(logrus.Fields{ "id": input.Thread, "error": err.Error(), }).Warn("Cannot retrieve a thread") utils.JSONResponse(w, 400, &EmailsCreateResponse{ Success: false, Message: "Invalid thread", }) return } // update thread.secure depending on email's kind if (input.Kind == "raw" && thread.Secure == "all") || (input.Kind == "manifest" && thread.Secure == "none") || (input.Kind == "pgpmime" && thread.Secure == "none") { if err := env.Threads.UpdateID(thread.ID, map[string]interface{}{ "secure": "some", }); err != nil { env.Log.WithFields(logrus.Fields{ "id": input.Thread, "error": err.Error(), }).Warn("Cannot update a thread") utils.JSONResponse(w, 400, &EmailsCreateResponse{ Success: false, Message: "Unable to update the thread", }) return } } } else { secure := "all" if input.Kind == "raw" { secure = "none" } thread := &models.Thread{ Resource: models.MakeResource(account.ID, "Encrypted thread"), Emails: []string{resource.ID}, Labels: []string{label.ID}, Members: append(append(input.To, input.CC...), input.BCC...), IsRead: true, SubjectHash: input.SubjectHash, Secure: secure, } err := env.Threads.Insert(thread) if err != nil { utils.JSONResponse(w, 500, &EmailsCreateResponse{ Success: false, Message: "Unable to create a new thread", }) env.Log.WithFields(logrus.Fields{ "error": err.Error(), }).Error("Unable to create a new thread") return } input.Thread = thread.ID } // Calculate the message ID idHash := sha256.Sum256([]byte(resource.ID)) messageID := hex.EncodeToString(idHash[:]) + "@" + env.Config.EmailDomain // Create a new email struct email := &models.Email{ Resource: resource, MessageID: messageID, Kind: input.Kind, Thread: input.Thread, From: input.From, To: input.To, CC: input.CC, BCC: input.BCC, PGPFingerprints: input.PGPFingerprints, Manifest: input.Manifest, Body: input.Body, Files: input.Files, ContentType: input.ContentType, ReplyTo: input.ReplyTo, Status: "queued", } // Insert the email into the database if err := env.Emails.Insert(email); err != nil { utils.JSONResponse(w, 500, &EmailsCreateResponse{ Success: false, Message: "internal server error - EM/CR/01", }) env.Log.WithFields(logrus.Fields{ "error": err.Error(), }).Error("Could not insert an email into the database") return } // I'm going to whine at this part, as we are doubling the email sending code // Check if To contains lavaboom emails /*for _, address := range email.To { parts := strings.SplitN(address, "@", 2) if parts[1] == env.Config.EmailDomain { go sendEmail(parts[0], email) } } // Check if CC contains lavaboom emails for _, address := range email.CC { parts := strings.SplitN(address, "@", 2) if parts[1] == env.Config.EmailDomain { go sendEmail(parts[0], email) } } // Check if BCC contains lavaboom emails for _, address := range email.BCC { parts := strings.SplitN(address, "@", 2) if parts[1] == env.Config.EmailDomain { go sendEmail(parts[0], email) } }*/ // Add a send request to the queue err = env.Producer.Publish("send_email", []byte(`"`+email.ID+`"`)) if err != nil { utils.JSONResponse(w, 500, &EmailsCreateResponse{ Success: false, Message: "internal server error - EM/CR/03", }) env.Log.WithFields(logrus.Fields{ "error": err.Error(), }).Error("Could not publish an email send request") return } utils.JSONResponse(w, 201, &EmailsCreateResponse{ Success: true, Created: []string{email.ID}, }) }
func TestLabelsRoute(t *testing.T) { Convey("Given a working account", t, func() { account := &models.Account{ Resource: models.MakeResource("", "johnorange2"), Status: "complete", AltEmail: "*****@*****.**", } err := account.SetPassword("fruityloops") So(err, ShouldBeNil) err = env.Accounts.Insert(account) So(err, ShouldBeNil) result, err := goreq.Request{ Method: "POST", Uri: server.URL + "/tokens", ContentType: "application/json", Body: `{ "type": "auth", "username": "******", "password": "******" }`, }.Do() So(err, ShouldBeNil) var response routes.TokensCreateResponse err = result.Body.FromJsonTo(&response) So(err, ShouldBeNil) So(response.Success, ShouldBeTrue) authToken := response.Token Convey("Creating a new label using invalid input should fail", func() { request := goreq.Request{ Method: "POST", Uri: server.URL + "/labels", ContentType: "application/json", Body: "!@#!@!@#", } request.AddHeader("Authorization", "Bearer "+authToken.ID) result, err := request.Do() So(err, ShouldBeNil) var response routes.LabelsCreateResponse err = result.Body.FromJsonTo(&response) So(err, ShouldBeNil) So(response.Message, ShouldEqual, "Invalid input format") So(response.Success, ShouldBeFalse) }) Convey("Updating a label using invalid input should fail", func() { request := goreq.Request{ Method: "PUT", Uri: server.URL + "/labels/anything", ContentType: "application/json", Body: "!@#!@!@#", } request.AddHeader("Authorization", "Bearer "+authToken.ID) result, err := request.Do() So(err, ShouldBeNil) var response routes.LabelsUpdateResponse err = result.Body.FromJsonTo(&response) So(err, ShouldBeNil) So(response.Message, ShouldEqual, "Invalid input format") So(response.Success, ShouldBeFalse) }) Convey("Creating a new label without enough information should fail", func() { request := goreq.Request{ Method: "POST", Uri: server.URL + "/labels", ContentType: "application/json", Body: "{}", } request.AddHeader("Authorization", "Bearer "+authToken.ID) result, err := request.Do() So(err, ShouldBeNil) var response routes.LabelsCreateResponse err = result.Body.FromJsonTo(&response) So(err, ShouldBeNil) So(response.Message, ShouldEqual, "Invalid request") So(response.Success, ShouldBeFalse) }) Convey("Getting a non-existing label should fail", func() { request := goreq.Request{ Method: "GET", Uri: server.URL + "/labels/nonexisting", } request.AddHeader("Authorization", "Bearer "+authToken.ID) result, err := request.Do() So(err, ShouldBeNil) var response routes.LabelsCreateResponse err = result.Body.FromJsonTo(&response) So(err, ShouldBeNil) So(response.Message, ShouldEqual, "Label not found") So(response.Success, ShouldBeFalse) }) Convey("Updating a non-existing label should fail", func() { request := goreq.Request{ Method: "PUT", Uri: server.URL + "/labels/anything", ContentType: "application/json", Body: "{}", } request.AddHeader("Authorization", "Bearer "+authToken.ID) result, err := request.Do() So(err, ShouldBeNil) var response routes.LabelsUpdateResponse err = result.Body.FromJsonTo(&response) So(err, ShouldBeNil) So(response.Message, ShouldEqual, "Label not found") So(response.Success, ShouldBeFalse) }) Convey("Deleting a non-existing label should fail", func() { request := goreq.Request{ Method: "DELETE", Uri: server.URL + "/labels/anything", } request.AddHeader("Authorization", "Bearer "+authToken.ID) result, err := request.Do() So(err, ShouldBeNil) var response routes.LabelsDeleteResponse err = result.Body.FromJsonTo(&response) So(err, ShouldBeNil) So(response.Message, ShouldEqual, "Label not found") So(response.Success, ShouldBeFalse) }) Convey("Getting a non-owned label should fail", func() { label := &models.Label{ Resource: models.MakeResource("not", uniuri.New()), Builtin: false, } err := env.Labels.Insert(label) So(err, ShouldBeNil) request := goreq.Request{ Method: "GET", Uri: server.URL + "/labels/" + label.ID, } request.AddHeader("Authorization", "Bearer "+authToken.ID) result, err := request.Do() So(err, ShouldBeNil) var response routes.LabelsGetResponse err = result.Body.FromJsonTo(&response) So(err, ShouldBeNil) So(response.Message, ShouldEqual, "Label not found") So(response.Success, ShouldBeFalse) Convey("Updating it should fail", func() { request := goreq.Request{ Method: "PUT", Uri: server.URL + "/labels/" + label.ID, ContentType: "application/json", Body: "{}", } request.AddHeader("Authorization", "Bearer "+authToken.ID) result, err := request.Do() So(err, ShouldBeNil) var response routes.LabelsUpdateResponse err = result.Body.FromJsonTo(&response) So(err, ShouldBeNil) So(response.Message, ShouldEqual, "Label not found") So(response.Success, ShouldBeFalse) }) Convey("Deleting it should fail", func() { request := goreq.Request{ Method: "DELETE", Uri: server.URL + "/labels/" + label.ID, } request.AddHeader("Authorization", "Bearer "+authToken.ID) result, err := request.Do() So(err, ShouldBeNil) var response routes.LabelsDeleteResponse err = result.Body.FromJsonTo(&response) So(err, ShouldBeNil) So(response.Message, ShouldEqual, "Label not found") So(response.Success, ShouldBeFalse) }) }) Convey("Creating a label should succeed", func() { request := goreq.Request{ Method: "POST", Uri: server.URL + "/labels", ContentType: "application/json", Body: `{ "name": "` + uniuri.New() + `" }`, } request.AddHeader("Authorization", "Bearer "+authToken.ID) result, err := request.Do() So(err, ShouldBeNil) var response routes.LabelsCreateResponse err = result.Body.FromJsonTo(&response) So(err, ShouldBeNil) So(response.Label.Name, ShouldNotBeEmpty) So(response.Success, ShouldBeTrue) label := response.Label Convey("That label should be visible on the labels list", func() { request := goreq.Request{ Method: "GET", Uri: server.URL + "/labels", } request.AddHeader("Authorization", "Bearer "+authToken.ID) result, err := request.Do() So(err, ShouldBeNil) var response routes.LabelsListResponse err = result.Body.FromJsonTo(&response) So(err, ShouldBeNil) So(response.Message, ShouldBeEmpty) So(len(*response.Labels), ShouldBeGreaterThan, 0) So(response.Success, ShouldBeTrue) }) Convey("Getting that label should succeed", func() { request := goreq.Request{ Method: "GET", Uri: server.URL + "/labels/" + label.ID, } request.AddHeader("Authorization", "Bearer "+authToken.ID) result, err := request.Do() So(err, ShouldBeNil) var response routes.LabelsGetResponse err = result.Body.FromJsonTo(&response) So(err, ShouldBeNil) So(response.Label.ID, ShouldEqual, label.ID) So(response.Success, ShouldBeTrue) }) Convey("Updating that label should succeed", func() { request := goreq.Request{ Method: "PUT", Uri: server.URL + "/labels/" + label.ID, ContentType: "application/json", Body: `{ "name": "test123" }`, } request.AddHeader("Authorization", "Bearer "+authToken.ID) result, err := request.Do() So(err, ShouldBeNil) var response routes.LabelsUpdateResponse err = result.Body.FromJsonTo(&response) So(err, ShouldBeNil) So(response.Label.Name, ShouldEqual, "test123") So(response.Success, ShouldBeTrue) }) Convey("Deleting that label should succeed", func() { request := goreq.Request{ Method: "DELETE", Uri: server.URL + "/labels/" + label.ID, } request.AddHeader("Authorization", "Bearer "+authToken.ID) result, err := request.Do() So(err, ShouldBeNil) var response routes.LabelsDeleteResponse err = result.Body.FromJsonTo(&response) So(err, ShouldBeNil) So(response.Message, ShouldEqual, "Label successfully removed") So(response.Success, ShouldBeTrue) }) }) }) }
// AccountsCreate creates a new account in the system. func AccountsCreate(w http.ResponseWriter, r *http.Request) { // Decode the request var input AccountsCreateRequest err := utils.ParseRequest(r, &input) if err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), }).Warn("Unable to decode a request") utils.JSONResponse(w, 400, &AccountsCreateResponse{ Success: false, Message: "Invalid input format", }) return } // TODO: Sanitize the username // TODO: Hash the password if it's not hashed already // Accounts flow: // 1) POST /accounts {username, alt_email} => status = registered // 2) POST /accounts {username, invite_code} => checks invite_code validity // 3) POST /accounts {username, invite_code, password} => status = setup requestType := "unknown" if input.Username != "" && input.Password == "" && input.AltEmail != "" && input.InviteCode == "" { requestType = "register" } else if input.Username != "" && input.Password == "" && input.AltEmail == "" && input.InviteCode != "" { requestType = "verify" } else if input.Username != "" && input.Password != "" && input.AltEmail == "" && input.InviteCode != "" { requestType = "setup" } // "unknown" requests are empty and invalid if requestType == "unknown" { utils.JSONResponse(w, 400, &AccountsCreateResponse{ Success: false, Message: "Invalid request", }) return } if requestType == "register" { // Normalize the username input.Username = utils.NormalizeUsername(input.Username) // Validate the username if len(input.Username) < 3 || len(utils.RemoveDots(input.Username)) < 3 || len(input.Username) > 32 { utils.JSONResponse(w, 400, &AccountsCreateResponse{ Success: false, Message: "Invalid username - it has to be at least 3 and at max 32 characters long", }) return } // Ensure that the username is not used in address table if used, err := env.Addresses.GetAddress(utils.RemoveDots(input.Username)); err == nil || used != nil { utils.JSONResponse(w, 409, &AccountsCreateResponse{ Success: false, Message: "Username already used", }) return } // Then check it in the accounts table if ok, err := env.Accounts.IsUsernameUsed(utils.RemoveDots(input.Username)); ok || err != nil { utils.JSONResponse(w, 409, &AccountsCreateResponse{ Success: false, Message: "Username already used", }) return } // Also check that the email is unique if used, err := env.Accounts.IsEmailUsed(input.AltEmail); err != nil || used { if err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), }).Error("Unable to lookup registered accounts for emails") } utils.JSONResponse(w, 409, &AccountsCreateResponse{ Success: false, Message: "Email already used", }) return } // Both username and email are filled, so we can create a new account. account := &models.Account{ Resource: models.MakeResource("", utils.RemoveDots(input.Username)), StyledName: input.Username, Type: "beta", // Is this the proper value? AltEmail: input.AltEmail, Status: "registered", } // Try to save it in the database if err := env.Accounts.Insert(account); err != nil { utils.JSONResponse(w, 500, &AccountsCreateResponse{ Success: false, Message: "Internal server error - AC/CR/02", }) env.Log.WithFields(logrus.Fields{ "error": err.Error(), }).Error("Could not insert an user into the database") return } // TODO: Send emails here. Depends on @andreis work. // Return information about the account utils.JSONResponse(w, 201, &AccountsCreateResponse{ Success: true, Message: "Your account has been added to the beta queue", Account: account, }) return } else if requestType == "verify" { // We're pretty much checking whether an invitation code can be used by the user input.Username = utils.RemoveDots( utils.NormalizeUsername(input.Username), ) // Fetch the user from database account, err := env.Accounts.FindAccountByName(input.Username) if err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), "username": input.Username, }).Warn("User not found in the database") utils.JSONResponse(w, 400, &AccountsCreateResponse{ Success: false, Message: "Invalid username", }) return } // Fetch the token from the database token, err := env.Tokens.GetToken(input.InviteCode) if err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), }).Warn("Unable to fetch a registration token from the database") utils.JSONResponse(w, 400, &AccountsCreateResponse{ Success: false, Message: "Invalid invitation code", }) return } // Ensure that the invite code was given to this particular user. if token.Owner != account.ID { env.Log.WithFields(logrus.Fields{ "user_id": account.ID, "owner": token.Owner, }).Warn("Not owned invitation code used by an user") utils.JSONResponse(w, 400, &AccountsCreateResponse{ Success: false, Message: "Invalid invitation code", }) return } // Ensure that the token's type is valid if token.Type != "verify" { utils.JSONResponse(w, 400, &AccountsCreateResponse{ Success: false, Message: "Invalid invitation code", }) return } // Check if it's expired if token.Expired() { utils.JSONResponse(w, 400, &AccountsCreateResponse{ Success: false, Message: "Expired invitation code", }) return } // Ensure that the account is "registered" if account.Status != "registered" { utils.JSONResponse(w, 403, &AccountsCreateResponse{ Success: true, Message: "This account was already configured", }) return } // Everything is fine, return it. utils.JSONResponse(w, 200, &AccountsCreateResponse{ Success: true, Message: "Valid token was provided", }) return } else if requestType == "setup" { // User is setting the password in the setup wizard. This should be one of the first steps, // as it's required for him to acquire an authentication token to configure their account. input.Username = utils.RemoveDots( utils.NormalizeUsername(input.Username), ) // Fetch the user from database account, err := env.Accounts.FindAccountByName(input.Username) if err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), "username": input.Username, }).Warn("User not found in the database") utils.JSONResponse(w, 400, &AccountsCreateResponse{ Success: false, Message: "Invalid username", }) return } // Fetch the token from the database token, err := env.Tokens.GetToken(input.InviteCode) if err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), }).Warn("Unable to fetch a registration token from the database") utils.JSONResponse(w, 400, &AccountsCreateResponse{ Success: false, Message: "Invalid invitation code", }) return } // Ensure that the invite code was given to this particular user. if token.Owner != account.ID { env.Log.WithFields(logrus.Fields{ "user_id": account.ID, "owner": token.Owner, }).Warn("Not owned invitation code used by an user") utils.JSONResponse(w, 400, &AccountsCreateResponse{ Success: false, Message: "Invalid invitation code", }) return } // Ensure that the token's type is valid if token.Type != "verify" { utils.JSONResponse(w, 400, &AccountsCreateResponse{ Success: false, Message: "Invalid invitation code", }) return } // Check if it's expired if token.Expired() { utils.JSONResponse(w, 400, &AccountsCreateResponse{ Success: false, Message: "Expired invitation code", }) return } // Ensure that the account is "registered" if account.Status != "registered" { utils.JSONResponse(w, 403, &AccountsCreateResponse{ Success: true, Message: "This account was already configured", }) return } // Our token is fine, next part: password. // Ensure that user has chosen a secure password (check against 10k most used) if env.PasswordBF.TestString(input.Password) { utils.JSONResponse(w, 403, &AccountsCreateResponse{ Success: false, Message: "Weak password", }) return } // We can't really make more checks on the password, user could as well send us a hash // of a simple password, but we assume that no developer is that stupid (actually, // considering how many people upload their private keys and AWS credentials, I'm starting // to doubt the competence of some so-called "web deyvelopayrs") // Set the password err = account.SetPassword(input.Password) if err != nil { utils.JSONResponse(w, 500, &AccountsCreateResponse{ Success: false, Message: "Internal server error - AC/CR/01", }) env.Log.WithFields(logrus.Fields{ "error": err.Error(), }).Error("Unable to hash the password") return } account.Status = "setup" // Create labels err = env.Labels.Insert([]*models.Label{ &models.Label{ Resource: models.MakeResource(account.ID, "Inbox"), Builtin: true, }, &models.Label{ Resource: models.MakeResource(account.ID, "Sent"), Builtin: true, }, &models.Label{ Resource: models.MakeResource(account.ID, "Drafts"), Builtin: true, }, &models.Label{ Resource: models.MakeResource(account.ID, "Trash"), Builtin: true, }, &models.Label{ Resource: models.MakeResource(account.ID, "Spam"), Builtin: true, }, &models.Label{ Resource: models.MakeResource(account.ID, "Starred"), Builtin: true, }, }) if err != nil { utils.JSONResponse(w, 500, &AccountsCreateResponse{ Success: false, Message: "Internal server error - AC/CR/03", }) env.Log.WithFields(logrus.Fields{ "error": err.Error(), }).Error("Could not insert labels into the database") return } // Add a new mapping err = env.Addresses.Insert(&models.Address{ Resource: models.Resource{ ID: account.Name, DateCreated: time.Now(), DateModified: time.Now(), Owner: account.ID, }, }) if err != nil { utils.JSONResponse(w, 500, &AccountsCreateResponse{ Success: false, Message: "Unable to create a new address mapping", }) env.Log.WithFields(logrus.Fields{ "error": err.Error(), }).Error("Could not insert an address mapping into db") return } // Update the account err = env.Accounts.UpdateID(account.ID, account) if err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), "id": account.ID, }).Error("Unable to update an account") utils.JSONResponse(w, 500, &AccountsCreateResponse{ Success: false, Message: "Unable to update the account", }) return } // Remove the token and return a response err = env.Tokens.DeleteID(input.InviteCode) if err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), "id": input.InviteCode, }).Error("Could not remove the token from database") } utils.JSONResponse(w, 200, &AccountsCreateResponse{ Success: true, Message: "Your account has been initialized successfully", Account: account, }) return } }
func create(w http.ResponseWriter, req *http.Request) { // Decode the body var msg createInput err := json.NewDecoder(req.Body).Decode(&msg) if err != nil { writeJSON(w, errorMsg{ Success: false, Message: err.Error(), }) return } // Fetch the invite from database cursor, err := r.Db(*rethinkName).Table("invites").Get(msg.Token).Run(session) if err != nil { writeJSON(w, errorMsg{ Success: false, Message: err.Error(), }) return } var invite *Invite err = cursor.One(&invite) if err != nil { writeJSON(w, errorMsg{ Success: false, Message: err.Error(), }) return } // Normalize the username styledName := msg.Username msg.Username = utils.RemoveDots(utils.NormalizeUsername(msg.Username)) var account *models.Account // If there's no account id, then simply check args if invite.AccountID == "" { if !govalidator.IsEmail(msg.Email) { writeJSON(w, errorMsg{ Success: false, Message: "Invalid email address", }) return } // Check if address is taken cursor, err = r.Db(*rethinkAPIName).Table("addresses").Get(msg.Username).Run(session) if err == nil || cursor != nil { writeJSON(w, freeMsg{ Success: false, UsernameTaken: true, }) return } // Check if email is used cursor, err = r.Db(*rethinkAPIName).Table("accounts"). GetAllByIndex("alt_email", msg.Email). Filter(r.Row.Field("id").Ne(r.Expr(invite.AccountID))). Count().Run(session) if err != nil { writeJSON(w, errorMsg{ Success: false, Message: err.Error(), }) return } var emailCount int err = cursor.One(&emailCount) if err != nil { writeJSON(w, errorMsg{ Success: false, Message: err.Error(), }) return } if emailCount > 0 { writeJSON(w, freeMsg{ Success: false, EmailUsed: true, }) return } // Prepare a new account account = &models.Account{ Resource: models.MakeResource("", msg.Username), AltEmail: msg.Email, StyledName: styledName, Status: "registered", Type: "supporter", } // Update the invite invite.AccountID = account.ID err = r.Db(*rethinkName).Table("invites").Get(invite.ID).Update(map[string]interface{}{ "account_id": invite.AccountID, }).Exec(session) if err != nil { writeJSON(w, errorMsg{ Success: false, Message: err.Error(), }) return } // Insert the account into db err = r.Db(*rethinkAPIName).Table("accounts").Insert(account).Exec(session) if err != nil { writeJSON(w, errorMsg{ Success: false, Message: err.Error(), }) return } } else { cursor, err = r.Db(*rethinkAPIName).Table("accounts").Get(invite.AccountID).Run(session) if err != nil { writeJSON(w, errorMsg{ Success: false, Message: err.Error(), }) return } defer cursor.Close() if err := cursor.One(&account); err != nil { writeJSON(w, errorMsg{ Success: false, Message: err.Error(), }) return } if account.Name != "" && account.Name != msg.Username { writeJSON(w, errorMsg{ Success: false, Message: "Invalid username", }) return } else if account.Name == "" { // Check if address is taken cursor, err = r.Db(*rethinkAPIName).Table("addresses").Get(msg.Username).Run(session) if err == nil || cursor != nil { writeJSON(w, errorMsg{ Success: false, Message: "Username is taken", }) return } } if account.AltEmail != "" && account.AltEmail != msg.Email { writeJSON(w, errorMsg{ Success: false, Message: "Invalid email", }) return } if account.AltEmail == "" { if !govalidator.IsEmail(msg.Email) { writeJSON(w, errorMsg{ Success: false, Message: "Invalid email address", }) return } // Check if email is used cursor, err = r.Db(*rethinkAPIName).Table("accounts"). GetAllByIndex("alt_email", msg.Email). Filter(r.Row.Field("id").Ne(r.Expr(invite.AccountID))). Count().Run(session) if err != nil { writeJSON(w, errorMsg{ Success: false, Message: err.Error(), }) return } defer cursor.Close() var emailCount int err = cursor.One(&emailCount) if err != nil { writeJSON(w, errorMsg{ Success: false, Message: err.Error(), }) return } if emailCount > 0 { writeJSON(w, errorMsg{ Success: false, Message: "Email is already used", }) return } } if err := r.Db(*rethinkAPIName).Table("accounts").Get(invite.AccountID).Update(map[string]interface{}{ "type": "supporter", }).Exec(session); err != nil { writeJSON(w, errorMsg{ Success: false, Message: err.Error(), }) return } } // Generate a new invite token for the user token := &models.Token{ Resource: models.MakeResource(account.ID, "Invitation token from invite-api"), Type: "verify", Expiring: models.Expiring{ ExpiryDate: time.Now().UTC().Add(time.Hour * 12), }, } // Insert it into db err = r.Db(*rethinkAPIName).Table("tokens").Insert(token).Exec(session) if err != nil { writeJSON(w, errorMsg{ Success: false, Message: err.Error(), }) return } // Here be dragons. Thou art forewarned. /*go func() { // Watch the changes cursor, err := r.Db(*rethinkAPIName).Table("accounts").Get(account.ID).Changes().Run(session) if err != nil { log.Print("Error while watching changes of user " + account.Name + " - " + err.Error()) return } defer cursor.Close() // Generate a timeout "flag" ts := uniuri.New() // Read them c := make(chan struct{}) go func() { var change struct { NewValue map[string]interface{} `gorethink:"new_val"` } for cursor.Next(&change) { if status, ok := change.NewValue["status"]; ok { if x, ok := status.(string); ok && x == "setup" { c <- struct{}{} return } } if iat, ok := change.NewValue["_invite_api_timeout"]; ok { if x, ok := iat.(string); ok && x == ts { log.Print("Account setup watcher timeout for name " + account.Name) return } } } }() // Block the goroutine select { case <-c: if err := r.Db(*rethinkName).Table("invites").Get(invite.ID).Delete().Exec(session); err != nil { log.Print("Unable to delete an invite. " + invite.ID + " - " + account.ID) return } return case <-time.After(12 * time.Hour): if err := r.Db(*rethinkAPIName).Table("accounts").Get(account.ID).Update(map[string]interface{}{ "_invite_api_timeout": ts, }).Exec(session); err != nil { log.Print("Failed to make a goroutine timeout. " + account.ID) } return } }()*/ // jk f**k that if err := r.Db(*rethinkName).Table("invites").Get(invite.ID).Delete().Exec(session); err != nil { log.Print("Unable to delete an invite. " + invite.ID + " - " + account.ID) return } // Return the token writeJSON(w, createMsg{ Success: true, Code: token.ID, }) }
// KeysCreate appens a new key to the server func KeysCreate(c web.C, w http.ResponseWriter, r *http.Request) { // Decode the request var input KeysCreateRequest err := utils.ParseRequest(r, &input) if err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), }).Warn("Unable to decode a request") utils.JSONResponse(w, 409, &KeysCreateResponse{ Success: false, Message: "Invalid input format", }) return } // Get the session session := c.Env["token"].(*models.Token) // Parse the armored key entityList, err := openpgp.ReadArmoredKeyRing(strings.NewReader(input.Key)) if err != nil { utils.JSONResponse(w, 409, &KeysCreateResponse{ Success: false, Message: "Invalid key format", }) env.Log.WithFields(logrus.Fields{ "error": err.Error(), "list": entityList, }).Warn("Cannot parse an armored key") return } // Parse using armor pkg block, err := armor.Decode(strings.NewReader(input.Key)) if err != nil { utils.JSONResponse(w, 409, &KeysCreateResponse{ Success: false, Message: "Invalid key format", }) env.Log.WithFields(logrus.Fields{ "error": err.Error(), "list": entityList, }).Warn("Cannot parse an armored key #2") return } // Get the account from db account, err := env.Accounts.GetAccount(session.Owner) if err != nil { utils.JSONResponse(w, 500, &KeysCreateResponse{ Success: false, Message: "Internal server error - KE/CR/01", }) env.Log.WithFields(logrus.Fields{ "error": err.Error(), "id": session.Owner, }).Error("Cannot fetch user from database") return } // Let's hope that the user is capable of sending proper armored keys publicKey := entityList[0] // Encode the fingerprint id := hex.EncodeToString(publicKey.PrimaryKey.Fingerprint[:]) // Get the key's bit length - should not return an error bitLength, _ := publicKey.PrimaryKey.BitLength() // Allocate a new key key := &models.Key{ Resource: models.MakeResource( account.ID, fmt.Sprintf( "%s/%d/%s", utils.GetAlgorithmName(publicKey.PrimaryKey.PubKeyAlgo), bitLength, publicKey.PrimaryKey.KeyIdString(), ), ), Headers: block.Header, Algorithm: utils.GetAlgorithmName(publicKey.PrimaryKey.PubKeyAlgo), Length: bitLength, Key: input.Key, KeyID: publicKey.PrimaryKey.KeyIdString(), KeyIDShort: publicKey.PrimaryKey.KeyIdShortString(), Reliability: 0, } // Update id as we can't do it directly during allocation key.ID = id // Try to insert it into the database if err := env.Keys.Insert(key); err != nil { utils.JSONResponse(w, 500, &KeysCreateResponse{ Success: false, Message: "Internal server error - KE/CR/02", }) env.Log.WithFields(logrus.Fields{ "error": err.Error(), }).Error("Could not insert a key to the database") return } // Return the inserted key utils.JSONResponse(w, 201, &KeysCreateResponse{ Success: true, Message: "A new key has been successfully inserted", Key: key, }) }
// TokensCreate allows logging in to an account. func TokensCreate(w http.ResponseWriter, r *http.Request) { // Decode the request var input TokensCreateRequest err := utils.ParseRequest(r, &input) if err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), }).Warn("Unable to decode a request") utils.JSONResponse(w, 409, &TokensCreateResponse{ Success: false, Message: "Invalid input format", }) return } // We can only create "auth" tokens now if input.Type != "auth" { utils.JSONResponse(w, 409, &TokensCreateResponse{ Success: false, Message: "Only auth tokens are implemented", }) return } input.Username = utils.RemoveDots( utils.NormalizeUsername(input.Username), ) // Check if account exists user, err := env.Accounts.FindAccountByName(input.Username) if err != nil { utils.JSONResponse(w, 403, &TokensCreateResponse{ Success: false, Message: "Wrong username or password", }) return } // "registered" accounts can't log in if user.Status == "registered" { utils.JSONResponse(w, 403, &TokensCreateResponse{ Success: false, Message: "Your account is not confirmed", }) return } // Verify the password valid, updated, err := user.VerifyPassword(input.Password) if err != nil || !valid { utils.JSONResponse(w, 403, &TokensCreateResponse{ Success: false, Message: "Wrong username or password", }) return } // Update the user if password was updated if updated { user.DateModified = time.Now() err := env.Accounts.UpdateID(user.ID, user) if err != nil { env.Log.WithFields(logrus.Fields{ "user": user.Name, "error": err.Error(), }).Error("Could not update user") // DO NOT RETURN! } } // Check for 2nd factor if user.FactorType != "" { factor, ok := env.Factors[user.FactorType] if ok { // Verify the 2FA verified, challenge, err := user.Verify2FA(factor, input.Token) if err != nil { utils.JSONResponse(w, 500, &TokensCreateResponse{ Success: false, Message: "Internal 2FA error", }) env.Log.WithFields(logrus.Fields{ "err": err.Error(), "factor": user.FactorType, }).Warn("2FA authentication error") return } // Token was probably empty. Return the challenge. if !verified && challenge != "" { utils.JSONResponse(w, 403, &TokensCreateResponse{ Success: false, Message: "2FA token was not passed", FactorType: user.FactorType, FactorChallenge: challenge, }) return } // Token was incorrect if !verified { utils.JSONResponse(w, 403, &TokensCreateResponse{ Success: false, Message: "Invalid token passed", FactorType: user.FactorType, }) return } } } // Calculate the expiry date expDate := time.Now().Add(time.Hour * time.Duration(env.Config.SessionDuration)) // Create a new token token := &models.Token{ Expiring: models.Expiring{ExpiryDate: expDate}, Resource: models.MakeResource(user.ID, "Auth token expiring on "+expDate.Format(time.RFC3339)), Type: input.Type, } // Insert int into the database env.Tokens.Insert(token) // Respond with the freshly created token utils.JSONResponse(w, 201, &TokensCreateResponse{ Success: true, Message: "Authentication successful", Token: token, }) }
func TestTokensRoute(t *testing.T) { Convey("Given a working account", t, func() { account := &models.Account{ Resource: models.MakeResource("", "johnorange5"), Status: "complete", AltEmail: "*****@*****.**", } err := account.SetPassword("fruityloops") So(err, ShouldBeNil) err = env.Accounts.Insert(account) So(err, ShouldBeNil) result, err := goreq.Request{ Method: "POST", Uri: server.URL + "/tokens", ContentType: "application/json", Body: `{ "type": "auth", "username": "******", "password": "******" }`, }.Do() So(err, ShouldBeNil) var response routes.TokensCreateResponse err = result.Body.FromJsonTo(&response) So(err, ShouldBeNil) So(response.Success, ShouldBeTrue) authToken := response.Token Convey("Creating a non-auth token should fail", func() { request, err := goreq.Request{ Method: "POST", Uri: server.URL + "/tokens", ContentType: "application/json", Body: `{ "type": "not-auth" }`, }.Do() So(err, ShouldBeNil) var response routes.TokensCreateResponse err = request.Body.FromJsonTo(&response) So(err, ShouldBeNil) So(response.Success, ShouldBeFalse) So(response.Message, ShouldEqual, "Only auth tokens are implemented") }) Convey("Trying to sign in using wrong username should fail", func() { request, err := goreq.Request{ Method: "POST", Uri: server.URL + "/tokens", ContentType: "application/json", Body: `{ "type": "auth", "username": "******", "password": "******" }`, }.Do() So(err, ShouldBeNil) var response routes.TokensCreateResponse err = request.Body.FromJsonTo(&response) So(err, ShouldBeNil) So(response.Success, ShouldBeFalse) So(response.Message, ShouldEqual, "Wrong username or password") }) Convey("Trying to sign in using wrong password should fail", func() { request, err := goreq.Request{ Method: "POST", Uri: server.URL + "/tokens", ContentType: "application/json", Body: `{ "type": "auth", "username": "******", "password": "******" }`, }.Do() So(err, ShouldBeNil) var response routes.TokensCreateResponse err = request.Body.FromJsonTo(&response) So(err, ShouldBeNil) So(response.Success, ShouldBeFalse) So(response.Message, ShouldEqual, "Wrong username or password") }) Convey("Trying to sign in using an invalid JSON input should fail", func() { request, err := goreq.Request{ Method: "POST", Uri: server.URL + "/tokens", ContentType: "application/json", Body: "123123123###434$#$", }.Do() So(err, ShouldBeNil) var response routes.TokensCreateResponse err = request.Body.FromJsonTo(&response) So(err, ShouldBeNil) So(response.Success, ShouldBeFalse) So(response.Message, ShouldEqual, "Invalid input format") }) Convey("Getting the currently used token should succeed", func() { request := goreq.Request{ Method: "GET", Uri: server.URL + "/tokens", } request.AddHeader("Authorization", "Bearer "+authToken.ID) result, err := request.Do() So(err, ShouldBeNil) var response routes.TokensGetResponse err = result.Body.FromJsonTo(&response) So(err, ShouldBeNil) So(response.Success, ShouldBeTrue) So(response.Token.ExpiryDate.After(time.Now().UTC()), ShouldBeTrue) }) Convey("Deleting the token by ID should succeed", func() { request := goreq.Request{ Method: "DELETE", Uri: server.URL + "/tokens/" + authToken.ID, } request.AddHeader("Authorization", "Bearer "+authToken.ID) result, err := request.Do() So(err, ShouldBeNil) var response routes.TokensDeleteResponse err = result.Body.FromJsonTo(&response) So(err, ShouldBeNil) So(response.Success, ShouldBeTrue) So(response.Message, ShouldEqual, "Successfully logged out") }) Convey("Deleting a non-existing token should fail", func() { request := goreq.Request{ Method: "DELETE", Uri: server.URL + "/tokens/123", } request.AddHeader("Authorization", "Bearer "+authToken.ID) result, err := request.Do() So(err, ShouldBeNil) var response routes.TokensDeleteResponse err = result.Body.FromJsonTo(&response) So(err, ShouldBeNil) So(response.Success, ShouldBeFalse) So(response.Message, ShouldEqual, "Invalid token ID") }) Convey("Deleting current token should succeed", func() { request := goreq.Request{ Method: "DELETE", Uri: server.URL + "/tokens", } request.AddHeader("Authorization", "Bearer "+authToken.ID) result, err := request.Do() So(err, ShouldBeNil) var response routes.TokensDeleteResponse err = result.Body.FromJsonTo(&response) So(err, ShouldBeNil) So(response.Success, ShouldBeTrue) So(response.Message, ShouldEqual, "Successfully logged out") }) }) }
func TestMiddleware(t *testing.T) { Convey("While querying a secure endpoint", t, func() { Convey("No header should fail", func() { result, err := goreq.Request{ Method: "GET", Uri: server.URL + "/accounts/me", }.Do() So(err, ShouldBeNil) var response routes.AuthMiddlewareResponse err = result.Body.FromJsonTo(&response) So(err, ShouldBeNil) So(response.Success, ShouldBeFalse) So(response.Message, ShouldEqual, "Missing auth token") }) Convey("An invalid header should fail", func() { request := goreq.Request{ Method: "GET", Uri: server.URL + "/accounts/me", } request.AddHeader("Authorization", "123") result, err := request.Do() So(err, ShouldBeNil) var response routes.AuthMiddlewareResponse err = result.Body.FromJsonTo(&response) So(err, ShouldBeNil) So(response.Success, ShouldBeFalse) So(response.Message, ShouldEqual, "Invalid authorization header") }) Convey("Invalid token should fail", func() { request := goreq.Request{ Method: "GET", Uri: server.URL + "/accounts/me", } request.AddHeader("Authorization", "Bearer 123") result, err := request.Do() So(err, ShouldBeNil) var response routes.AuthMiddlewareResponse err = result.Body.FromJsonTo(&response) So(err, ShouldBeNil) So(response.Success, ShouldBeFalse) So(response.Message, ShouldEqual, "Invalid authorization token") }) Convey("Expired token should fail", func() { account := &models.Account{ Resource: models.MakeResource("", "johnorange"), Status: "complete", AltEmail: "*****@*****.**", } err := account.SetPassword("fruityloops") So(err, ShouldBeNil) err = env.Accounts.Insert(account) So(err, ShouldBeNil) token := models.Token{ Resource: models.MakeResource(account.ID, "test invite token"), Expiring: models.Expiring{ ExpiryDate: time.Now().UTC().Truncate(time.Hour * 8), }, Type: "auth", } err = env.Tokens.Insert(token) So(err, ShouldBeNil) request := goreq.Request{ Method: "GET", Uri: server.URL + "/accounts/me", } request.AddHeader("Authorization", "Bearer "+token.ID) result, err := request.Do() So(err, ShouldBeNil) var response routes.AuthMiddlewareResponse err = result.Body.FromJsonTo(&response) So(err, ShouldBeNil) So(response.Success, ShouldBeFalse) So(response.Message, ShouldEqual, "Authorization token has expired") }) }) }