func FilesList(c web.C, w http.ResponseWriter, r *http.Request) { session := c.Env["token"].(*models.Token) query := r.URL.Query() email := query.Get("email") name := query.Get("name") if email == "" || name == "" { utils.JSONResponse(w, 400, &FilesListResponse{ Success: false, Message: "No email or name in get params", }) return } files, err := env.Files.GetInEmail(session.Owner, email, name) if err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), }).Error("Unable to fetch files") utils.JSONResponse(w, 500, &FilesListResponse{ Success: false, Message: "Internal error (code FI/LI/01)", }) return } utils.JSONResponse(w, 200, &FilesListResponse{ Success: true, Files: &files, }) }
// FilesGet gets the requested file from the database func FilesGet(c web.C, w http.ResponseWriter, r *http.Request) { // Get the file from the database file, err := env.Files.GetFile(c.URLParams["id"]) if err != nil { utils.JSONResponse(w, 404, &FilesGetResponse{ Success: false, Message: "File not found", }) return } // Fetch the current session from the middleware session := c.Env["token"].(*models.Token) // Check for ownership if file.Owner != session.Owner { utils.JSONResponse(w, 404, &FilesGetResponse{ Success: false, Message: "File not found", }) return } // Write the file to the response utils.JSONResponse(w, 200, &FilesGetResponse{ Success: true, File: file, }) }
// LabelsGet does *something* - TODO func LabelsGet(c web.C, w http.ResponseWriter, req *http.Request) { // Get the label from the database label, err := env.Labels.GetLabel(c.URLParams["id"]) if err != nil { utils.JSONResponse(w, 404, &LabelsGetResponse{ Success: false, Message: "Label not found", }) return } // Fetch the current session from the middleware session := c.Env["token"].(*models.Token) // Check for ownership if label.Owner != session.Owner { utils.JSONResponse(w, 404, &LabelsGetResponse{ Success: false, Message: "Label not found", }) return } totalCount, err := env.Threads.CountByLabel(label.ID) if err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), "label": label.ID, }).Error("Unable to fetch total threads count") utils.JSONResponse(w, 500, &LabelsListResponse{ Success: false, Message: "Internal error (code LA/GE/01)", }) return } unreadCount, err := env.Threads.CountByLabelUnread(label.ID) if err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), "label": label.ID, }).Error("Unable to fetch unread threads count") utils.JSONResponse(w, 500, &LabelsListResponse{ Success: false, Message: "Internal error (code LA/GE/01)", }) return } label.TotalThreadsCount = totalCount label.UnreadThreadsCount = unreadCount // Write the label to the response utils.JSONResponse(w, 200, &LabelsGetResponse{ Success: true, Label: label, }) }
// ContactsGet gets the requested contact from the database func ContactsGet(c web.C, w http.ResponseWriter, r *http.Request) { // Get the contact from the database contact, err := env.Contacts.GetContact(c.URLParams["id"]) if err != nil { utils.JSONResponse(w, 404, &ContactsGetResponse{ Success: false, Message: "Contact not found", }) return } // Fetch the current session from the middleware session := c.Env["token"].(*models.Token) // Check for ownership if contact.Owner != session.Owner { utils.JSONResponse(w, 404, &ContactsGetResponse{ Success: false, Message: "Contact not found", }) return } // Write the contact to the response utils.JSONResponse(w, 200, &ContactsGetResponse{ Success: true, Contact: contact, }) }
// EmailsGet responds with a single email message func EmailsGet(c web.C, w http.ResponseWriter, r *http.Request) { // Get the email from the database email, err := env.Emails.GetEmail(c.URLParams["id"]) if err != nil { utils.JSONResponse(w, 404, &EmailsGetResponse{ Success: false, Message: "Email not found", }) return } // Fetch the current session from the middleware session := c.Env["token"].(*models.Token) // Check for ownership if email.Owner != session.Owner { utils.JSONResponse(w, 404, &EmailsGetResponse{ Success: false, Message: "Email not found", }) return } // Write the email to the response utils.JSONResponse(w, 200, &EmailsGetResponse{ Success: true, Email: email, }) }
// 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 := c.URLParams["id"] // Right now we only support "me" as the ID if id != "me" { utils.JSONResponse(w, 501, &AccountsGetResponse{ Success: false, Message: `Only the "me" user is implemented`, }) return } // Fetch the current session from the database session := c.Env["token"].(*models.Token) // Fetch the user object from the database user, err := env.Accounts.GetAccount(session.Owner) if err != nil { utils.JSONResponse(w, 500, &AccountsDeleteResponse{ Success: false, Message: "Unable to resolve the account", }) return } // Return the user struct utils.JSONResponse(w, 200, &AccountsGetResponse{ Success: true, Account: user, }) }
// 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, }) }
// 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 := c.URLParams["id"] // 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["token"].(*models.Token) // Fetch the user object from the database user, err := env.Accounts.GetTokenOwner(session) 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, &AccountsWipeDataResponse{ Success: false, Message: "Account disabled", }) return } // TODO: Delete contacts // TODO: Delete emails // TODO: Delete labels // TODO: Delete threads // Delete tokens err = env.Tokens.DeleteOwnedBy(user.ID) if err != nil { env.Log.WithFields(logrus.Fields{ "id": user.ID, "error": err.Error(), }).Error("Unable to remove account's tokens") utils.JSONResponse(w, 500, &AccountsWipeDataResponse{ Success: false, Message: "Internal error (code AC/WD/05)", }) return } utils.JSONResponse(w, 200, &AccountsWipeDataResponse{ Success: true, Message: "Your account has been successfully wiped", }) }
// TokensGet returns information about the current token. func TokensGet(c web.C, w http.ResponseWriter, r *http.Request) { // Initialize var ( token *models.Token err error ) id, ok := c.URLParams["id"] if !ok || id == "" { // Get the token from the middleware token = c.Env["token"].(*models.Token) } else { token, err = env.Tokens.GetToken(id) if err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), "id": id, }).Warn("Unable to find the token") utils.JSONResponse(w, 404, &TokensGetResponse{ Success: false, Message: "Invalid token ID", }) return } } // Respond with the token information utils.JSONResponse(w, 200, &TokensGetResponse{ Success: true, Token: token, }) }
// 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, }) }
// ThreadsDelete removes a thread from the database func ThreadsDelete(c web.C, w http.ResponseWriter, r *http.Request) { // Get the thread from the database thread, err := env.Threads.GetThread(c.URLParams["id"]) if err != nil { utils.JSONResponse(w, 404, &ThreadsDeleteResponse{ Success: false, Message: "Thread not found", }) return } // Fetch the current session from the middleware session := c.Env["token"].(*models.Token) // Check for ownership if thread.Owner != session.Owner { utils.JSONResponse(w, 404, &ThreadsDeleteResponse{ Success: false, Message: "Thread not found", }) return } // Perform the deletion err = env.Threads.DeleteID(c.URLParams["id"]) if err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), "id": c.URLParams["id"], }).Error("Unable to delete a thread") utils.JSONResponse(w, 500, &ThreadsDeleteResponse{ Success: false, Message: "Internal error (code TH/DE/01)", }) return } // Remove dependent emails err = env.Emails.DeleteByThread(c.URLParams["id"]) if err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), "id": c.URLParams["id"], }).Error("Unable to delete emails by thread") utils.JSONResponse(w, 500, &ThreadsDeleteResponse{ Success: false, Message: "Internal error (code TH/DE/02)", }) return } // Write the thread to the response utils.JSONResponse(w, 200, &ThreadsDeleteResponse{ Success: true, Message: "Thread successfully removed", }) }
// ThreadsGet returns information about a single thread. func ThreadsGet(c web.C, w http.ResponseWriter, r *http.Request) { thread, err := env.Threads.GetThread(c.URLParams["id"]) if err != nil { utils.JSONResponse(w, 404, &ThreadsGetResponse{ Success: false, Message: "Thread not found", }) return } session := c.Env["token"].(*models.Token) if thread.Owner != session.Owner { utils.JSONResponse(w, 404, &ThreadsGetResponse{ Success: false, Message: "Thread not found", }) return } manifest, err := env.Emails.GetThreadManifest(thread.ID) if err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), "id": thread.ID, }).Error("Unable to get a manifest") } else { thread.Manifest = manifest } var emails []*models.Email if ok := r.URL.Query().Get("list_emails"); ok == "true" || ok == "1" { emails, err = env.Emails.GetByThread(thread.ID) if err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), "id": thread.ID, }).Error("Unable to fetch emails linked to a thread") utils.JSONResponse(w, 500, &ThreadsGetResponse{ Success: false, Message: "Unable to retrieve emails", }) return } } utils.JSONResponse(w, 200, &ThreadsGetResponse{ Success: true, Thread: thread, Emails: &emails, }) }
// AuthMiddleware checks whether the token passed with the request is valid 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 token from the database token, err := env.Tokens.GetToken(headerParts[1]) if err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), }).Error("Cannot retrieve session from the database") utils.JSONResponse(w, 401, &AuthMiddlewareResponse{ Success: false, Message: "Invalid authorization token", }) return } // Check if it's expired if token.Expired() { utils.JSONResponse(w, 419, &AuthMiddlewareResponse{ Success: false, Message: "Authorization token has expired", }) env.Tokens.DeleteID(token.ID) return } // Continue to the next middleware/route c.Env["token"] = token h.ServeHTTP(w, r) }) }
func deleteToken(c web.C, w http.ResponseWriter, req *http.Request) { id := c.URLParams["id"] wr, err := r.Table("tokens").Get(id).Delete().RunWrite(session) if err != nil { utils.JSONResponse(w, 500, map[string]interface{}{ "error": err.Error(), }) return } utils.JSONResponse(w, 200, map[string]interface{}{ "dropped": wr.Dropped, }) }
// 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", DocsURL: "https://docs.lavaboom.io/", Version: env.Config.APIVersion, }) }
// 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) { // Initialize var ( token *models.Token err error ) id, ok := c.URLParams["id"] if !ok || id == "" { // Get the token from the middleware token = c.Env["token"].(*models.Token) } else { token, err = env.Tokens.GetToken(id) if err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), "id": id, }).Warn("Unable to find the token") utils.JSONResponse(w, 404, &TokensDeleteResponse{ Success: false, Message: "Invalid token ID", }) return } } // Delete it from the database if err := env.Tokens.DeleteID(token.ID); err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), }).Error("Unable to delete a token") utils.JSONResponse(w, 500, &TokensDeleteResponse{ Success: false, Message: "Internal server error - TO/DE/02", }) return } utils.JSONResponse(w, 200, &TokensDeleteResponse{ Success: true, Message: "Successfully logged out", }) }
// FilesDelete removes a file from the database func FilesDelete(c web.C, w http.ResponseWriter, r *http.Request) { // Get the file from the database file, err := env.Files.GetFile(c.URLParams["id"]) if err != nil { utils.JSONResponse(w, 404, &FilesDeleteResponse{ Success: false, Message: "File not found", }) return } // Fetch the current session from the middleware session := c.Env["token"].(*models.Token) // Check for ownership if file.Owner != session.Owner { utils.JSONResponse(w, 404, &FilesDeleteResponse{ Success: false, Message: "File not found", }) return } // Perform the deletion err = env.Files.DeleteID(c.URLParams["id"]) if err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), "id": c.URLParams["id"], }).Error("Unable to delete a file") utils.JSONResponse(w, 500, &FilesDeleteResponse{ Success: false, Message: "Internal error (code FI/DE/01)", }) return } // Write the file to the response utils.JSONResponse(w, 200, &FilesDeleteResponse{ Success: true, Message: "File successfully removed", }) }
func AddressesList(c web.C, w http.ResponseWriter, r *http.Request) { session := c.Env["token"].(*models.Token) addresses, err := env.Addresses.GetOwnedBy(session.Owner) if err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), }).Error("Unable to fetch addresses") utils.JSONResponse(w, 500, &AddressesListResponse{ Success: false, Message: "Internal error (code AD/LI/01)", }) return } utils.JSONResponse(w, 200, &AddressesListResponse{ Success: true, Addresses: addresses, }) }
func Avatars(c web.C, w http.ResponseWriter, r *http.Request) { // Parse the query params query := r.URL.Query() // Settings var ( widthString = query.Get("width") width int ) // Read width if widthString == "" { width = 100 } else { var err error width, err = strconv.Atoi(widthString) if err != nil { utils.JSONResponse(w, 400, map[string]interface{}{ "succes": false, "message": "Invalid width", }) return } } hash := c.URLParams["hash"] ext := c.URLParams["ext"] // data to parse var data []byte // md5 hash if len(hash) == 32 { data, _ = hex.DecodeString(hash) } // not md5 if data == nil { hashed := md5.Sum([]byte(hash)) data = hashed[:] } // if svg if ext == "svg" { w.Header().Set("Content-Type", "image/svg+xml") avatarConfig.MakeSVG(w, width, false, data) return } // generate the png w.Header().Set("Content-Type", "image/png") png.Encode(w, avatarConfig.Make(width, false, data)) }
func AccountsStartOnboarding(c web.C, w http.ResponseWriter, r *http.Request) { // Get the account ID from the request id := c.URLParams["id"] // Right now we only support "me" as the ID if id != "me" { utils.JSONResponse(w, 501, &AccountsStartOnboardingResponse{ Success: false, Message: `Only the "me" user is implemented`, }) return } // Fetch the current session from the database session := c.Env["token"].(*models.Token) data, err := json.Marshal(&events.Onboarding{ Account: session.Owner, }) if err != nil { utils.JSONResponse(w, 500, &AccountsStartOnboardingResponse{ Success: false, Message: "Unable to encode a message", }) return } if err := env.Producer.Publish("hook_onboarding", data); err != nil { utils.JSONResponse(w, 500, &AccountsCreateResponse{ Success: false, Message: "Unable to initialize onboarding emails", }) return } utils.JSONResponse(w, 200, &AccountsStartOnboardingResponse{ Success: true, Message: "Onboarding emails for your account have been initialized", }) }
// KeysList responds with the list of keys assigned to the spiecified email func KeysList(w http.ResponseWriter, r *http.Request) { // Get the username from the GET query user := r.URL.Query().Get("user") if user == "" { utils.JSONResponse(w, 409, &KeysListResponse{ Success: false, Message: "Invalid username", }) return } user = utils.RemoveDots(utils.NormalizeUsername(user)) address, err := env.Addresses.GetAddress(user) if err != nil { utils.JSONResponse(w, 409, &KeysListResponse{ Success: false, Message: "Invalid address", }) return } // Find all keys owner by user keys, err := env.Keys.FindByOwner(address.Owner) if err != nil { utils.JSONResponse(w, 500, &KeysListResponse{ Success: false, Message: "Internal server error (KE//LI/01)", }) return } // Respond with list of keys utils.JSONResponse(w, 200, &KeysListResponse{ Success: true, Keys: &keys, }) }
func deleteToken(w http.ResponseWriter, req *http.Request) { var user *models.Token if err := json.NewDecoder(req).Decode(&user); err != nil { utils.JSONResponse(w, 500, map[string]interface{}{ "error": err.Error(), }) return } user.ID = uniuri.NewLen(uniuri.UUIDLen) wr, err := r.Table("tokens").Insert(user).RunWrite(session) if err != nil { utils.JSONResponse(w, 500, map[string]interface{}{ "error": err.Error(), }) return } utils.JSONResponse(w, 200, map[string]interface{}{ "created": wr.Created, }) }
// ContactsList does *something* - TODO func ContactsList(c web.C, w http.ResponseWriter, r *http.Request) { // Fetch the current session from the database session := c.Env["token"].(*models.Token) // Get contacts from the database contacts, err := env.Contacts.GetOwnedBy(session.Owner) if err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), }).Error("Unable to fetch contacts") utils.JSONResponse(w, 500, &ContactsListResponse{ Success: false, Message: "Internal error (code CO/LI/01)", }) return } utils.JSONResponse(w, 200, &ContactsListResponse{ Success: true, Contacts: &contacts, }) }
// LabelsUpdate does *something* - TODO func LabelsUpdate(c web.C, w http.ResponseWriter, req *http.Request) { // Decode the request var input LabelsUpdateRequest 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, &LabelsUpdateResponse{ Success: false, Message: "Invalid input format", }) return } // Get the label from the database label, err := env.Labels.GetLabel(c.URLParams["id"]) if err != nil { utils.JSONResponse(w, 404, &LabelsUpdateResponse{ Success: false, Message: "Label not found", }) return } // Fetch the current session from the middleware session := c.Env["token"].(*models.Token) // Check for ownership if label.Owner != session.Owner { utils.JSONResponse(w, 404, &LabelsUpdateResponse{ Success: false, Message: "Label not found", }) return } if input.Name != "" { label.Name = input.Name } // Perform the update err = env.Labels.UpdateID(c.URLParams["id"], label) if err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), "id": c.URLParams["id"], }).Error("Unable to update a contact") utils.JSONResponse(w, 500, &LabelsUpdateResponse{ Success: false, Message: "Internal error (code LA/UP/01)", }) return } // Write the contact to the response utils.JSONResponse(w, 200, &LabelsUpdateResponse{ Success: true, Label: label, }) }
// ThreadsList shows all threads func ThreadsList(c web.C, w http.ResponseWriter, r *http.Request) { session := c.Env["token"].(*models.Token) var ( query = r.URL.Query() sortRaw = query.Get("sort") offsetRaw = query.Get("offset") limitRaw = query.Get("limit") labelsRaw = query.Get("label") labels []string sort []string offset int limit int ) if offsetRaw != "" { o, err := strconv.Atoi(offsetRaw) if err != nil { env.Log.WithFields(logrus.Fields{ "error": err, "offset": offset, }).Error("Invalid offset") utils.JSONResponse(w, 400, &ThreadsListResponse{ Success: false, Message: "Invalid offset", }) return } offset = o } if limitRaw != "" { l, err := strconv.Atoi(limitRaw) if err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), "limit": limit, }).Error("Invalid limit") utils.JSONResponse(w, 400, &ThreadsListResponse{ Success: false, Message: "Invalid limit", }) return } limit = l } if sortRaw != "" { sort = strings.Split(sortRaw, ",") } if labelsRaw != "" { labels = strings.Split(labelsRaw, ",") } threads, err := env.Threads.List(session.Owner, sort, offset, limit, labels) if err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), }).Error("Unable to fetch threads") utils.JSONResponse(w, 500, &ThreadsListResponse{ Success: false, Message: "Internal error (code TH/LI/01)", }) return } if offsetRaw != "" || limitRaw != "" { count, err := env.Threads.CountOwnedBy(session.Owner) if err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), }).Error("Unable to count threads") utils.JSONResponse(w, 500, &ThreadsListResponse{ Success: false, Message: "Internal error (code TH/LI/02)", }) return } w.Header().Set("X-Total-Count", strconv.Itoa(count)) } utils.JSONResponse(w, 200, &ThreadsListResponse{ Success: true, Threads: &threads, }) }
// ThreadsUpdate does *something* with a thread. func ThreadsUpdate(c web.C, w http.ResponseWriter, r *http.Request) { var input ThreadsUpdateRequest 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, &ThreadsUpdateResponse{ Success: false, Message: "Invalid input format", }) return } // Get the thread from the database thread, err := env.Threads.GetThread(c.URLParams["id"]) if err != nil { utils.JSONResponse(w, 404, &ThreadsUpdateResponse{ Success: false, Message: "Thread not found", }) return } // Fetch the current session from the middleware session := c.Env["token"].(*models.Token) // Check for ownership if thread.Owner != session.Owner { utils.JSONResponse(w, 404, &ContactsUpdateResponse{ Success: false, Message: "Contact not found", }) return } if input.Labels != nil && !reflect.DeepEqual(thread.Labels, input.Labels) { thread.Labels = input.Labels } if input.LastRead != nil && *input.LastRead != thread.LastRead { thread.LastRead = *input.LastRead } if input.IsRead != nil && *input.IsRead != thread.IsRead { thread.IsRead = *input.IsRead } // Disabled for now, as we're using DateModified for sorting by the date of the last email // thread.DateModified = time.Now() err = env.Threads.UpdateID(c.URLParams["id"], thread) if err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), "id": c.URLParams["id"], }).Error("Unable to update a thread") utils.JSONResponse(w, 500, &ThreadsUpdateResponse{ Success: false, Message: "Internal error (code TH/UP/01)", }) return } // Write the thread to the response utils.JSONResponse(w, 200, &ThreadsUpdateResponse{ Success: true, Thread: thread, }) }
// PrepareMux sets up the API func PrepareMux(flags *env.Flags) *web.Mux { // Set up a new logger log := logrus.New() // Set the formatter depending on the passed flag's value if flags.LogFormatterType == "text" { log.Formatter = &logrus.TextFormatter{ ForceColors: flags.ForceColors, } } else if flags.LogFormatterType == "json" { log.Formatter = &logrus.JSONFormatter{} } // Install Logrus hooks if flags.SlackURL != "" { var level []logrus.Level switch flags.SlackLevels { case "debug": level = slackrus.LevelThreshold(logrus.DebugLevel) case "error": level = slackrus.LevelThreshold(logrus.ErrorLevel) case "fatal": level = slackrus.LevelThreshold(logrus.FatalLevel) case "info": level = slackrus.LevelThreshold(logrus.InfoLevel) case "panic": level = slackrus.LevelThreshold(logrus.PanicLevel) case "warn": level = slackrus.LevelThreshold(logrus.WarnLevel) } log.Hooks.Add(&slackrus.SlackrusHook{ HookURL: flags.SlackURL, AcceptedLevels: level, Channel: flags.SlackChannel, IconEmoji: flags.SlackIcon, Username: flags.SlackUsername, }) } // Connect to raven var rc *raven.Client if flags.RavenDSN != "" { h, err := os.Hostname() if err != nil { log.Fatal(err) } rc, err = raven.NewClient(flags.RavenDSN, map[string]string{ "hostname": h, }) if err != nil { log.Fatal(err) } } env.Raven = rc // Pass it to the environment package env.Log = log // Load the bloom filter bf := bloom.NewWithEstimates(flags.BloomCount, 0.001) bff, err := os.Open(flags.BloomFilter) if err != nil { log.WithFields(logrus.Fields{ "error": err.Error(), }).Fatal("Unable to open the bloom filter file") } defer bff.Close() if _, err := bf.ReadFrom(bff); err != nil { log.WithFields(logrus.Fields{ "error": err.Error(), }).Fatal("Unable to read from the bloom filter file") } env.PasswordBF = bf // Initialize the cache redis, err := cache.NewRedisCache(&cache.RedisCacheOpts{ Address: flags.RedisAddress, Database: flags.RedisDatabase, Password: flags.RedisPassword, }) if err != nil { log.WithFields(logrus.Fields{ "error": err, }).Fatal("Unable to connect to the redis server") } env.Cache = redis // Set up the database rethinkOpts := gorethink.ConnectOpts{ Address: flags.RethinkDBAddress, AuthKey: flags.RethinkDBKey, MaxIdle: 10, Timeout: time.Second * 10, } err = db.Setup(rethinkOpts) if err != nil { log.WithFields(logrus.Fields{ "error": err, }).Fatal("Unable to set up the database") } // Initialize the actual connection rethinkOpts.Database = flags.RethinkDBDatabase rethinkSession, err := gorethink.Connect(rethinkOpts) if err != nil { log.WithFields(logrus.Fields{ "error": err, }).Fatal("Unable to connect to the database") } // Put the RethinkDB session into the environment package env.Rethink = rethinkSession // Initialize factors env.Factors = make(map[string]factor.Factor) if flags.YubiCloudID != "" { yubicloud, err := factor.NewYubiCloud(flags.YubiCloudID, flags.YubiCloudKey) if err != nil { env.Log.WithFields(logrus.Fields{ "error": err, }).Fatal("Unable to initiate YubiCloud") } env.Factors[yubicloud.Type()] = yubicloud } authenticator := factor.NewAuthenticator(6) env.Factors[authenticator.Type()] = authenticator // Initialize the tables env.Tokens = &db.TokensTable{ RethinkCRUD: db.NewCRUDTable( rethinkSession, rethinkOpts.Database, "tokens", ), Cache: redis, } env.Accounts = &db.AccountsTable{ RethinkCRUD: db.NewCRUDTable( rethinkSession, rethinkOpts.Database, "accounts", ), Tokens: env.Tokens, } env.Addresses = &db.AddressesTable{ RethinkCRUD: db.NewCRUDTable( rethinkSession, rethinkOpts.Database, "addresses", ), } env.Keys = &db.KeysTable{ RethinkCRUD: db.NewCRUDTable( rethinkSession, rethinkOpts.Database, "keys", ), } env.Contacts = &db.ContactsTable{ RethinkCRUD: db.NewCRUDTable( rethinkSession, rethinkOpts.Database, "contacts", ), } env.Reservations = &db.ReservationsTable{ RethinkCRUD: db.NewCRUDTable( rethinkSession, rethinkOpts.Database, "reservations", ), } env.Emails = &db.EmailsTable{ RethinkCRUD: db.NewCRUDTable( rethinkSession, rethinkOpts.Database, "emails", ), } env.Threads = &db.ThreadsTable{ RethinkCRUD: db.NewCRUDTable( rethinkSession, rethinkOpts.Database, "threads", ), } env.Labels = &db.LabelsTable{ RethinkCRUD: db.NewCRUDTable( rethinkSession, rethinkOpts.Database, "labels", ), Emails: env.Emails, //Cache: redis, } env.Files = &db.FilesTable{ Emails: env.Emails, RethinkCRUD: db.NewCRUDTable( rethinkSession, rethinkOpts.Database, "files", ), } // Create a producer producer, err := nsq.NewProducer(flags.NSQdAddress, nsq.NewConfig()) if err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), }).Fatal("Unable to create a new nsq producer") } /*defer func(producer *nsq.Producer) { producer.Stop() }(producer)*/ env.Producer = producer // Get the hostname hostname, err := os.Hostname() if err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), }).Fatal("Unable to get the hostname") } // Create a delivery consumer deliveryConsumer, err := nsq.NewConsumer("email_delivery", hostname, nsq.NewConfig()) if err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), "topic": "email_delivery", }).Fatal("Unable to create a new nsq consumer") } //defer deliveryConsumer.Stop() deliveryConsumer.AddConcurrentHandlers(nsq.HandlerFunc(func(m *nsq.Message) error { // Raven recoverer defer func() { rec := recover() if rec == nil { return } msg := &raven.Message{ Message: string(m.Body), Params: []interface{}{"delivery"}, } var packet *raven.Packet switch rval := recover().(type) { case error: packet = raven.NewPacket(rval.Error(), msg, raven.NewException(rval, raven.NewStacktrace(2, 3, nil))) default: str := fmt.Sprintf("%+v", rval) packet = raven.NewPacket(str, msg, raven.NewException(errors.New(str), raven.NewStacktrace(2, 3, nil))) } rc.Capture(packet, nil) }() var msg *struct { ID string `json:"id"` Owner string `json:"owner"` } if err := json.Unmarshal(m.Body, &msg); err != nil { return err } // Check if we are handling owner's session if _, ok := sessions[msg.Owner]; !ok { return nil } if len(sessions[msg.Owner]) == 0 { return nil } // Resolve the email email, err := env.Emails.GetEmail(msg.ID) if err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), "id": msg.ID, }).Error("Unable to resolve an email from queue") return nil } // Resolve the thread thread, err := env.Threads.GetThread(email.Thread) if err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), "id": msg.ID, "thread": email.Thread, }).Error("Unable to resolve a thread from queue") return nil } // Send notifications to subscribers for _, session := range sessions[msg.Owner] { result, _ := json.Marshal(map[string]interface{}{ "type": "delivery", "id": msg.ID, "name": email.Name, "thread": email.Thread, "labels": thread.Labels, }) err = session.Send(string(result)) if err != nil { env.Log.WithFields(logrus.Fields{ "id": session.ID(), "error": err.Error(), }).Warn("Error while writing to a WebSocket") } } return nil }), 10) if err := deliveryConsumer.ConnectToNSQLookupd(flags.LookupdAddress); err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), }).Fatal("Unable to connect to nsqlookupd") } // Create a receipt consumer receiptConsumer, err := nsq.NewConsumer("email_receipt", hostname, nsq.NewConfig()) if err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), "topic": "email_receipt", }).Fatal("Unable to create a new nsq consumer") } //defer receiptConsumer.Stop() receiptConsumer.AddConcurrentHandlers(nsq.HandlerFunc(func(m *nsq.Message) error { // Raven recoverer defer func() { rec := recover() if rec == nil { return } msg := &raven.Message{ Message: string(m.Body), Params: []interface{}{"receipt"}, } var packet *raven.Packet switch rval := recover().(type) { case error: packet = raven.NewPacket(rval.Error(), msg, raven.NewException(rval, raven.NewStacktrace(2, 3, nil))) default: str := fmt.Sprintf("%+v", rval) packet = raven.NewPacket(str, msg, raven.NewException(errors.New(str), raven.NewStacktrace(2, 3, nil))) } rc.Capture(packet, nil) }() var msg *struct { ID string `json:"id"` Owner string `json:"owner"` } if err := json.Unmarshal(m.Body, &msg); err != nil { return err } // Check if we are handling owner's session if _, ok := sessions[msg.Owner]; !ok { return nil } if len(sessions[msg.Owner]) == 0 { return nil } // Resolve the email email, err := env.Emails.GetEmail(msg.ID) if err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), "id": msg.ID, }).Error("Unable to resolve an email from queue") return nil } // Resolve the thread thread, err := env.Threads.GetThread(email.Thread) if err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), "id": msg.ID, "thread": email.Thread, }).Error("Unable to resolve a thread from queue") return nil } // Send notifications to subscribers for _, session := range sessions[msg.Owner] { result, _ := json.Marshal(map[string]interface{}{ "type": "receipt", "id": msg.ID, "name": email.Name, "thread": email.Thread, "labels": thread.Labels, }) err = session.Send(string(result)) if err != nil { env.Log.WithFields(logrus.Fields{ "id": session.ID(), "error": err.Error(), }).Warn("Error while writing to a WebSocket") } } return nil }), 10) if err := receiptConsumer.ConnectToNSQLookupd(flags.LookupdAddress); err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), }).Fatal("Unable to connect to nsqlookupd") } // 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 automatically responds to OPTIONS requests mux.Use(func(c *web.C, h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // sockjs doesn't want to work with our code, as the author doesn't understand http.Headers if strings.HasPrefix(r.RequestURI, "/ws") { h.ServeHTTP(w, r) return } // because why not w.Header().Set("Access-Control-Allow-Credentials", "true") allowedHeaders := []string{ "Origin", "Content-Type", "Authorization", "X-Requested-With", } reqHeaders := strings.Split(r.Header.Get("Access-Control-Request-Headers"), ",") allowedHeaders = append(allowedHeaders, reqHeaders...) resultHeaders := []string{} seenHeaders := map[string]struct{}{} for _, val := range allowedHeaders { if _, ok := seenHeaders[val]; !ok && val != "" { resultHeaders = append(resultHeaders, val) seenHeaders[val] = struct{}{} } } w.Header().Set("Access-Control-Allow-Headers", strings.Join(resultHeaders, ",")) /* if c.Env != nil { if v, ok := c.Env[web.ValidMethodsKey]; ok { if methods, ok := v.([]string); ok { methodsString := strings.Join(methods, ",") w.Header().Set("Allow", methodsString) w.Header().Set("Access-Control-Allow-Methods", methodsString) } } } */ // yolo w.Header().Set("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE") w.Header().Set("Access-Control-Allow-Origin", "*") if r.Method != "OPTIONS" { h.ServeHTTP(w, r) } }) }) mux.Use(middleware.RequestID) //mux.Use(glogrus.NewGlogrus(log, "api")) mux.Use(recoverer) mux.Use(middleware.AutomaticOptions) // Set up an auth'd mux auth := web.New() auth.Use(routes.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-data", routes.AccountsWipeData) auth.Post("/accounts/:id/start-onboarding", routes.AccountsStartOnboarding) // Addresses auth.Get("/addresses", routes.AddressesList) // Avatars mux.Get(regexp.MustCompile(`/avatars/(?P<hash>[\S\s]*?)\.(?P<ext>svg|png)(?:[\S\s]*?)$`), routes.Avatars) //mux.Get("/avatars/:hash.:ext", routes.Avatars) // Files auth.Get("/files", routes.FilesList) auth.Post("/files", routes.FilesCreate) auth.Get("/files/:id", routes.FilesGet) auth.Put("/files/:id", routes.FilesUpdate) auth.Delete("/files/:id", routes.FilesDelete) // Tokens auth.Get("/tokens", routes.TokensGet) auth.Get("/tokens/:id", routes.TokensGet) mux.Post("/tokens", routes.TokensCreate) auth.Delete("/tokens", routes.TokensDelete) auth.Delete("/tokens/:id", routes.TokensDelete) // Threads auth.Get("/threads", routes.ThreadsList) auth.Get("/threads/:id", routes.ThreadsGet) auth.Put("/threads/:id", routes.ThreadsUpdate) auth.Delete("/threads/:id", routes.ThreadsDelete) // Emails auth.Get("/emails", routes.EmailsList) auth.Post("/emails", routes.EmailsCreate) auth.Get("/emails/:id", routes.EmailsGet) 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 mux.Get("/keys", routes.KeysList) auth.Post("/keys", routes.KeysCreate) mux.Get("/keys/:id", routes.KeysGet) auth.Post("/keys/:id/vote", routes.KeysVote) // Headers proxy mux.Get("/headers", func(w http.ResponseWriter, r *http.Request) { utils.JSONResponse(w, 200, r.Header) }) mux.Handle("/ws/*", sockjs.NewHandler("/ws", sockjs.DefaultOptions, func(session sockjs.Session) { var subscribed string // A new goroutine seems to be spawned for each new session for { // Read a message from the input msg, err := session.Recv() if err != nil { if err != sockjs.ErrSessionNotOpen { env.Log.WithFields(logrus.Fields{ "id": session.ID(), "error": err.Error(), }).Warn("Error while reading from a WebSocket") } break } // Decode the message var input struct { Type string `json:"type"` Token string `json:"token"` ID string `json:"id"` Method string `json:"method"` Path string `json:"path"` Body string `json:"body"` Headers map[string]string `json:"headers"` } err = json.Unmarshal([]byte(msg), &input) if err != nil { // Return an error response resp, _ := json.Marshal(map[string]interface{}{ "type": "error", "error": err, }) err := session.Send(string(resp)) if err != nil { env.Log.WithFields(logrus.Fields{ "id": session.ID(), "error": err.Error(), }).Warn("Error while writing to a WebSocket") break } continue } // Check message's type if input.Type == "subscribe" { // Listen to user's events // Check if token is empty if input.Token == "" { // Return an error response resp, _ := json.Marshal(map[string]interface{}{ "type": "error", "error": "Invalid token", }) err := session.Send(string(resp)) if err != nil { env.Log.WithFields(logrus.Fields{ "id": session.ID(), "error": err.Error(), }).Warn("Error while writing to a WebSocket") break } continue } // Check the token in database token, err := env.Tokens.GetToken(input.Token) if err != nil { // Return an error response resp, _ := json.Marshal(map[string]interface{}{ "type": "error", "error": "Invalid token", }) err := session.Send(string(resp)) if err != nil { env.Log.WithFields(logrus.Fields{ "id": session.ID(), "error": err.Error(), }).Warn("Error while writing to a WebSocket") break } continue } // Do the actual subscription subscribed = token.Owner sessionsLock.Lock() // Sessions map already contains this owner if _, ok := sessions[token.Owner]; ok { sessions[token.Owner] = append(sessions[token.Owner], session) } else { // We have to allocate a new slice sessions[token.Owner] = []sockjs.Session{session} } // Unlock the map write sessionsLock.Unlock() // Return a response resp, _ := json.Marshal(map[string]interface{}{ "type": "subscribed", }) err = session.Send(string(resp)) if err != nil { env.Log.WithFields(logrus.Fields{ "id": session.ID(), "error": err.Error(), }).Warn("Error while writing to a WebSocket") break } } else if input.Type == "unsubscribe" { if subscribed == "" { resp, _ := json.Marshal(map[string]interface{}{ "type": "error", "error": "Not subscribed", }) err := session.Send(string(resp)) if err != nil { env.Log.WithFields(logrus.Fields{ "id": session.ID(), "error": err.Error(), }).Warn("Error while writing to a WebSocket") break } } sessionsLock.Lock() if _, ok := sessions[subscribed]; !ok { // Return a response resp, _ := json.Marshal(map[string]interface{}{ "type": "unsubscribed", }) err := session.Send(string(resp)) if err != nil { env.Log.WithFields(logrus.Fields{ "id": session.ID(), "error": err.Error(), }).Warn("Error while writing to a WebSocket") sessionsLock.Unlock() subscribed = "" break } sessionsLock.Unlock() subscribed = "" continue } if len(sessions[subscribed]) == 1 { delete(sessions, subscribed) // Return a response resp, _ := json.Marshal(map[string]interface{}{ "type": "unsubscribed", }) err := session.Send(string(resp)) if err != nil { env.Log.WithFields(logrus.Fields{ "id": session.ID(), "error": err.Error(), }).Warn("Error while writing to a WebSocket") sessionsLock.Unlock() subscribed = "" break } sessionsLock.Unlock() subscribed = "" continue } // Find the session index := -1 for i, session2 := range sessions[subscribed] { if session == session2 { index = i break } } // We didn't find anything if index == -1 { // Return a response resp, _ := json.Marshal(map[string]interface{}{ "type": "unsubscribed", }) err := session.Send(string(resp)) if err != nil { env.Log.WithFields(logrus.Fields{ "id": session.ID(), "error": err.Error(), }).Warn("Error while writing to a WebSocket") sessionsLock.Unlock() subscribed = "" break } sessionsLock.Unlock() subscribed = "" continue } // We found it, so we are supposed to slice it sessions[subscribed][index] = sessions[subscribed][len(sessions[subscribed])-1] sessions[subscribed][len(sessions[subscribed])-1] = nil sessions[subscribed] = sessions[subscribed][:len(sessions[subscribed])-1] // Return a response resp, _ := json.Marshal(map[string]interface{}{ "type": "unsubscribed", }) err := session.Send(string(resp)) if err != nil { env.Log.WithFields(logrus.Fields{ "id": session.ID(), "error": err.Error(), }).Warn("Error while writing to a WebSocket") sessionsLock.Unlock() subscribed = "" break } sessionsLock.Unlock() subscribed = "" } else if input.Type == "request" { // Perform the request w := httptest.NewRecorder() r, err := http.NewRequest(strings.ToUpper(input.Method), "http://api.lavaboom.io"+input.Path, strings.NewReader(input.Body)) if err != nil { env.Log.WithFields(logrus.Fields{ "id": session.ID(), "error": err.Error(), "path": input.Path, }).Warn("SockJS request error") // Return an error response resp, _ := json.Marshal(map[string]interface{}{ "error": err.Error(), }) err := session.Send(string(resp)) if err != nil { env.Log.WithFields(logrus.Fields{ "id": session.ID(), "error": err.Error(), }).Warn("Error while writing to a WebSocket") break } continue } r.Body = nopCloser{strings.NewReader(input.Body)} r.RequestURI = input.Path for key, value := range input.Headers { r.Header.Set(key, value) } mux.ServeHTTP(w, r) // Return the final response result, _ := json.Marshal(map[string]interface{}{ "type": "response", "id": input.ID, "status": w.Code, "headers": w.HeaderMap, "body": w.Body.String(), }) err = session.Send(string(result)) if err != nil { env.Log.WithFields(logrus.Fields{ "id": session.ID(), "error": err.Error(), }).Warn("Error while writing to a WebSocket") break } } } // We have to clear the subscription here too. TODO: make the code shorter if subscribed == "" { return } sessionsLock.Lock() if _, ok := sessions[subscribed]; !ok { sessionsLock.Unlock() return } if len(sessions[subscribed]) == 1 { delete(sessions, subscribed) sessionsLock.Unlock() return } // Find the session index := -1 for i, session2 := range sessions[subscribed] { if session == session2 { index = i break } } // We didn't find anything if index == -1 { sessionsLock.Unlock() return } // We found it, so we are supposed to slice it sessions[subscribed][index] = sessions[subscribed][len(sessions[subscribed])-1] sessions[subscribed][len(sessions[subscribed])-1] = nil sessions[subscribed] = sessions[subscribed][:len(sessions[subscribed])-1] // Unlock the mutex sessionsLock.Unlock() })) // Merge the muxes mux.Handle("/*", auth) // Compile the routes mux.Compile() return mux }
// EmailsList sends a list of the emails in the inbox. func EmailsList(c web.C, w http.ResponseWriter, r *http.Request) { // Fetch the current session from the database session := c.Env["token"].(*models.Token) // Parse the query var ( query = r.URL.Query() sortRaw = query.Get("sort") offsetRaw = query.Get("offset") limitRaw = query.Get("limit") thread = query.Get("thread") sort []string offset int limit int ) if offsetRaw != "" { o, err := strconv.Atoi(offsetRaw) if err != nil { env.Log.WithFields(logrus.Fields{ "error": err, "offset": offset, }).Error("Invalid offset") utils.JSONResponse(w, 400, &EmailsListResponse{ Success: false, Message: "Invalid offset", }) return } offset = o } if limitRaw != "" { l, err := strconv.Atoi(limitRaw) if err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), "limit": limit, }).Error("Invalid limit") utils.JSONResponse(w, 400, &EmailsListResponse{ Success: false, Message: "Invalid limit", }) return } limit = l } if sortRaw != "" { sort = strings.Split(sortRaw, ",") } // Get contacts from the database emails, err := env.Emails.List(session.Owner, sort, offset, limit, thread) if err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), }).Error("Unable to fetch emails") utils.JSONResponse(w, 500, &EmailsListResponse{ Success: false, Message: "Internal error (code EM/LI/01)", }) return } if offsetRaw != "" || limitRaw != "" { count, err := env.Emails.CountOwnedBy(session.Owner) if err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), }).Error("Unable to count emails") utils.JSONResponse(w, 500, &EmailsListResponse{ Success: false, Message: "Internal error (code EM/LI/02)", }) return } w.Header().Set("X-Total-Count", strconv.Itoa(count)) } utils.JSONResponse(w, 200, &EmailsListResponse{ Success: true, Emails: &emails, }) // GET parameters: // sort - split by commas, prefixes: - is desc, + is asc // offset, limit - for pagination // Pagination ADDS X-Total-Count to the response! }
// 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}, }) }
// LabelsList fetches all labels func LabelsList(c web.C, w http.ResponseWriter, req *http.Request) { session := c.Env["token"].(*models.Token) cursor, err := env.Labels.GetTable().GetAllByIndex("nameOwnerBuiltin", []interface{}{ "Spam", session.Owner, true, }, []interface{}{ "Trash", session.Owner, true, }, []interface{}{ "Sent", session.Owner, true, }).Run(env.Rethink) if err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), }).Error("Unable to get account's specified labels") utils.JSONResponse(w, 500, &LabelsListResponse{ Success: false, Message: err.Error(), }) return } defer cursor.Close() var spamTrashSent []*models.Label if err := cursor.All(&spamTrashSent); err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), }).Error("Unable to unmarshal account's specified labels") utils.JSONResponse(w, 500, &LabelsListResponse{ Success: false, Message: err.Error(), }) return } if len(spamTrashSent) != 3 { env.Log.WithFields(logrus.Fields{ "count": len(spamTrashSent), "account": session.Owner, }).Error("Invalid count of Trash, Sent and Spam labels") utils.JSONResponse(w, 500, &LabelsListResponse{ Success: false, Message: "Misconfigured account", }) return } cursor, err = env.Labels.GetTable().GetAllByIndex("owner", session.Owner).Map(func(label r.Term) r.Term { return label.Merge(map[string]interface{}{ "total_threads_count": env.Threads.GetTable().GetAllByIndex("labels", label.Field("id")).Count(), "unread_threads_count": env.Threads.GetTable().GetAllByIndex("labels", label.Field("id")).Filter(func(thread r.Term) r.Term { return r.Not(thread.Field("is_read")).And( r.Not( thread.Field("labels").Contains(spamTrashSent[0].ID).Or( thread.Field("labels").Contains(spamTrashSent[1].ID).Or( thread.Field("labels").Contains(spamTrashSent[2].ID), ), ), ), ) }).Count(), }) }).Run(env.Rethink) if err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), }).Error("Unable to get account's all labels") utils.JSONResponse(w, 500, &LabelsListResponse{ Success: false, Message: err.Error(), }) return } defer cursor.Close() var labels []*models.Label if err := cursor.All(&labels); err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), }).Error("Unable to unmarshal account's labels") utils.JSONResponse(w, 500, &LabelsListResponse{ Success: false, Message: err.Error(), }) return } utils.JSONResponse(w, 200, &LabelsListResponse{ Success: true, Labels: &labels, }) }