diff --git a/changelog.d/17984.feature b/changelog.d/17984.feature new file mode 100644 index 00000000000..609f3a7cd8c --- /dev/null +++ b/changelog.d/17984.feature @@ -0,0 +1 @@ +Add `recaptcha_private_key_path` and `recaptcha_public_key_path` config option. \ No newline at end of file diff --git a/docs/usage/configuration/config_documentation.md b/docs/usage/configuration/config_documentation.md index 74522a1e4e5..714d6bd7c5b 100644 --- a/docs/usage/configuration/config_documentation.md +++ b/docs/usage/configuration/config_documentation.md @@ -2295,6 +2295,22 @@ Example configuration: ```yaml recaptcha_public_key: "YOUR_PUBLIC_KEY" ``` +--- +### `recaptcha_public_key_path` + +An alternative to [`recaptcha_public_key`](#recaptcha_public_key): +allows the public key to be specified in an external file. + +The file should be a plain text file, containing only the public key. +Synapse reads the public key from the given file once at startup. + +Example configuration: +```yaml +recaptcha_public_key_path: /path/to/key/file +``` + +_Added in Synapse 1.121.0._ + --- ### `recaptcha_private_key` @@ -2306,6 +2322,22 @@ Example configuration: ```yaml recaptcha_private_key: "YOUR_PRIVATE_KEY" ``` +--- +### `recaptcha_private_key_path` + +An alternative to [`recaptcha_private_key`](#recaptcha_private_key): +allows the private key to be specified in an external file. + +The file should be a plain text file, containing only the private key. +Synapse reads the private key from the given file once at startup. + +Example configuration: +```yaml +recaptcha_private_key_path: /path/to/key/file +``` + +_Added in Synapse 1.121.0._ + --- ### `enable_registration_captcha` diff --git a/synapse/config/captcha.py b/synapse/config/captcha.py index 84897c09c54..28b93a8458c 100644 --- a/synapse/config/captcha.py +++ b/synapse/config/captcha.py @@ -23,7 +23,17 @@ from synapse.types import JsonDict -from ._base import Config, ConfigError +from ._base import Config, ConfigError, read_file + +CONFLICTING_RECAPTCHA_PRIVATE_KEY_OPTS_ERROR = """\ +You have configured both `recaptcha_private_key` and +`recaptcha_private_key_path`. These are mutually incompatible. +""" + +CONFLICTING_RECAPTCHA_PUBLIC_KEY_OPTS_ERROR = """\ +You have configured both `recaptcha_public_key` and `recaptcha_public_key_path`. +These are mutually incompatible. +""" class CaptchaConfig(Config): @@ -31,6 +41,13 @@ class CaptchaConfig(Config): def read_config(self, config: JsonDict, **kwargs: Any) -> None: recaptcha_private_key = config.get("recaptcha_private_key") + recaptcha_private_key_path = config.get("recaptcha_private_key_path") + if recaptcha_private_key_path: + if recaptcha_private_key: + raise ConfigError(CONFLICTING_RECAPTCHA_PRIVATE_KEY_OPTS_ERROR) + recaptcha_private_key = read_file( + recaptcha_private_key_path, ("recaptcha_private_key_path",) + ).strip() if recaptcha_private_key is not None and not isinstance( recaptcha_private_key, str ): @@ -38,6 +55,13 @@ def read_config(self, config: JsonDict, **kwargs: Any) -> None: self.recaptcha_private_key = recaptcha_private_key recaptcha_public_key = config.get("recaptcha_public_key") + recaptcha_public_key_path = config.get("recaptcha_public_key_path") + if recaptcha_public_key_path: + if recaptcha_public_key: + raise ConfigError(CONFLICTING_RECAPTCHA_PUBLIC_KEY_OPTS_ERROR) + recaptcha_public_key = read_file( + recaptcha_public_key_path, ("recaptcha_public_key_path",) + ).strip() if recaptcha_public_key is not None and not isinstance( recaptcha_public_key, str ): diff --git a/tests/config/test_load.py b/tests/config/test_load.py index f8f7b72e401..f0c4058e9f2 100644 --- a/tests/config/test_load.py +++ b/tests/config/test_load.py @@ -132,6 +132,8 @@ def test_depreciated_identity_server_flag_throws_error(self) -> None: "turn_shared_secret_path: /does/not/exist", "registration_shared_secret_path: /does/not/exist", "macaroon_secret_key_path: /does/not/exist", + "recaptcha_private_key_path: /does/not/exist", + "recaptcha_public_key_path: /does/not/exist", *["redis:\n enabled: true\n password_path: /does/not/exist"] * (hiredis is not None), ] @@ -157,6 +159,14 @@ def test_secret_files_missing(self, config_str: str) -> None: "macaroon_secret_key_path: {}", lambda c: c.key.macaroon_secret_key, ), + ( + "recaptcha_private_key_path: {}", + lambda c: c.captcha.recaptcha_private_key.encode("utf-8"), + ), + ( + "recaptcha_public_key_path: {}", + lambda c: c.captcha.recaptcha_public_key.encode("utf-8"), + ), *[ ( "redis:\n enabled: true\n password_path: {}",