Example #1
0
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,
	})
}
Example #2
0
// 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,
	})
}
Example #3
0
// 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,
	})
}
Example #4
0
// 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,
	})
}
Example #5
0
// 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,
	})
}
Example #6
0
// 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,
	})
}
Example #7
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,
	})
}
Example #8
0
// 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",
	})
}
Example #9
0
// 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,
	})
}
Example #10
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,
	})
}
Example #11
0
// 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",
	})
}
Example #12
0
// 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,
	})
}
Example #13
0
// 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)
	})
}
Example #14
0
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,
	})
}
Example #15
0
// 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,
	})
}
Example #16
0
// 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",
	})
}
Example #17
0
// 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",
	})
}
Example #18
0
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,
	})
}
Example #19
0
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))
}
Example #20
0
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",
	})
}
Example #21
0
// 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,
	})
}
Example #22
0
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,
	})
}
Example #23
0
// 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,
	})
}
Example #24
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,
	})
}
Example #25
0
// 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,
	})
}
Example #26
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,
	})
}
Example #27
0
// 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
}
Example #28
0
// 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!
}
Example #29
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},
	})
}
Example #30
0
// 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,
	})
}