Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import React from 'react';
import TopLevelGroup from '../../TopLevelGroup';
import useSettingGroup from '../../../hooks/useSettingGroup';
import {Separator, SettingGroupContent, TextArea, Toggle, withErrorBoundary} from '@tryghost/admin-x-design-system';
import {Separator, SettingGroupContent, TextArea, TextField, Toggle, withErrorBoundary} from '@tryghost/admin-x-design-system';
import {getSettingValue, getSettingValues} from '@tryghost/admin-x-framework/api/settings';
import {useGlobalData} from '../../providers/GlobalDataProvider';

const SpamFilters: React.FC<{ keywords: string[] }> = ({keywords}) => {
const {
Expand All @@ -21,16 +22,29 @@ const SpamFilters: React.FC<{ keywords: string[] }> = ({keywords}) => {
}
});

const {config} = useGlobalData();

const [initialBlockedEmailDomainsJSON] = getSettingValues(localSettings, ['blocked_email_domains']) as string[];
const initialBlockedEmailDomains = JSON.parse(initialBlockedEmailDomainsJSON || '[]') as string[];
const [blockedEmailDomains, setBlockedEmailDomains] = React.useState(initialBlockedEmailDomains.join('\n'));

const [captchaEnabled] = getSettingValues(localSettings, ['captcha_enabled']) as boolean[];
const [captchaSitekey, captchaSecret] = getSettingValues(localSettings, ['captcha_sitekey', 'captcha_secret']) as string[];
const handleToggleChange = (key: string, e: React.ChangeEvent<HTMLInputElement>) => {
updateSetting(key, e.target.checked);
handleEditingChange(true);
};

const handleSitekeyChange = (e: React.ChangeEvent<HTMLInputElement>) => {
updateSetting('captcha_sitekey', e.target.value);
handleEditingChange(true);
};

const handleSecretChange = (e: React.ChangeEvent<HTMLInputElement>) => {
updateSetting('captcha_secret', e.target.value);
handleEditingChange(true);
};

const labs = JSON.parse(getSettingValue<string>(localSettings, 'labs') || '{}');

const updateBlockedEmailDomainsSetting = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
Expand Down Expand Up @@ -95,11 +109,31 @@ const SpamFilters: React.FC<{ keywords: string[] }> = ({keywords}) => {
gap='gap-0'
hint={captchaHint}
label='Enable strict signup security'
labelClasses='block text-sm font-medium tracking-normal text-grey-900 w-full mt-[-10px]'
labelClasses='block text-sm font-medium tracking-normal text-grey-900 dark:text-grey-500 w-full mt-[-10px]'
onChange={(e) => {
handleToggleChange('captcha_enabled', e);
}}
/>
{/* Sitekey / secret are only modifiable in self-hoster setups */}
{config?.hostSettings?.captcha || (<>
<SettingGroupContent>
<TextField
hint="TODO Change: Unique identifier for your site"
maxLength={36} // UUIDv4 format
Comment on lines +121 to +122
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Update placeholder TODO hint text

The "TODO Change" in the hint text indicates that the copy needs to be updated before the PR is finalized. Consider providing a more descriptive hint that explains what a site key is and how to obtain it from hCaptcha.

placeholder="hCaptcha sitekey"
title="hCaptcha sitekey"
value={captchaSitekey}
onChange={handleSitekeyChange}
/>
<TextField
hint="TODO Change:Private secret key used to verify hCaptcha responses"
maxLength={100}
Comment on lines +129 to +130
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix typo in hint text and update the placeholder

There's a missing space after the colon in "TODO Change:Private", and the hint text needs to be updated. Consider providing clear instructions on how to obtain the secret key from hCaptcha.

- hint="TODO Change:Private secret key used to verify hCaptcha responses"
+ hint="TODO Change: Private secret key used to verify hCaptcha responses"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
hint="TODO Change:Private secret key used to verify hCaptcha responses"
maxLength={100}
hint="TODO Change: Private secret key used to verify hCaptcha responses"
maxLength={100}

placeholder="hCaptcha secret"
title="hCaptcha secret"
value={captchaSecret}
onChange={handleSecretChange} />
</SettingGroupContent>
</>)}
</>)}
</SettingGroupContent>
</TopLevelGroup>
Expand Down
12 changes: 9 additions & 3 deletions ghost/core/core/server/api/endpoints/settings-public.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,15 @@ const labs = require('../../../shared/labs');

const getCaptchaSettings = () => {
if (labs.isSet('captcha')) {
return {
captcha_sitekey: config.get('captcha:siteKey')
};
const siteKey = config.get('hostSettings:captcha:siteKey');
if (siteKey) {
return {
captcha_sitekey: siteKey
};
} else {
// Sitekey pulled from settings for self-hosters, no need to override
return {};
}
} else {
return {
captcha_enabled: false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ const EDITABLE_SETTINGS = [
'heading_font',
'blocked_email_domains',
'captcha_enabled',
'captcha_sitekey',
'captcha_secret',
'require_email_mfa'
];

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
const {addSetting, combineTransactionalMigrations} = require('../../utils');

module.exports = combineTransactionalMigrations([
addSetting({
key: 'captcha_sitekey',
value: null,
type: 'string',
group: 'members'
}),
addSetting({
key: 'captcha_secret',
value: null,
type: 'string',
group: 'members'
})
]);
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,14 @@
"isIn": [["true", "false"]]
},
"type": "boolean"
},
"captcha_sitekey": {
"defaultValue": null,
"type": "string"
},
"captcha_secret": {
"defaultValue": null,
"type": "string"
}
},
"portal": {
Expand Down
2 changes: 1 addition & 1 deletion ghost/core/core/server/services/members/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ function createApiInstance(config) {
captchaService: new CaptchaService({
enabled: labsService.isSet('captcha') && sharedConfig.get('captcha:enabled'),
scoreThreshold: sharedConfig.get('captcha:scoreThreshold'),
secretKey: sharedConfig.get('captcha:secretKey')
secretKey: sharedConfig.get('hostSettings:captcha:secretKey') || settingsCache.get('captcha_secret')
})
});

Expand Down
2 changes: 2 additions & 0 deletions ghost/core/core/shared/settings-cache/CacheManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ const _ = require('lodash');
* @property {string|null} support_email_address - Support email address
* @property {string|null} editor_default_email_recipients - Default email recipients for editor
* @property {boolean|null} captcha_enabled - Whether captcha is enabled
* @property {string|null} captcha_sitekey - Unique sitekey for hCaptcha
* @property {string|null} captcha_secret - Private key to validate hCaptcha responses
* @property {string|null} labs - JSON string of enabled labs features
* @property {never} [x] - Prevent accessing undefined properties
*/
Expand Down
Loading