// 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 ( "fmt" "html/template" "net/http" "github.com/matrix-org/dendrite/clientapi/auth/authtypes" "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, ) { // We currently only support "m.login.recaptcha", so fail early if that's not requested if authType == authtypes.LoginTypeRecaptcha { if !cfg.RecaptchaEnabled { writeHTTPMessage(w, req, "Recaptcha login is disabled on this Homeserver", http.StatusBadRequest, ) return } } else { writeHTTPMessage(w, req, fmt.Sprintf("Unknown authtype %q", authType), http.StatusNotImplemented) return } sessionID := req.URL.Query().Get("session") if sessionID == "" { writeHTTPMessage(w, req, "Session ID not provided", http.StatusBadRequest, ) return } serveRecaptcha := func() { data := map[string]string{ "myUrl": req.URL.String(), "session": sessionID, "apiJsUrl": cfg.RecaptchaApiJsUrl, "sitekey": cfg.RecaptchaPublicKey, "sitekeyClass": cfg.RecaptchaSitekeyClass, "formField": cfg.RecaptchaFormField, } serveTemplate(w, recaptchaTemplate, data) } serveSuccess := func() { data := map[string]string{} serveTemplate(w, successTemplate, data) } if req.Method == http.MethodGet { // Handle Recaptcha serveRecaptcha() return } else if req.Method == http.MethodPost { // Handle Recaptcha clientIP := req.RemoteAddr err := req.ParseForm() if err != nil { util.GetLogger(req.Context()).WithError(err).Error("req.ParseForm failed") w.WriteHeader(http.StatusBadRequest) serveRecaptcha() return } response := req.Form.Get(cfg.RecaptchaFormField) err = validateRecaptcha(cfg, response, clientIP) switch err { case ErrMissingResponse: w.WriteHeader(http.StatusBadRequest) serveRecaptcha() // serve the initial page again, instead of nothing return case ErrInvalidCaptcha: w.WriteHeader(http.StatusUnauthorized) serveRecaptcha() return case nil: default: // something else failed util.GetLogger(req.Context()).WithError(err).Error("failed to validate recaptcha") serveRecaptcha() return } // Success. Add recaptcha as a completed login flow sessions.addCompletedSessionStage(sessionID, authtypes.LoginTypeRecaptcha) serveSuccess() return } writeHTTPMessage(w, req, "Bad method", http.StatusMethodNotAllowed) } // 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, ) { w.WriteHeader(header) _, err := w.Write([]byte(message)) if err != nil { util.GetLogger(req.Context()).WithError(err).Error("w.Write failed") } }