Implemented ReCaptcha registration method

Signed-off-by: Andrew (anoa) <anoa@openmailbox.org>
This commit is contained in:
Andrew (anoa) 2017-11-16 19:41:58 -08:00
parent 90396b5620
commit 6e65560ee3
No known key found for this signature in database
GPG key ID: 174BEAB009FD176D
3 changed files with 105 additions and 3 deletions

View file

@ -7,4 +7,5 @@ type LoginType string
const (
LoginTypeDummy = "m.login.dummy"
LoginTypeSharedSecret = "org.matrix.login.shared_secret"
LoginTypeRecaptcha = "m.login.recaptcha"
)

View file

@ -19,16 +19,18 @@ import (
"context"
"crypto/hmac"
"crypto/sha1"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"regexp"
"strings"
"time"
"github.com/matrix-org/dendrite/common/config"
log "github.com/sirupsen/logrus"
"github.com/matrix-org/dendrite/clientapi/auth"
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
"github.com/matrix-org/dendrite/clientapi/auth/storage/accounts"
@ -37,6 +39,7 @@ import (
"github.com/matrix-org/dendrite/clientapi/jsonerror"
"github.com/matrix-org/gomatrixserverlib"
"github.com/matrix-org/util"
log "github.com/sirupsen/logrus"
)
const (
@ -66,6 +69,9 @@ type authDict struct {
Type authtypes.LoginType `json:"type"`
Session string `json:"session"`
Mac gomatrixserverlib.HexString `json:"mac"`
// ReCaptcha
Response string `json:"response"`
// TODO: Lots of custom keys depending on the type
}
@ -106,6 +112,13 @@ type registerResponse struct {
DeviceID string `json:"device_id"`
}
type recaptchaResponse struct {
Success bool `json:"success"`
ChallengeTS time.Time `json:"challenge_ts"`
Hostname string `json:"hostname"`
ErrorCodes []int `json:"error-codes"`
}
// validateUserName returns an error response if the username is invalid
func validateUserName(username string) *util.JSONResponse {
// https://github.com/matrix-org/synapse/blob/v0.20.0/synapse/rest/client/v2_alpha/register.py#L161
@ -145,6 +158,64 @@ func validatePassword(password string) *util.JSONResponse {
return nil
}
// validateRecaptcha returns an error response if the captcha response is invalid
func validateRecaptcha(
cfg *config.Dendrite,
response string,
clientip string,
) *util.JSONResponse {
if response == "" {
return &util.JSONResponse{
Code: 400,
JSON: jsonerror.BadJSON("Captcha response is required"),
}
}
// Make a POST request to Google's API to check the captcha response
resp, err := http.PostForm(cfg.Matrix.RecaptchaSiteVerifyAPI,
url.Values{
"secret": {cfg.Matrix.RecaptchaPrivateKey},
"response": {response},
"remoteip": {clientip},
},
)
if err != nil {
return &util.JSONResponse{
Code: 500,
JSON: jsonerror.BadJSON("Error in requesting validation of captcha response"),
}
}
defer resp.Body.Close()
// Grab the body of the response from the captcha server
var r recaptchaResponse
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return &util.JSONResponse{
Code: 500,
JSON: jsonerror.BadJSON("Error in contacting captcha server" + err.Error()),
}
}
err = json.Unmarshal(body, &r)
if err != nil {
return &util.JSONResponse{
Code: 500,
JSON: jsonerror.BadJSON("Error in unmarshaling captcha server's response: " + err.Error()),
}
}
// Check that we received a "success"
if !r.Success {
return &util.JSONResponse{
Code: 401,
JSON: jsonerror.BadJSON("Invalid captcha response. Please try again."),
}
}
return nil
}
// Register processes a /register request. http://matrix.org/speculator/spec/HEAD/client_server/unstable.html#post-matrix-client-unstable-register
func Register(
req *http.Request,
@ -166,6 +237,7 @@ func Register(
// Server admins should be able to change things around (eg enable captcha)
JSON: newUserInteractiveResponse(time.Now().String(), []authFlow{
{[]authtypes.LoginType{authtypes.LoginTypeDummy}},
{[]authtypes.LoginType{authtypes.LoginTypeRecaptcha}},
{[]authtypes.LoginType{authtypes.LoginTypeSharedSecret}},
}),
}
@ -193,8 +265,25 @@ func Register(
// TODO: Handle loading of previous session parameters from database.
// TODO: Handle mapping registrationRequest parameters into session parameters
// TODO: email / msisdn / recaptcha auth types.
// TODO: email / msisdn auth types.
switch r.Auth.Type {
case authtypes.LoginTypeRecaptcha:
if !cfg.Matrix.RegistrationRecaptcha {
return util.MessageResponse(400, "Captcha registration is disabled")
}
logger := util.GetLogger(req.Context())
logger.WithFields(log.Fields{
"clientip": req.RemoteAddr,
"response": r.Auth.Response,
}).Info("Submitting recaptcha response")
// Check given captcha response
if resErr = validateRecaptcha(cfg, r.Auth.Response, req.RemoteAddr); resErr != nil {
return *resErr
}
return completeRegistration(req.Context(), accountDB, deviceDB, r.Username, r.Password)
case authtypes.LoginTypeSharedSecret:
if cfg.Matrix.RegistrationSharedSecret == "" {
return util.MessageResponse(400, "Shared secret registration is disabled")

View file

@ -25,8 +25,8 @@ import (
"strings"
"time"
"github.com/sirupsen/logrus"
"github.com/matrix-org/gomatrixserverlib"
"github.com/sirupsen/logrus"
"golang.org/x/crypto/ed25519"
"gopkg.in/yaml.v2"
@ -82,6 +82,18 @@ type Dendrite struct {
// If set, allows registration by anyone who also has the shared
// secret, even if registration is otherwise disabled.
RegistrationSharedSecret string `yaml:"registration_shared_secret"`
// This Home Server's ReCAPTCHA public key.
RecaptchaPublicKey string `yaml:"recaptcha_public_key"`
// This Home Server's ReCAPTCHA private key.
RecaptchaPrivateKey string `yaml:"recaptcha_private_key"`
// Boolean stating whether catpcha registration is enabled
// and required
RegistrationRecaptcha bool `yaml:"enable_registration_captcha"`
// Secret used to bypass the captcha registration entirely
RecaptchaBypassSecret string `yaml:"captcha_bypass_secret"`
// HTTP API endpoint used to verify whether the captcha response
// was successful
RecaptchaSiteVerifyAPI string `yaml:"recaptcha_siteverify_api"`
} `yaml:"matrix"`
// The configuration specific to the media repostitory.