package connect import ( "amadan.net/oficina/estilo.git/homeconnect" "bytes" "net/http" "net/url" "strings" ) // OAuthContinuation ends the local POST before navigating to Keymail. Safari // otherwise applies form-action to the entire external redirect chain. // Wrap only the trusted local authentication plugin's Begin handler. func OAuthContinuation(origin string, next http.HandlerFunc, render func(http.ResponseWriter, *http.Request, string)) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if next == nil || render == nil { http.Error(w, "Sign-in unavailable.", http.StatusServiceUnavailable) return } result := &oauthResponse{header: make(http.Header)} next(result, r) destination := result.header.Get("Location") external := destination != "" && (!strings.HasPrefix(destination, "/") || strings.HasPrefix(destination, "//")) if result.status == http.StatusSeeOther && external { if !keymailDestination(destination, origin) { w.Header().Set("Cache-Control", "no-store") http.Error(w, "Sign-in unavailable.", http.StatusBadGateway) return } for _, cookie := range result.header.Values("Set-Cookie") { w.Header().Add("Set-Cookie", cookie) } w.Header().Set("Cache-Control", "no-store") w.Header().Set("Referrer-Policy", "no-referrer") render(w, r, destination) return } for name, values := range result.header { for _, value := range values { w.Header().Add(name, value) } } if result.status == 0 { result.status = http.StatusOK } w.WriteHeader(result.status) _, _ = w.Write(result.body.Bytes()) } } func keymailDestination(destination, origin string) bool { u, err := url.Parse(destination) if err != nil || u.Scheme != "https" || u.Host == "" || u.User != nil || u.Fragment != "" || u.Path != "/oauth/authorize" || u.RawPath != "" { return false } q, err := url.ParseQuery(u.RawQuery) if err != nil { return false } for _, key := range []string{"client_id", "redirect_uri", "scope", "code_challenge_method", "state", "code_challenge"} { if len(q[key]) != 1 || q.Get(key) == "" { return false } } origin = strings.TrimSuffix(origin, "/") return q.Get("client_id") == origin && q.Get("redirect_uri") == origin+"/auth/callback" && q.Get("scope") == "identify" && q.Get("code_challenge_method") == "S256" } type oauthResponse struct { header http.Header status int body bytes.Buffer } func (w *oauthResponse) Header() http.Header { return w.header } func (w *oauthResponse) WriteHeader(status int) { if w.status == 0 { w.status = status } } func (w *oauthResponse) Write(p []byte) (int, error) { if w.status == 0 { w.WriteHeader(http.StatusOK) } return w.body.Write(p) } type OAuthData = homeconnect.OAuthData func OAuthPage(destination string) OAuthData { return OAuthData{URL: destination, Title: "Continue signing in", ActionLabel: "Continue to Keymail"} }