MediaWiki core and extensions use email in a number of different ways (see a survey here); most of these only share code at the lowest level of functionality (via UserMailer, responsible for actually sending out the email but not managing contents or targets in any way). Because of T355450: mediawiki support for one-click unsubscribe we'll need to adjust many of these by adding a machine-readable and machine-usable unsubscribe link (see here for technical details). This means we will need to provide an URL which encodes a user identity and an action ("unsubscribe from type X email") and isn't spoofable (you can't unsubscribe someone else by constructing the URL and clicking on it, even though the URL will need to work without session cookies).
This task proposes a way of doing that that can also be reused to do one-click actions from emails more generally - it's no extra work compared to implementing one-click unsubscribe, and could be useful for other things (such as Gmail one-lick actions).
Requirements
- The system should not hard-code what "unsubscribe" means. Usually it's unsetting a user preference, but it might be something else (removing a page from a watchlist, muting a user). This also allows easy reusability if we want non-unsubscribe-related one-click actions from emails.
- The system needs to be able to assure the action is coming from the right user (ie. that it wouldn't be possible to construct the URL for the action without access to the email) without relying on any other authentication (such as session cookies or interactive login), per RFC 8058.
- RFC 8058 specifies a POST endpoint, but it should also work with GET so we can reuse it for unsubscribe links in emails as the user might have an easier way of finding those, or their mail client might not support automatic unsubscribe; or we want to include multiple types of unsubscription as links (e.g. "unsubscribe from this type of Echo notification" vs "mute notifications from this user" vs "mute notifications from this page")
- In the GET case, we should show a user-friendly landing page that tells the user what happened and how to undo it (in the case of an misclick / changed minds).
Proposal
- Create a helper for converting between a tuple of (user, action, ...further action-specific parameters) and a "one-click token", a string that's suitable for inclusion into an URL and cryptographically secured against tampering (signed, or signed and encrypted). Preferably in some standard way, such as a JWT.
- Provide a mechanism by which any code can register a one-click action, and there is some central dispatcher which can take a one-click token and calls the suitable handler. The registration could be done with hooks, but maybe more elegant to have a registry service and extension.json attributes with ObjectFactory specs.
- Provide an action handler for the common case of setting a specific user preference to a specific value.
- Provide an endpoint that can take a one-click token and dispatch it; probably a REST API route. It should support GET and POST, and allow for write on GET (in Wikimedia's case, that means being routed to the primary DC).
- Make it easy to associate a one-click unsubscribe URL with an email: add an option to UserMailer::send() / Emailer::send() which takes the token, generates the URL and adds the appropriate List-Unsubscribe and List-Unsubscribe-Post headers.