2019-08-14 12:34:49 -05:00
|
|
|
// Copyright 2019 Parminder Singh <parmsingh129@gmail.com>
|
|
|
|
//
|
|
|
|
// 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 (
|
2022-12-23 07:11:11 -06:00
|
|
|
"fmt"
|
2019-08-14 12:34:49 -05:00
|
|
|
"html/template"
|
|
|
|
"net/http"
|
|
|
|
|
|
|
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
2020-12-02 11:41:00 -06:00
|
|
|
"github.com/matrix-org/dendrite/setup/config"
|
2019-08-14 12:34:49 -05:00
|
|
|
"github.com/matrix-org/util"
|
|
|
|
)
|
|
|
|
|
|
|
|
// recaptchaTemplate is an HTML webpage template for recaptcha auth
|
|
|
|
const recaptchaTemplate = `
|
|
|
|
<html>
|
|
|
|
<head>
|
|
|
|
<title>Authentication</title>
|
|
|
|
<meta name='viewport' content='width=device-width, initial-scale=1,
|
|
|
|
user-scalable=no, minimum-scale=1.0, maximum-scale=1.0'>
|
2022-10-28 05:25:01 -05:00
|
|
|
<script src="{{.apiJsUrl}}" async defer></script>
|
2019-08-14 12:34:49 -05:00
|
|
|
<script src="//code.jquery.com/jquery-1.11.2.min.js"></script>
|
|
|
|
<script>
|
|
|
|
function captchaDone() {
|
|
|
|
$('#registrationForm').submit();
|
|
|
|
}
|
|
|
|
</script>
|
|
|
|
</head>
|
|
|
|
<body>
|
|
|
|
<form id="registrationForm" method="post" action="{{.myUrl}}">
|
|
|
|
<div>
|
|
|
|
<p>
|
|
|
|
Hello! We need to prevent computer programs and other automated
|
|
|
|
things from creating accounts on this server.
|
|
|
|
</p>
|
|
|
|
<p>
|
|
|
|
Please verify that you're not a robot.
|
|
|
|
</p>
|
|
|
|
<input type="hidden" name="session" value="{{.session}}" />
|
2022-10-28 05:25:01 -05:00
|
|
|
<div class="{{.sitekeyClass}}"
|
|
|
|
data-sitekey="{{.sitekey}}"
|
2019-08-14 12:34:49 -05:00
|
|
|
data-callback="captchaDone">
|
|
|
|
</div>
|
|
|
|
<noscript>
|
|
|
|
<input type="submit" value="All Done" />
|
|
|
|
</noscript>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</form>
|
|
|
|
</body>
|
|
|
|
</html>
|
|
|
|
`
|
|
|
|
|
|
|
|
// successTemplate is an HTML template presented to the user after successful
|
|
|
|
// recaptcha completion
|
|
|
|
const successTemplate = `
|
|
|
|
<html>
|
|
|
|
<head>
|
|
|
|
<title>Success!</title>
|
|
|
|
<meta name='viewport' content='width=device-width, initial-scale=1,
|
|
|
|
user-scalable=no, minimum-scale=1.0, maximum-scale=1.0'>
|
|
|
|
<script>
|
|
|
|
if (window.onAuthDone) {
|
|
|
|
window.onAuthDone();
|
|
|
|
} else if (window.opener && window.opener.postMessage) {
|
|
|
|
window.opener.postMessage("authDone", "*");
|
|
|
|
}
|
|
|
|
</script>
|
|
|
|
</head>
|
|
|
|
<body>
|
|
|
|
<div>
|
|
|
|
<p>Thank you!</p>
|
|
|
|
<p>You may now close this window and return to the application.</p>
|
|
|
|
</div>
|
|
|
|
</body>
|
|
|
|
</html>
|
|
|
|
`
|
|
|
|
|
|
|
|
// 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,
|
2020-08-10 08:18:04 -05:00
|
|
|
cfg *config.ClientAPI,
|
2022-12-23 07:11:11 -06:00
|
|
|
) {
|
|
|
|
// 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
|
|
|
|
}
|
2019-08-14 12:34:49 -05:00
|
|
|
|
2022-12-23 07:11:11 -06:00
|
|
|
sessionID := req.URL.Query().Get("session")
|
2019-08-14 12:34:49 -05:00
|
|
|
if sessionID == "" {
|
2022-12-23 07:11:11 -06:00
|
|
|
writeHTTPMessage(w, req,
|
2019-08-14 12:34:49 -05:00
|
|
|
"Session ID not provided",
|
|
|
|
http.StatusBadRequest,
|
|
|
|
)
|
2022-12-23 07:11:11 -06:00
|
|
|
return
|
2019-08-14 12:34:49 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
serveRecaptcha := func() {
|
|
|
|
data := map[string]string{
|
2022-10-28 05:25:01 -05:00
|
|
|
"myUrl": req.URL.String(),
|
|
|
|
"session": sessionID,
|
|
|
|
"apiJsUrl": cfg.RecaptchaApiJsUrl,
|
|
|
|
"sitekey": cfg.RecaptchaPublicKey,
|
|
|
|
"sitekeyClass": cfg.RecaptchaSitekeyClass,
|
|
|
|
"formField": cfg.RecaptchaFormField,
|
2019-08-14 12:34:49 -05:00
|
|
|
}
|
|
|
|
serveTemplate(w, recaptchaTemplate, data)
|
|
|
|
}
|
|
|
|
|
|
|
|
serveSuccess := func() {
|
|
|
|
data := map[string]string{}
|
|
|
|
serveTemplate(w, successTemplate, data)
|
|
|
|
}
|
|
|
|
|
|
|
|
if req.Method == http.MethodGet {
|
|
|
|
// Handle Recaptcha
|
2022-12-23 07:11:11 -06:00
|
|
|
serveRecaptcha()
|
|
|
|
return
|
2019-08-14 12:34:49 -05:00
|
|
|
} else if req.Method == http.MethodPost {
|
|
|
|
// Handle Recaptcha
|
2022-12-23 07:11:11 -06:00
|
|
|
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
|
2019-08-14 12:34:49 -05:00
|
|
|
}
|
|
|
|
|
2022-12-23 07:11:11 -06:00
|
|
|
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
|
2019-08-14 12:34:49 -05:00
|
|
|
}
|
|
|
|
|
2022-12-23 07:11:11 -06:00
|
|
|
// Success. Add recaptcha as a completed login flow
|
|
|
|
sessions.addCompletedSessionStage(sessionID, authtypes.LoginTypeRecaptcha)
|
|
|
|
|
|
|
|
serveSuccess()
|
|
|
|
return
|
2019-08-14 12:34:49 -05:00
|
|
|
}
|
2022-12-23 07:11:11 -06:00
|
|
|
writeHTTPMessage(w, req, "Bad method", http.StatusMethodNotAllowed)
|
2019-08-14 12:34:49 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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,
|
2022-12-23 07:11:11 -06:00
|
|
|
) {
|
2019-08-14 12:34:49 -05:00
|
|
|
w.WriteHeader(header)
|
|
|
|
_, err := w.Write([]byte(message))
|
|
|
|
if err != nil {
|
2020-03-02 10:20:44 -06:00
|
|
|
util.GetLogger(req.Context()).WithError(err).Error("w.Write failed")
|
2019-08-14 12:34:49 -05:00
|
|
|
}
|
|
|
|
}
|