// Copyright 2019 Parminder Singh // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package routing import ( "html/template" "net/http" "github.com/matrix-org/dendrite/clientapi/auth/authtypes" "github.com/matrix-org/dendrite/clientapi/jsonerror" "github.com/matrix-org/dendrite/setup/config" "github.com/matrix-org/util" ) // recaptchaTemplate is an HTML webpage template for recaptcha auth const recaptchaTemplate = ` Authentication

Hello! We need to prevent computer programs and other automated things from creating accounts on this server.

Please verify that you're not a robot.

` // successTemplate is an HTML template presented to the user after successful // recaptcha completion const successTemplate = ` Success!

Thank you!

You may now close this window and return to the application.

` // serveTemplate fills template data and serves it using http.ResponseWriter func serveTemplate(w http.ResponseWriter, templateHTML string, data map[string]string) { t := template.Must(template.New("response").Parse(templateHTML)) if err := t.Execute(w, data); err != nil { panic(err) } } // AuthFallback implements GET and POST /auth/{authType}/fallback/web?session={sessionID} func AuthFallback( w http.ResponseWriter, req *http.Request, authType string, cfg *config.ClientAPI, ) *util.JSONResponse { sessionID := req.URL.Query().Get("session") if sessionID == "" { return writeHTTPMessage(w, req, "Session ID not provided", http.StatusBadRequest, ) } serveRecaptcha := func() { data := map[string]string{ "myUrl": req.URL.String(), "session": sessionID, "siteKey": cfg.RecaptchaPublicKey, } serveTemplate(w, recaptchaTemplate, data) } serveSuccess := func() { data := map[string]string{} serveTemplate(w, successTemplate, data) } if req.Method == http.MethodGet { // Handle Recaptcha if authType == authtypes.LoginTypeRecaptcha { if err := checkRecaptchaEnabled(cfg, w, req); err != nil { return err } serveRecaptcha() return nil } return &util.JSONResponse{ Code: http.StatusNotFound, JSON: jsonerror.NotFound("Unknown auth stage type"), } } else if req.Method == http.MethodPost { // Handle Recaptcha if authType == authtypes.LoginTypeRecaptcha { if err := checkRecaptchaEnabled(cfg, w, req); err != nil { return err } clientIP := req.RemoteAddr err := req.ParseForm() if err != nil { util.GetLogger(req.Context()).WithError(err).Error("req.ParseForm failed") res := jsonerror.InternalServerError() return &res } response := req.Form.Get("g-recaptcha-response") if err := validateRecaptcha(cfg, response, clientIP); err != nil { util.GetLogger(req.Context()).Error(err) return err } // Success. Add recaptcha as a completed login flow sessions.addCompletedSessionStage(sessionID, authtypes.LoginTypeRecaptcha) serveSuccess() return nil } return &util.JSONResponse{ Code: http.StatusNotFound, JSON: jsonerror.NotFound("Unknown auth stage type"), } } return &util.JSONResponse{ Code: http.StatusMethodNotAllowed, JSON: jsonerror.NotFound("Bad method"), } } // checkRecaptchaEnabled creates an error response if recaptcha is not usable on homeserver. func checkRecaptchaEnabled( cfg *config.ClientAPI, w http.ResponseWriter, req *http.Request, ) *util.JSONResponse { if !cfg.RecaptchaEnabled { return writeHTTPMessage(w, req, "Recaptcha login is disabled on this Homeserver", http.StatusBadRequest, ) } return nil } // writeHTTPMessage writes the given header and message to the HTTP response writer. // Returns an error JSONResponse obtained through httputil.LogThenError if the writing failed, otherwise nil. func writeHTTPMessage( w http.ResponseWriter, req *http.Request, message string, header int, ) *util.JSONResponse { w.WriteHeader(header) _, err := w.Write([]byte(message)) if err != nil { util.GetLogger(req.Context()).WithError(err).Error("w.Write failed") res := jsonerror.InternalServerError() return &res } return nil }