oficina / meet Public

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

Plain git — no account needed to clone.

Download

Download this file

1package calls
2
3import (
4 "amadan.net/rastrillo/rastrillo/sessions"
5 "errors"
6 "html/template"
7 "net/http"
8 "net/url"
9
10 "github.com/go-chi/chi/v5"
11)
12
13// followLink is GET /j/{token}: one 404 for unknown, revoked, ended or
14// expired; otherwise one of three outcomes (spec §3.2). It is mounted
15// outside the session group because the person is often not signed in.
16func (a *app) followLink(w http.ResponseWriter, r *http.Request) {
17 link, m, live, err := a.store.liveLink(r.Context(), chi.URLParam(r, "token"))
18 if err != nil {
19 a.logger.Error("calls: follow link", "err", err)
20 a.ErrorPage(w, r, http.StatusInternalServerError, "")
21 return
22 }
23 if !live {
24 a.ErrorPage(w, r, http.StatusNotFound, "")
25 return
26 }
27 id, signedIn := a.auth.SessionFrom(r)
28 if !signedIn {
29 a.setJoinCookie(w, link.TokenHash)
30 if !m.RequireSignin {
31 a.guestPage(w, r, m, chi.URLParam(r, "token"), "", "")
32 return
33 }
34 http.Redirect(w, r, signinPath, http.StatusSeeOther)
35 return
36 }
37 p, err := a.personForSession(r.Context(), sessions.Session{Subject: id.Address, Method: string(id.Method), AuthTime: id.AuthTime, At: id.At})
38 if errors.Is(err, errHomeIdentity) {
39 a.ErrorPage(w, r, http.StatusNotFound, "")
40 return
41 }
42 if err != nil {
43 a.logger.Error("calls: ensure person", "err", err)
44 a.ErrorPage(w, r, http.StatusInternalServerError, "")
45 return
46 }
47 if p.Anonymous && m.RequireSignin {
48 a.setJoinCookie(w, link.TokenHash)
49 http.Redirect(w, r, signinPath, 303)
50 return
51 }
52 if !a.isMember(p) {
53 // Via the link, not the meeting: this handler read the link
54 // before it resolved the person, and a member may have replaced
55 // it in between. The re-read happens inside the grant's own
56 // transaction, and a link that is no longer live gets the same
57 // 404 an unknown token gets (review C1-3).
58 _, live, err := a.store.mintGrantViaLink(r.Context(), link.ID, p.ID)
59 if err != nil {
60 a.logger.Error("calls: mint grant", "err", err)
61 a.ErrorPage(w, r, http.StatusInternalServerError, "")
62 return
63 }
64 if !live {
65 a.ErrorPage(w, r, http.StatusNotFound, "")
66 return
67 }
68 }
69 http.Redirect(w, r, "/m/"+m.Sqid+"/join", http.StatusSeeOther)
70}
71
72// holding is GET /m/{sqid}/join, authorized by standing or a live
73// participation for this session (spec §2.5), the same row the room
74// page uses.
75func (a *app) holding(w http.ResponseWriter, r *http.Request) {
76 m, p, st, ok := a.roomAccess(w, r)
77 if !ok {
78 return
79 }
80 // This session's own peer, if it already has one here — a second
81 // tab open on the same meeting — points the person at the takeover
82 // confirm page rather than a plain join that would collide with it.
83 var openElsewhere bool
84 if session, ok := a.sessionKey(r); ok {
85 _, openElsewhere = a.hub.LiveForSession(m.Sqid, session)
86 }
87 title := a.store.title(m)
88 render(w, "join", a.page(r, onMeeting("", title), map[string]any{
89 "Sqid": m.Sqid, "Title": title, "Starts": a.fmtStart(m.StartsAt),
90 "Name": p.Name, "Standing": st.String(), "IsMember": st == StandingMember,
91 "Floor": !a.relayConfigured(), "OpenElsewhere": openElsewhere,
92 // Not oficina-calls://join?url=…/m/{sqid} (until 2026-10-02): the
93 // app has a session of its own, or none, and the meeting's
94 // address told it nothing about who this is, so a guest — and
95 // anyone whose default browser was not this one — could not get
96 // in. The offer hands this browser's person over (appOffer).
97 "AppHref": "/m/" + m.Sqid + "/app",
98 }))
99}
100
101// Only app-constructed meeting URLs are promoted to the custom URL scheme.
102func nativeJoinURL(meeting string) template.URL {
103 return template.URL("oficina-calls://join?url=" + url.QueryEscape(meeting))
104}
105