Private, friends-only, IndieWeb stuff

Yesterday I participated in the IndieWeb sensitive data pop-up, or at least the first half of it (I had to disappear for my refrigerator delivery). It was really great to have some further discussion about what people want out of this stuff and how we’re all going to agree to get it.

Authentication stuff

One of the biggest pain points that keeps on coming up is there being no support for people to be able to get private posts without having to log in or be notified about them in side channels. Lots of people are doing things like making pages with unguessable URLs and then doing side-channel notification, but that’s unwieldy; fewer folks are doing things with actual login mechanisms.

In Publ I support both approaches, and also templates are allowed to get limited data about unauthorized posts so that they can provide some sort of generic notification about unauthorized content. For example, in my Atom template I have the following:

{% for entry in view.entries(unauthorized=1 if not user else 0) %}
    <entry>
        {% if entry.authorized %}
            <title>{{'🔏 ' if entry.private else ''}}{{entry.category.name ~ ": " if entry.category.name != category.name}}{{entry.title(markup=False, smartquotes=False)}}</title>
            <!-- the rest of the authorized entry metadata goes here -->
            <content type="html"><![CDATA[
                {% if entry.private %}
                    <aside><i><mark>Note:</mark> This is a private entry, so please use discretion in linking to it or mentioning it publicly. Thanks!</i></aside>
                {% endif %}
                <!-- The rest of the entry content goes here -->
            ]]></content>
        {% else %}
            <title>🔏 Private entry [{{entry.title(always_show=True).split()|join(attribute=0)}}]</title>
            <link href="{{ entry.permalink(absolute=True) }}" rel="alternate" type="text/html" />
            <published>{{entry.date.isoformat()}}</published>
            <updated>{{entry.last_modified.isoformat()}}</updated>
            <id>urn:uuid:{{entry.uuid}}</id>
            <author><name>{{ entry.author if entry.author else "fluffy" }}</name></author>
            <content type="html">This entry has a restricted audience.</content>
        {% endif %}
    </entry>
{% endfor %}

What this does is:

  • If the user (or their feed reader) is authorized to view the entry, show it directly

    Further, if the entry has a privacy restriction, indicate that clearly in both the title and the <aside> blurb.

  • If they are not authorized, show a stub entry that indicates that there is a private entry available

    In this context, Publ limits the kind of data that’s available for an entry, and takes a secure-by-default approach; entry.permalink can only generate slugless, categoryless short-links (e.g. https://example.com/12345), and very few other metadata items are available at all (basically just the bare minimum that’s necessary for Atom feeds, really). entry.title will normally not be generated, but the always_show=True parameter overrides that, and in this case the feed template uses that to generate an initialism of the post title, just for the convenience of readers (for example, a post with a title of This thing that keeps on PISSING ME OFF will appear as [TttkoPMO] in the unauthorized feed).

Also, for an extra layer of leak-avoidance, Publ has to be told how many unauthorized entries to even allow in the view to begin with; view.entries(unauthorized=1) means only the most recent unauthorized entry will even get a stub. This way, random people can’t see how many private posts I’ve been making recently without a lot of work. (The if not user check is so that if someone is logged in, no stubs get generated at all — if someone’s signed in there’s no sense in telling them there’s entries they still can’t see!)

Granted, there are ways that they could figure out which days have private entries, or they could infer it by looking at the Twitter autoposts, or they could subscribe to my feed and log all the private posts that occur going forward, but I find this an acceptable tradeoff.

But! My hope is that this tradeoff won’t be necessary forever. I’ve long been very interested in supporting a protocol like AutoAuth or TicketAuth. Neither has gotten a lot of traction, unfortunately; AutoAuth has one experimental implementation that I know of, and adding support for it requires a significant amount of agreement between a lot of moving parts (for example, it needs to be supported by the IndieAuth endpoint, its associated token endpoint, the feed reader, and the publisher), whereas TicketAuth seems a lot more feasible (as it only requires support from a purpose-specific TicketAuth endpoint and the publisher, and then the reader needs to be able to consume the ticket in some way, and also the publisher’s side of things is much, much simpler). TicketAuth also only tries to answer the question about how a ticket gets sent in the first place, and doesn’t make any specific interaction requirements on how a publisher opts to grant a ticket to a reader.

I already have most of the functionality in Publ for supporting TicketAuth; for example, there is full support for bearer tokens (want one? get it from your user profile!) and if you already have a means of associating a bearer token with your feed fetcher, you can get fully-authenticated feeds already; for example, curl https://beesbuzz.biz/feed -H 'Authorization: Bearer [TOKEN GOES HERE]' — and this will work on any page request). What’s missing is the actual token grant flow.

My intention has been that when someone logs in to my site, the profile parser (which lives in Authl) will see if there’s a ticketauth endpoint and provide that to Publ, and if Publ sees that, it’ll enqueue a ticket granting transaction. I was debating whether Publ should do the profile parsing itself with the hope that non-IndieAuth things support this (after all, that should be possible), but the possibility of that ever happening seems pretty remote, so it’d be better to just have it live in the identity providers. Anyway, if hell freezes over and Twitter does support arbitrary federated social media endpoints, it’d probably be in their user-specific JSON blob and not on the user profile page anyway, right?

Mastodon support seems less-remote, but Mastodon already has a different means of getting private posts (via ActivityPub delivery filtering), and supporting that would involve natively supporting ActivityPub, which is a lot of work, but something I do want to do… someday… eventually…

I’ve also been wanting to hack a TicketAuth endpoint into Feed-on-Feeds, but its security model makes that a really bad idea. Improving the security model is one of my many goals for Subl, which I swear I’ll get around to working on someday. I suppose that adding bearer token support to FoF is possible, but it’d have the caveat that FoF doesn’t have any means of doing per-user data on a feed or any way of doing a per-user subscription, and it seems really risky to allow this on a multi-user installation. I suppose I could hack it in for my own personal uses but it just seems like a gigantic privacy risk right now. So I’d rather not.

Anyway. The lack of TicketAuth support has been a big chicken-egg situation; I’ve been really wanting to implement the publisher side of TicketAuth, but with no receivers out there it just didn’t feel like a worthwhile time investment. But! As a result of yesterday’s popup, several folks (including David Shanske) have voiced support towards implementing a TicketAuth endpoint, which at least provides an initial test case for this.

To reiterate what David said:

But first things first. Let’s build something.

I absolutely promise that I will build TicketAuth publishing into Publ, and when someone has built a TicketAuth endpoint I will work with them to make sure it actually works.

Heck, I should build a standalone TicketAuth endpoint just as a proof-of-concept. And for testing. And so on.

Yeah. Let’s build something.

Other content limiting stuff

Another topic which came up is the desire to have an entry have different content visible to different viewers; for example, friends can see “I went out to lunch with Barack Obama” while others can only see “I went out to lunch with a well-known politician.” Back in the day when I was running Movable Type and had all sorts of unwieldy hacks for friends-only access, I was able to do this, as an additional layer of hacks.

Since my MT templates were still statically-generating files which happened to be PHP, I could put something like this into my entries:

Today I had lunch with <?= $user_friend ? "Barack Obama" : "a well-known politician" ?>

and it would work. Or at least it did until DreamHost beefed up their mod_security settings and detected this as a PHP injection attack (which, to be fair, it was).

Publ doesn’t have any current support for inline conditional content, although I’d be tempted to try adding it. I sort of want to hold off on markup-related extensions until I get around to switching to a more flexible Markdown processor, although I suppose this sort of thing would be better served in the entry body parser anyway. Like maybe adding a custom XMLish tag that runs blocks through Jinja before handing it off to the actual renderer. That would be tricky to do right though, and I’m not sure how much benefit there’d be.

That said, Publ does have an incredibly unwieldy mechanism that could be used for this sort of thing, in the form of entry attachments. Right now I only use attachments for comic transcripts, but one of the intended use cases would be to provide “cut” sections, and since attachments get the same per-entry security model (since attachments are just entries) it’d be possible to have my content block be something like:

{{entry.body}}

{% for attach in entry.attachments(order='title') %}
{{attach.body}}
{% endfor %}

and then have different versions of attachments for different permission types. I don’t really know of a good way to do the fallback, though, and managing this from the UX standpoint would be ridiculous. (There’s also a few other things I want to rework with attachments anyway, like it should be possible for something to attach itself to something else, rather than requiring an entry to declare its attachments.)

Comments

Before commenting, please read the comment policy.

Avatars provided via Libravatar