Exemple #1
0
// 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,
	})
}
Exemple #2
0
// 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,
	})
}
Exemple #3
0
// 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,
	})
}
Exemple #4
0
// 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},
	})
}
Exemple #5
0
// 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,
	})
}
Exemple #6
0
// 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,
	})
}
Exemple #7
0
// 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
	}
}
Exemple #8
0
// 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,
	})
}
Exemple #9
0
// 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,
	})
}
Exemple #10
0
// 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,
	})
}