// 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, }) }
// 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, }) }
// 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, }) }
// 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}, }) }
// 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, }) }
// AccountsUpdate allows changing the account's information (password etc.) func AccountsUpdate(c web.C, w http.ResponseWriter, r *http.Request) { // Decode the request var input AccountsUpdateRequest err := utils.ParseRequest(r, &input) if err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), }).Warn("Unable to decode a request") utils.JSONResponse(w, 400, &AccountsUpdateResponse{ Success: false, Message: "Invalid input format", }) return } // 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, &AccountsUpdateResponse{ 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 } if input.NewPassword != "" { if valid, _, err := user.VerifyPassword(input.CurrentPassword); err != nil || !valid { utils.JSONResponse(w, 403, &AccountsUpdateResponse{ Success: false, Message: "Invalid current password", }) 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, &AccountsUpdateResponse{ 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, &AccountsUpdateResponse{ Success: false, Message: "2FA token was not passed", FactorType: user.FactorType, FactorChallenge: challenge, }) return } // Token was incorrect if !verified { utils.JSONResponse(w, 403, &AccountsUpdateResponse{ Success: false, Message: "Invalid token passed", FactorType: user.FactorType, }) return } } } if input.NewPassword != "" && env.PasswordBF.TestString(input.NewPassword) { utils.JSONResponse(w, 400, &AccountsUpdateResponse{ Success: false, Message: "Weak new password", }) return } if input.NewPassword != "" { err = user.SetPassword(input.NewPassword) if err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), }).Error("Unable to hash a password") utils.JSONResponse(w, 500, &AccountsUpdateResponse{ Success: false, Message: "Internal error (code AC/UP/01)", }) return } } if input.AltEmail != "" { user.AltEmail = input.AltEmail } if input.Settings != nil { user.Settings = input.Settings } if input.PublicKey != "" { key, err := env.Keys.FindByFingerprint(input.PublicKey) if err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), "fingerprint": input.PublicKey, }).Error("Unable to find a key") utils.JSONResponse(w, 400, &AccountsUpdateResponse{ Success: false, Message: "Invalid public key", }) return } if key.Owner != user.ID { env.Log.WithFields(logrus.Fields{ "user_id:": user.ID, "owner": key.Owner, "fingerprint": input.PublicKey, }).Error("Unable to find a key") utils.JSONResponse(w, 400, &AccountsUpdateResponse{ Success: false, Message: "Invalid public key", }) return } user.PublicKey = input.PublicKey } if input.FactorType != "" { // Check if such factor exists if _, exists := env.Factors[input.FactorType]; !exists { utils.JSONResponse(w, 400, &AccountsUpdateResponse{ Success: false, Message: "Invalid new 2FA type", }) return } user.FactorType = input.FactorType } if input.FactorValue != nil && len(input.FactorValue) > 0 { user.FactorValue = input.FactorValue } user.DateModified = time.Now() err = env.Accounts.UpdateID(session.Owner, user) if err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), }).Error("Unable to update an account") utils.JSONResponse(w, 500, &AccountsUpdateResponse{ Success: false, Message: "Internal error (code AC/UP/02)", }) return } utils.JSONResponse(w, 200, &AccountsUpdateResponse{ Success: true, Message: "Your account has been successfully updated", Account: user, }) }
// 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 } }
// 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, }) }
// FilesUpdate updates an existing file in the database func FilesUpdate(c web.C, w http.ResponseWriter, r *http.Request) { // Decode the request var input FilesUpdateRequest 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, &FilesUpdateResponse{ Success: false, Message: "Invalid input format", }) return } // Get the file from the database file, err := env.Files.GetFile(c.URLParams["id"]) if err != nil { utils.JSONResponse(w, 404, &FilesUpdateResponse{ 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, &FilesUpdateResponse{ Success: false, Message: "File not found", }) return } if input.Data != "" { file.Data = input.Data } if input.Name != "" { file.Name = input.Name } if input.Encoding != "" { file.Encoding = input.Encoding } if input.VersionMajor != nil { file.VersionMajor = *input.VersionMajor } if input.VersionMinor != nil { file.VersionMinor = *input.VersionMinor } if input.PGPFingerprints != nil { file.PGPFingerprints = input.PGPFingerprints } // Perform the update err = env.Files.UpdateID(c.URLParams["id"], file) if err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), "id": c.URLParams["id"], }).Error("Unable to update a file") utils.JSONResponse(w, 500, &FilesUpdateResponse{ Success: false, Message: "Internal error (code FI/UP/01)", }) return } // Write the file to the response utils.JSONResponse(w, 200, &FilesUpdateResponse{ Success: true, File: file, }) }