mirror of
https://github.com/matrix-org/dendrite.git
synced 2025-12-14 10:23:46 -06:00
Implemented ReCaptcha registration method
Signed-off-by: Andrew (anoa) <anoa@openmailbox.org>
This commit is contained in:
parent
90396b5620
commit
6e65560ee3
|
|
@ -7,4 +7,5 @@ type LoginType string
|
||||||
const (
|
const (
|
||||||
LoginTypeDummy = "m.login.dummy"
|
LoginTypeDummy = "m.login.dummy"
|
||||||
LoginTypeSharedSecret = "org.matrix.login.shared_secret"
|
LoginTypeSharedSecret = "org.matrix.login.shared_secret"
|
||||||
|
LoginTypeRecaptcha = "m.login.recaptcha"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -19,16 +19,18 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/matrix-org/dendrite/common/config"
|
"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"
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
||||||
"github.com/matrix-org/dendrite/clientapi/auth/storage/accounts"
|
"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/dendrite/clientapi/jsonerror"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -66,6 +69,9 @@ type authDict struct {
|
||||||
Type authtypes.LoginType `json:"type"`
|
Type authtypes.LoginType `json:"type"`
|
||||||
Session string `json:"session"`
|
Session string `json:"session"`
|
||||||
Mac gomatrixserverlib.HexString `json:"mac"`
|
Mac gomatrixserverlib.HexString `json:"mac"`
|
||||||
|
|
||||||
|
// ReCaptcha
|
||||||
|
Response string `json:"response"`
|
||||||
// TODO: Lots of custom keys depending on the type
|
// TODO: Lots of custom keys depending on the type
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -106,6 +112,13 @@ type registerResponse struct {
|
||||||
DeviceID string `json:"device_id"`
|
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
|
// validateUserName returns an error response if the username is invalid
|
||||||
func validateUserName(username string) *util.JSONResponse {
|
func validateUserName(username string) *util.JSONResponse {
|
||||||
// https://github.com/matrix-org/synapse/blob/v0.20.0/synapse/rest/client/v2_alpha/register.py#L161
|
// 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
|
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
|
// Register processes a /register request. http://matrix.org/speculator/spec/HEAD/client_server/unstable.html#post-matrix-client-unstable-register
|
||||||
func Register(
|
func Register(
|
||||||
req *http.Request,
|
req *http.Request,
|
||||||
|
|
@ -166,6 +237,7 @@ func Register(
|
||||||
// Server admins should be able to change things around (eg enable captcha)
|
// Server admins should be able to change things around (eg enable captcha)
|
||||||
JSON: newUserInteractiveResponse(time.Now().String(), []authFlow{
|
JSON: newUserInteractiveResponse(time.Now().String(), []authFlow{
|
||||||
{[]authtypes.LoginType{authtypes.LoginTypeDummy}},
|
{[]authtypes.LoginType{authtypes.LoginTypeDummy}},
|
||||||
|
{[]authtypes.LoginType{authtypes.LoginTypeRecaptcha}},
|
||||||
{[]authtypes.LoginType{authtypes.LoginTypeSharedSecret}},
|
{[]authtypes.LoginType{authtypes.LoginTypeSharedSecret}},
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|
@ -193,8 +265,25 @@ func Register(
|
||||||
// TODO: Handle loading of previous session parameters from database.
|
// TODO: Handle loading of previous session parameters from database.
|
||||||
// TODO: Handle mapping registrationRequest parameters into session parameters
|
// TODO: Handle mapping registrationRequest parameters into session parameters
|
||||||
|
|
||||||
// TODO: email / msisdn / recaptcha auth types.
|
// TODO: email / msisdn auth types.
|
||||||
switch r.Auth.Type {
|
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:
|
case authtypes.LoginTypeSharedSecret:
|
||||||
if cfg.Matrix.RegistrationSharedSecret == "" {
|
if cfg.Matrix.RegistrationSharedSecret == "" {
|
||||||
return util.MessageResponse(400, "Shared secret registration is disabled")
|
return util.MessageResponse(400, "Shared secret registration is disabled")
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"golang.org/x/crypto/ed25519"
|
"golang.org/x/crypto/ed25519"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
|
|
||||||
|
|
@ -82,6 +82,18 @@ type Dendrite struct {
|
||||||
// If set, allows registration by anyone who also has the shared
|
// If set, allows registration by anyone who also has the shared
|
||||||
// secret, even if registration is otherwise disabled.
|
// secret, even if registration is otherwise disabled.
|
||||||
RegistrationSharedSecret string `yaml:"registration_shared_secret"`
|
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"`
|
} `yaml:"matrix"`
|
||||||
|
|
||||||
// The configuration specific to the media repostitory.
|
// The configuration specific to the media repostitory.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue