Some have bemoaned Urbit's absence on the clearnet. Everyone accesses their urbits via HTTP, but few apps use that same affordance to expose themselves to the public at large.
But that's not very strange given the current state of HTTP access. As long as
you are authenticated as the host ship, you get first-class access to the ship
through Eyre's "channels" system, letting you pretend you are in userspace by
sending %poke
s and %watch
es as the host. However, if you are not
authenticated, none of that is available at all, leaving you at the mercy of
whatever HTTP APIs apps on that ship expose to the public. Generally, because
it requires additional implementation work, that is none at all.
Here, we propose a mechanism by which unauthenticated clients are assigned temporary, "fake" guest identities, and use those to interact with the host's userspace through the existing Eyre channels system.
Presently, requests are authenticated by including an urbauth-~sampel-hoster
cookie, where the ship name corresponds to the host ship, and the cookie
contains a unique key that identifies an authenticated session. These sessions
are created upon successful use of the login page, and expire after some time,
but are extended upon usage.
This proposal suggests that, in the absence of that cookie, Eyre should
automatically create a "guest session", and provide a cookie with the key for
it to the requester. Every such session has a randomly generated comet-length
@p
associated with it. (For extra flair, and a very soft indication of
provenance, Eyre may generate a comet name whose tail matches the host ship.)
Cookies for guest sessions follow the familiar behaviors: they expire after
some time, but get renewed upon use. More importantly, they provide access to
Eyre's channels. For userspace interactions made through channels, and for
HTTP requests that get routed into userspace, the src.bowl
gets set to the
comet associated with the session.
If a request comes in that does not provide any session cookies, a new guest cookie is minted and will be returned in the response. The handling of the request proceeds as if the new guest cookie was provided by the client. In other words, if a request is not authenticated, it will always have a guest session associated with it.
Channels created & used by guests behave exactly as normal channels, except
that the pokes and watches they send will have the guest identity in the
src.bowl
when they get processed by the agent.
Incoming HTTP requests may end up getting handled by a generator or userspace
agent. Presently, the src.bowl
in those cases will always be the host ship.
The authenticated
flag of the $inbound-request
will be set to true only if
the request included a valid authentication cookie.
For incoming requests that are not authenticated as the host ship, we set the
src.bowl
to be the identity of the guest session, but leave the
authenticated
flag as false. It may now be more appropriate to read that flag
as "did the ship in src.bowl
formally authenticate this request?", or, "is
the ship in src.bowl
real?".
This is, hopefully, the most minimally disruptive change. Presently, the
authenticated
flag is commonly used for checking if the request is coming
from the user that owns the host ship. This remains unchanged. It is somewhat
less common, but still not unheard of, to assert that =(src our):bowl
when
handling incoming HTTP requests. This too continues to behave as desired: not
providing responses for unauthenticated sessions.
However, with an eye towards stronger urb-ification of Eyre, developers are
now encouraged to use the src.bowl
as the primary way to check the provenance
of HTTP requests. The exact desired behavior may differ for specific cases,
but most should be well-served by simply reading the src.bowl
as one would
with other userspace events.
The /~/name
endpoint already exists and can be used to request the @p
the
requester is currently authenticated as. This will be updated to respond with
the guest identity for guest sessions.
Because this is no longer guaranteed to be equivalent to the host's @p
, but
there might still be a need to, say, poke an agent specifically on the host's
ship (in fact, guests must), we may want to add a /~/host
endpoint or
similar, to expose the @p
of the ship that is handling incoming requests.
Public access to userspace pokes and subscriptions has had real-world demand and implementations in the past.
The old urbit.org/stream1 did something along these lines, letting web randos post with comet identities into a designated chat channel. (I do not recall how exactly it worked. It preceded Lighter Than Eyre.)
The graph-stream-hook3 that powered pal.dev/lobby provided similar behavior, but did so by reimplementing parts of Eyre's channels system in userspace. Of course, it skips out on event queues and other reliability must-haves, because the stakes were low and the (re)implementation cost not worth it. It was backed by fakeid4, which implements guest cookies, approximately as described above. Podium5 was an attempt to generalize the "channels but in userspace" pattern.
Initially, using a separate urbauth-~sampel-hoster-guest
cookie seemed
appropriate. However, this makes it possible to receive multiple authentication
cookies within a single request. The easy answer is to just have "real"
authentication take precedence, but a stronger position is to say a client
should only ever be authenticated into a single session. So, while the client
may get assigned a guest session on first visit, logging in will overwrite the
associated cookie with one for a normal session instead.
We intentionally do not change the behavior of the /~/scry
endpoint, which
currently gives a 403
response for unauthenticated requests, even though
the ability to perform scries is very useful for clients. Scry authentication
has historically not been a real thing (even though the kernel hints at it, see
$gang
), resulting in the assumption that all scry calls come from the local
ship exclusively. This is bound to change in the future, but forcing the issue
for this particular proposal seems undesirable.
One can easily imagine memory pressure becoming a risk. But the channel system as it exists today already has logic for closing subscriptions in channels of uncooperative channels, and channels already get cleaned up after twelve hours of no use. It is possible to make these numbers tighter for guest channels, but unclear at this time whether that will actually be needed.
Additionally, trim events already prompt eyre to clean up presently-inactive channels, or even all channels if the trim is issued with maximum priority.
Direct access into userspace broadens the ways in which an urbit can be DoS-ed.
But this is not a new attack surface. Certainly HTTP requests of any kind can
be used for this. Agents can and should take care to enforce =(src our):bowl
where appropriate, both presently and after these changes.
Given that Eyre channels support poking arbitrary agents on arbitrary ships,
one might be concerned that this opens a vector through which malicious actors
can get anyone's urbit to send network packets. However, Eyre will construct
the $sock
(that is, the [from=@p to=@p]
for a Gall task) as
[~authenticated-as ~target-ship]
, and Gall asserts that at least one of those
must be our
. So, outsider cannot interact with non-local agents.
Even with Gall's enforcement in place, it is probably nicest for Eyre to perform this check within its own logic, to save on the round-trip, and more easily provide an explicit error message.
Agents may store (semi-)permanent state and associate it with the "fake" comet identity generated for a guest session. Alternatively, or as a consequence, agents may attempt to send ames packets to such a comet. But this is no different from the regular comet case, where it may pop into existence only briefly, flail around on the network, and then disappear forever. Ames already has a mechanism for expiring comms with comets. Agents might consider doing a similar clean-up on-trim (if only they were ever told about trim).
In response to socially malicious behavior (that is, being mean against people as opposed to infrastructure), users may want to block the user associated with a specific guest identity. It is entirely possible for Eyre to track IP addresses alongside the generated guest identities, and provide an IP blacklist whose requests we will always 403. The latter does not currently exist, but would be part of an abuse protection pass regardless of this proposal. For best effect, extend it into the runtime. (And, again, this is not too different from the normal comet case. Arguably we even have stronger responses here.)
By far the biggest risk here is ambiguous or confusing developer-facing
language. Both "session" and "authentication" can now refer to two different
kinds of themselves. Above, we have said "guest session" to disambiguate, but
rarely clarified on "authentication". Worse, there is an authentication
flag
in $inbound-request
that will be set to false when you are authenticated
into a guest session.
We may say we just have normal "sessions", always, and every session has a @p
associated with it. And instead of calling requests authenticated
or not, we
may instead specify whether their associated @p
is authentic
or not.
One may be slightly tempted to say the flag needs to be is-this-us
, but that
leaves it vulnerable to a future change we will probably want before too long:
In this proposal we have described the "authentication" flag as now meaning whether the identity of the request sender is "real" or not. That seems like it is overfitting slightly, the identity is either a fake guest, or our real selves.
However, it is not entirely unreasonable to imagine wanting to extend the courtesy we now grant to guests, to real Urbit Identities as well. Supporting some form of cross-ship authentication6, letting Eyre associate channels with real identities, would enable more traditional client-server style architectures. We already have server-server, which is nicer in most cases, but does require both parties to install the relevant app. One can imagine a "demo mode" where I can pair with you, or play a game with you, without committing to installing the app, but while also maintaining my own identity.
Very much outside of scope for this proposal, but seems fairly desirable and a logical extension to the "fake" identities used here, so worth keeping in mind while we figure out the exact desired semantics.
--
No, I mean being able to poke other ships from the frontend. I don't remember exactly, maybe it was disallowed in Gall instead, but I recall there being a check against
%poke-as
that meant remote pokes from eyre didn't work. It's been a while since I saw it though, has it changed since?