Skip to content

Feature: Allow interacting with tickets via email #2386

Closed
@MTecknology

Description

@MTecknology

As far as I can tell, gitea has no support for receiving email responses from users. When a user receives a message, their only option for response is to open the web interface and use a web browser. This is rarely convenient for discussions while mobile, especially if your git services passwords are >200 chars. I believe this is a critical feature that needs discussion and implementation.

From my perspective, the big picture pieces are:
1a. fetch messages
1b. receive messages
2. validate messages (including duplicate check)
3. append information to the ticket

(Note: ticket -> issue or pull)

1a. - I think IMAP is the standard. Gitea is already has source that interacts with IMAP/S services. It could just be a matter of marking a message as read, pulling it down, and dropping it into a processing queue. Of course, offering a DELETE_MESSAGE=(true|false) will always be appreciated.

1b. - Listening for an SMTP connection sounds neat, but please don't ever consider it. This would be a pretty scary attack surface for quite a number of reasons. (I typed out a lot here, but... tl;dr-- postfix kicks butt at being a mail server, gitea should not reinvent that wheel). Instead, I'd like to suggest listening on a unix socket.

In settings where this would be observed, you can expect to see custom-written middleware that does things like check the spamassassin score. This middleware then opens a socket to write the message content to.

In the case of 1b, it's almost exactly like 1a, except that it listens to a socket for messages instead of connecting to an outside service, trying to handle duplicate message race conditions, etc. The former (1a) would always work well for single-node setups where the latter (1b) would be available for handling scale.

2. - I envision another process pulling messages off of a queue (another redis db sounds nice) and doing some validation before letting the message be included in a ticket.

I'd like to start with opinions (stated as expectations):

  • A user may only respond to messages; always reply, never compose.
  • A user may only respond to the ticket the message was sent from,
  • Any computational verification should be minimally invasive (not taxing).
  • Verification should be nearly stateless; avoid storing large tables of tokens.
  • A user may not use their response to impersonate another user. (@strk: fun times!)
  • User-visible tokens must be cryptographically secure.

Now, for the fun part... I've been poking around at how github handles this and I think would could handle a simple implementation that meets (my) expectations. This would require adding a "salt" column to the user table.

I have a larger vision of an overall workflow, but starting the the explanation at this point is easiest. At 2. we will have a From: address that looks similar to github's. This is in the form "reply+<glob>@reply.domain.tld", where <glob> is a series of hashed and non-hashed values. We will also have an In-Reply-To: field that tells us where this message should be directed.

With <glob> taken out, we can dissect it into it's various components (my thoughts):

  • $user-id => Our copy of the user-id, hex value, left zero-padded to 10 digits
  • $key => A cryptographically pseudo-random (i.e. cat /dev/urandom | md5sum) value the user gets
  • $cksum => A value we generated with a combination of the $key + <the user's current salt>

From the Message-ID field, we will have these components available:

  • $locator => <team|user>/<project>/<issue|pull>/<number>
  • $replied-to => The specific message being replied to. (or just the timestamp of it)

[ note: ^ the sources of these values are what I skipped earlier .. coming up soon ]

To verify this message is a reply from a message we sent to that user (email.. considered good enough for password resets), we'll be able to run the formula:

user_hash = checksum($user-id + $key + _user_salt($user-id) + $locator + $replied-to)
if $user_hash != $cksum { drop_message() }
elif get_post_date($replied-to) >= 90days { drop_message() }
elif # could possibly also check From: and verify it came from an address on file (maybe...)
else { post_message($locator, $user-id, $message-body) }

That works because... When we sent the message, we set two fields: Reply-To and Message-ID.

Ex: Message-ID: <go-gitea/gitea/issue/2351/1220588887@domain.tld> ; the exact structure could be different, but I think this is fairly straight-forward (note: the produced message-id [https://en.wikipedia.org/wiki/Message-ID])must always) be globally unique--except when sending the same message to multiple recipents). This is the message the user is replying to and it's post-date gets to be used in our TTL/validity calculation. The mail client should turn this into In-Reply-To for us to verify later.

Ex: Reply-To: reply+<glob>@domain.tld ; This is where we'll want the message routed. We should be able to customize the exact "reply" user. This allows formats such as mycorp-inbound+<glob>@gmail.com without causing routing problems because this is a format that gmail permits.

The <glob> portion was explained earlier. It takes the form: $user-id$key$cksum.

2. summary:

In the end, all this does is generate a random value, combine it with a user salt and some other things, and then append some extra information to the email.

When we get the message back we verify that we can re-produce the $cksum they provided. Because they've never had access to the salt, this is a value they can't produce on their own. Provided the key is sufficiently large, I believe this is more than adequate to provide this feature without opening the doors for abuse. This allows us to automatically invalidate old tokens with one simple check (is $message-id older than X days?).

= Discussion!

I know this is massive, and probably not without mistakes, but I'd prefer get the discussion started since I can't see one person implementing this by themselves.

Metadata

Metadata

Assignees

No one assigned

    Labels

    type/proposalThe new feature has not been accepted yet but needs to be discussed first.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions