package connect import ( "errors" "net/http" "amadan.net/oficina/home/connect/linkstore" "amadan.net/oficina/home/handoff" ) // Link routes belong behind the app's CSRF, session, identity and admission // middleware. They recheck the actual cookie at each sensitive transition. func (b *Bridge) LinkPage(w http.ResponseWriter, r *http.Request) { if !b.guard(w, r, http.MethodGet, "/account/home") { return } session, err := b.actualSession(r) if err != nil || session == nil { b.fail(w, r, http.StatusForbidden) return } person, err := b.existingPerson(r, *session) if err != nil { b.fail(w, r, http.StatusForbidden) return } b.render(w, r, "start", person.Label, http.StatusOK) } func (b *Bridge) LinkStart(w http.ResponseWriter, r *http.Request) { if !b.guard(w, r, http.MethodPost, "/account/home") { return } for _, name := range []string{homeInitCookie, homeProofCookie} { if _, _, ok := homeCookie(r, name); !ok { b.fail(w, r, http.StatusBadRequest) return } } local, _, err := b.localBinding(r) // The reviewed local reauthentication continuation is wired separately. // Stale local authentication cannot begin or reuse a linking initiation. if err != nil { b.fail(w, r, http.StatusForbidden) return } path := "/" if b.returnPath != nil { if candidate := b.returnPath(r); handoff.SafePath(candidate) { path = candidate } } secret, err := b.links.Start(r.Context(), local, path, b.now()) if err != nil { b.linkFailure(w, r, err) return } setHomeCookie(w, homeInitCookie, secret, 300) setHomeCookie(w, homeProofCookie, "", -1) http.Redirect(w, r, "/auth/start?return_to=%2Faccount%2Fhome%2Fconfirm", http.StatusSeeOther) } func (b *Bridge) ConfirmPage(w http.ResponseWriter, r *http.Request) { if !b.guard(w, r, http.MethodGet, "/account/home/confirm") { return } secret, present, ok := homeCookie(r, homeProofCookie) if !ok || !present { b.fail(w, r, http.StatusBadRequest) return } local, person, err := b.localBinding(r) if err != nil { b.fail(w, r, http.StatusForbidden) return } if _, err := b.links.Pending(r.Context(), secret, local, b.now()); err != nil { if errors.Is(err, linkstore.ErrRefused) { setHomeCookie(w, homeProofCookie, "", -1) } b.linkFailure(w, r, err) return } b.render(w, r, "confirm", person.Label, http.StatusOK) } func (b *Bridge) ConfirmPost(w http.ResponseWriter, r *http.Request) { if !b.guard(w, r, http.MethodPost, "/account/home/confirm") { return } secret, present, ok := homeCookie(r, homeProofCookie) if !ok || !present { b.fail(w, r, http.StatusBadRequest) return } local, _, err := b.localBinding(r) if err != nil { b.fail(w, r, http.StatusForbidden) return } pending, err := b.links.Confirm(r.Context(), secret, local, b.now()) if err == nil || errors.Is(err, linkstore.ErrRefused) { setHomeCookie(w, homeProofCookie, "", -1) } if err != nil { b.linkFailure(w, r, err) return } http.Redirect(w, r, pending.ReturnPath, http.StatusSeeOther) } func (b *Bridge) linkFailure(w http.ResponseWriter, r *http.Request, err error) { status := http.StatusServiceUnavailable if errors.Is(err, linkstore.ErrRefused) { status = http.StatusForbidden } b.fail(w, r, status) }