oficina / sheets Public

Clone
git clone https://amadan.net/oficina/sheets

Plain git — no account needed to clone.

Download

Download this file

1package sheets
2
3import (
4 "net/http"
5
6 "amadan.net/oficina/home/connect"
7 "amadan.net/rastrillo/rastrillo/auth"
8)
9
10// handleBegin is POST /signin, in both modes: auth's Begin for EVERY
11// address, under connect.OAuthContinuation.
12//
13// What the form answers depends on the address's domain, never on
14// admission. Answering "that address cannot sign in" — or answering an
15// admitted address differently from a refused one in any way — would
16// turn this endpoint into a membership oracle: anyone could enumerate
17// who is on the team by watching which addresses get a different
18// answer. So:
19//
20// - A Keymail address (a domain that runs Keymail, or delegates to it
21// with a `_keymail` TXT record), admitted or not, goes to Keymail.
22// Begin answers with a 303 to Keymail's authorize URL, and
23// OAuthContinuation turns that into a page (renderHomeOAuth): CSP's
24// form-action 'self' covers a form submission's whole redirect
25// chain in Safari, so a 303 out of the POST was refused there
26// ("Refused to load https://keymail.dev/oauth/authorize… form-action",
27// Paul, 2026-10-06). Admission is asked after Keymail has verified
28// the address, by auth's Authorize (App.Admits), and a refusal is
29// the door saying so (App.refused), told only to someone who has
30// just proved they hold the address.
31// - Any other address is answered ?sent=1, and mailQueue mails the
32// link only if App.Admits says yes — after the response, in a
33// worker, so not even the response time differs (mailqueue.go). Without that, anyone who
34// can reach the instance could make it email a live sign-in link to
35// any address they name — abuse of our sending identity, found by
36// POSTing a stranger's address at the deployed instance. The cost
37// is real and accepted: a member who mistypes their own address is
38// told to check their email and nothing arrives.
39//
40// Both kinds of address spend the same rate budgets and are parsed the
41// same way, so ?err=rate and ?err=address say nothing about admission
42// either; and since the send happens after the response, a failed one
43// is logged rather than answered.
44//
45// It used to gate admission in front of Begin instead, which answered a
46// non-admitted Keymail address ?sent=1 and sent an admitted one to
47// Keymail: the oracle, one level narrower.
48func (a *App) handleBegin(w http.ResponseWriter, r *http.Request) {
49 connect.OAuthContinuation(a.origin, a.auth.Begin, a.renderHomeOAuth)(w, r)
50}
51
52// refused is auth's Refused hook: Keymail or an emailed link verified
53// the address and Admits said no. It answers with the sign-in door,
54// carrying the refusal, in a 403 — no session is minted, and auth has
55// already ended the attempt.
56//
57// Rendered, not redirected to ?err=refused: a query parameter is
58// something anybody can type, and the sentence means "you proved this
59// address and it is not admitted" (Astra on oficina/slides #20, round
60// 3). Only this hook can say it, and auth calls it only after the
61// address is proved, so saying it is no membership oracle.
62func (a *App) refused(w http.ResponseWriter, r *http.Request, _ auth.Identity) {
63 a.renderDoor(w, r, http.StatusForbidden,
64 "That address can’t sign in to Sheets. Use a different address, or ask someone to share a sheet with it.", false)
65}
66