From 83e18259edeb45a74d8ef7fb3115d349e6291724 Mon Sep 17 00:00:00 2001 From: Danny Rorabaugh Date: Tue, 16 Jul 2024 10:19:15 -0400 Subject: [PATCH 01/24] Replace CAPTCHA with Turnstile (with test keys) --- .github/workflows/deploy_qa.yml | 3 +- Backend/Controllers/UserController.cs | 26 +++++- package-lock.json | 10 +++ package.json | 1 + src/api/api/user-api.ts | 107 +++++++++++++++++++++++ src/backend/index.ts | 8 ++ src/components/Login/Login.tsx | 7 +- src/components/Login/Signup.tsx | 7 +- src/components/Login/Turnstile.tsx | 33 +++++++ src/components/PasswordReset/Request.tsx | 10 ++- 10 files changed, 199 insertions(+), 13 deletions(-) create mode 100644 src/components/Login/Turnstile.tsx diff --git a/.github/workflows/deploy_qa.yml b/.github/workflows/deploy_qa.yml index bcab832f05..f3102f9374 100644 --- a/.github/workflows/deploy_qa.yml +++ b/.github/workflows/deploy_qa.yml @@ -4,6 +4,7 @@ on: push: branches: - master + - turnstile permissions: contents: read @@ -94,7 +95,7 @@ jobs: deploy_update: needs: build # Only push to the QA server when built on the master branch - if: ${{ github.ref_name == 'master' }} + #if: ${{ github.ref_name == 'master' }} runs-on: [self-hosted, thecombine] steps: - name: Harden Runner diff --git a/Backend/Controllers/UserController.cs b/Backend/Controllers/UserController.cs index cbf0fa3175..d532a740d5 100644 --- a/Backend/Controllers/UserController.cs +++ b/Backend/Controllers/UserController.cs @@ -1,5 +1,7 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using System.Net.Http; using System.Threading.Tasks; using BackendFramework.Helper; using BackendFramework.Interfaces; @@ -22,6 +24,8 @@ public class UserController : Controller private readonly IPasswordResetService _passwordResetService; private readonly IPermissionService _permissionService; + private const string TurnstileVerifyUrl = "https://challenges.cloudflare.com/turnstile/v0/siteverify"; + public UserController(IUserRepository userRepo, IPermissionService permissionService, IEmailService emailService, IPasswordResetService passwordResetService) { @@ -31,6 +35,26 @@ public UserController(IUserRepository userRepo, IPermissionService permissionSer _permissionService = permissionService; } + /// Validates a Cloudflare Turnstile token + [AllowAnonymous] + [HttpGet("turnstile/{token}", Name = "ValidateTurnstile")] + [ProducesResponseType(StatusCodes.Status200OK)] + public async Task ValidateTurnstile(string token) + { + var secret = Environment.GetEnvironmentVariable("TURNSTILE_SECRET_KEY"); + var httpContent = new FormUrlEncodedContent(new KeyValuePair[]{ + new ("response", token), + //new ("secret", secret ?? "1x0000000000000000000000000000000AA"), // pass + new ("secret", secret ?? "2x0000000000000000000000000000000AA"), // fail + //new ("secret", secret ?? "3x0000000000000000000000000000000AA"), // token spent + }); + using var result = await new HttpClient().PostAsync(TurnstileVerifyUrl, httpContent); + var contentString = await result.Content.ReadAsStringAsync(); + Console.WriteLine($"content: {contentString}"); + return contentString.Contains("\"success\":true") ? Ok() : BadRequest(); + } + + /// Sends a password reset request [AllowAnonymous] [HttpPost("forgot", Name = "ResetPasswordRequest")] diff --git a/package-lock.json b/package-lock.json index ddf7e4f281..60c08bd092 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", "@loadable/component": "^5.16.3", + "@marsidev/react-turnstile": "^0.7.2", "@matt-block/react-recaptcha-v2": "^2.0.1", "@microsoft/signalr": "^8.0.0", "@mui/icons-material": "^5.15.7", @@ -4330,6 +4331,15 @@ "node": ">=8" } }, + "node_modules/@marsidev/react-turnstile": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@marsidev/react-turnstile/-/react-turnstile-0.7.2.tgz", + "integrity": "sha512-0jwLvAUkcLkaYaS6jBOZB3zzUiKi5dU3kZtlaeBX6yV7Y4CbFEtfHCY352ovphNz1v0ZjpOj6+3QUczJvD56VA==", + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, "node_modules/@matt-block/react-recaptcha-v2": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@matt-block/react-recaptcha-v2/-/react-recaptcha-v2-2.0.1.tgz", diff --git a/package.json b/package.json index 453c8d0ae5..51d2d86835 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", "@loadable/component": "^5.16.3", + "@marsidev/react-turnstile": "^0.7.2", "@matt-block/react-recaptcha-v2": "^2.0.1", "@microsoft/signalr": "^8.0.0", "@mui/icons-material": "^5.15.7", diff --git a/src/api/api/user-api.ts b/src/api/api/user-api.ts index 611cb22647..cf68b089c2 100644 --- a/src/api/api/user-api.ts +++ b/src/api/api/user-api.ts @@ -601,6 +601,51 @@ export const UserApiAxiosParamCreator = function ( ...options.headers, }; + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @param {string} token + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + validateTurnstile: async ( + token: string, + options: any = {} + ): Promise => { + // verify required parameter 'token' is not null or undefined + assertParamExists("validateTurnstile", "token", token); + const localVarPath = `/v1/users/turnstile/{token}`.replace( + `{${"token"}}`, + encodeURIComponent(String(token)) + ); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { + method: "GET", + ...baseOptions, + ...options, + }; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); + let headersFromBaseOptions = + baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = { + ...localVarHeaderParameter, + ...headersFromBaseOptions, + ...options.headers, + }; + return { url: toPathString(localVarUrlObj), options: localVarRequestOptions, @@ -884,6 +929,27 @@ export const UserApiFp = function (configuration?: Configuration) { configuration ); }, + /** + * + * @param {string} token + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async validateTurnstile( + token: string, + options?: any + ): Promise< + (axios?: AxiosInstance, basePath?: string) => AxiosPromise + > { + const localVarAxiosArgs = + await localVarAxiosParamCreator.validateTurnstile(token, options); + return createRequestFunction( + localVarAxiosArgs, + globalAxios, + BASE_PATH, + configuration + ); + }, }; }; @@ -1039,6 +1105,17 @@ export const UserApiFactory = function ( .validateResetToken(token, options) .then((request) => request(axios, basePath)); }, + /** + * + * @param {string} token + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + validateTurnstile(token: string, options?: any): AxiosPromise { + return localVarFp + .validateTurnstile(token, options) + .then((request) => request(axios, basePath)); + }, }; }; @@ -1189,6 +1266,20 @@ export interface UserApiValidateResetTokenRequest { readonly token: string; } +/** + * Request parameters for validateTurnstile operation in UserApi. + * @export + * @interface UserApiValidateTurnstileRequest + */ +export interface UserApiValidateTurnstileRequest { + /** + * + * @type {string} + * @memberof UserApiValidateTurnstile + */ + readonly token: string; +} + /** * UserApi - object-oriented interface * @export @@ -1376,4 +1467,20 @@ export class UserApi extends BaseAPI { .validateResetToken(requestParameters.token, options) .then((request) => request(this.axios, this.basePath)); } + + /** + * + * @param {UserApiValidateTurnstileRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof UserApi + */ + public validateTurnstile( + requestParameters: UserApiValidateTurnstileRequest, + options?: any + ) { + return UserApiFp(this.configuration) + .validateTurnstile(requestParameters.token, options) + .then((request) => request(this.axios, this.basePath)); + } } diff --git a/src/backend/index.ts b/src/backend/index.ts index ce63fe05fc..83cc9f7bf6 100644 --- a/src/backend/index.ts +++ b/src/backend/index.ts @@ -45,6 +45,7 @@ const config = new Api.Configuration(config_parameters); * and the blanket error pop ups should be suppressed.*/ const whiteListedErrorUrls = [ "users/authenticate", + "users/turnstile", "/speakers/create/", "/speakers/update/", ]; @@ -611,6 +612,13 @@ export async function getProgressEstimationLineChartRoot( /* UserController.cs */ +export async function validateTurnstile(token: string): Promise { + return await userApi + .validateTurnstile({ token }) + .then(() => true) + .catch(() => false); +} + export async function resetPasswordRequest( emailOrUsername: string ): Promise { diff --git a/src/components/Login/Login.tsx b/src/components/Login/Login.tsx index a185fa7393..b9f1ea36c2 100644 --- a/src/components/Login/Login.tsx +++ b/src/components/Login/Login.tsx @@ -21,9 +21,9 @@ import { useTranslation } from "react-i18next"; import { BannerType } from "api/models"; import { getBannerText } from "backend"; import { LoadingButton } from "components/Buttons"; -import Captcha from "components/Login/Captcha"; import { asyncLogIn } from "components/Login/Redux/LoginActions"; import { LoginStatus } from "components/Login/Redux/LoginReduxTypes"; +import Turnstile from "components/Login/Turnstile"; import { reset } from "rootRedux/actions"; import { useAppDispatch, useAppSelector } from "rootRedux/hooks"; import { type StoreState } from "rootRedux/types"; @@ -153,10 +153,7 @@ export default function Login(): ReactElement { )} - setIsVerified(false)} - onSuccess={() => setIsVerified(true)} - /> + {/* User Guide, Sign Up, and Log In buttons */} diff --git a/src/components/Login/Signup.tsx b/src/components/Login/Signup.tsx index fb8bd44333..ec38e96aa3 100644 --- a/src/components/Login/Signup.tsx +++ b/src/components/Login/Signup.tsx @@ -17,9 +17,9 @@ import { import { useTranslation } from "react-i18next"; import { LoadingDoneButton } from "components/Buttons"; -import Captcha from "components/Login/Captcha"; import { asyncSignUp } from "components/Login/Redux/LoginActions"; import { LoginStatus } from "components/Login/Redux/LoginReduxTypes"; +import Turnstile from "components/Login/Turnstile"; import { reset } from "rootRedux/actions"; import { useAppDispatch, useAppSelector } from "rootRedux/hooks"; import { type StoreState } from "rootRedux/types"; @@ -257,10 +257,7 @@ export default function Signup(props: SignupProps): ReactElement { )} - setIsVerified(false)} - onSuccess={() => setIsVerified(true)} - /> + {/* Sign Up and Log In buttons */} diff --git a/src/components/Login/Turnstile.tsx b/src/components/Login/Turnstile.tsx new file mode 100644 index 0000000000..b9f4c422e3 --- /dev/null +++ b/src/components/Login/Turnstile.tsx @@ -0,0 +1,33 @@ +import { Turnstile as MarsiTurnstile } from "@marsidev/react-turnstile"; +import { Fragment, type ReactElement } from "react"; + +import { validateTurnstile } from "backend"; +import { RuntimeConfig } from "types/runtimeConfig"; + +interface TurnstileProps { + setSuccess: (success: boolean) => void; +} + +export default function Turnstile(props: TurnstileProps): ReactElement { + const siteKey = + process.env.NODE_ENV === "development" + ? //"1x00000000000000000000AA" // visible pass + //"2x00000000000000000000AB" // visible fail + //"1x00000000000000000000BB" // invisible pass + //"2x00000000000000000000BB" // invisible fail + "3x00000000000000000000FF" // force interactive challenge + : "0x4AAAAAAAe9zmM2ysXGSJk1"; // the true site key for deployment + return RuntimeConfig.getInstance().captchaRequired() ? ( + props.setSuccess(false)} + onExpire={() => props.setSuccess(false)} + onSuccess={(token: string) => { + validateTurnstile(token).then(props.setSuccess); + }} + options={{ theme: "light" }} + siteKey={siteKey} + /> + ) : ( + + ); +} diff --git a/src/components/PasswordReset/Request.tsx b/src/components/PasswordReset/Request.tsx index ed571db5cc..4f6515d0a4 100644 --- a/src/components/PasswordReset/Request.tsx +++ b/src/components/PasswordReset/Request.tsx @@ -5,7 +5,9 @@ import { useNavigate } from "react-router-dom"; import { resetPasswordRequest } from "backend"; import { LoadingDoneButton } from "components/Buttons"; +import Turnstile from "components/Login/Turnstile"; import { Path } from "types/path"; +import { RuntimeConfig } from "types/runtimeConfig"; export enum PasswordRequestIds { ButtonLogin = "password-request-login", @@ -18,6 +20,9 @@ export default function ResetRequest(): ReactElement { const [isDone, setIsDone] = useState(false); const [isError, setIsError] = useState(false); const [isLoading, setIsLoading] = useState(false); + const [isVerified, setIsVerified] = useState( + !RuntimeConfig.getInstance().captchaRequired() + ); const { t } = useTranslation(); const navigate = useNavigate(); @@ -80,6 +85,9 @@ export default function ResetRequest(): ReactElement { variant="outlined" /> + + + onSubmit, variant: "contained", }} - disabled={!emailOrUsername} + disabled={!emailOrUsername || !isVerified} loading={isLoading} > {t("passwordReset.submit")} From 969ccc9d909b89098e334511756252cd1d5ab20a Mon Sep 17 00:00:00 2001 From: Danny Rorabaugh Date: Tue, 16 Jul 2024 14:38:09 -0400 Subject: [PATCH 02/24] Complete CAPTCHA replacement --- .vscode/settings.json | 1 - Backend/Controllers/UserController.cs | 11 +++-- .../assets/licenses/frontend_licenses.txt | 47 +------------------ package-lock.json | 13 +---- package.json | 1 - public/locales/en/translation.json | 3 ++ src/components/Login/Captcha.tsx | 28 ----------- src/components/Login/Turnstile.tsx | 31 +++++++++--- src/components/Login/tests/Login.test.tsx | 8 +--- src/components/Login/tests/MockTurnstile.tsx | 11 +++++ src/components/Login/tests/Signup.test.tsx | 8 +--- .../PasswordReset/tests/Request.test.tsx | 2 + 12 files changed, 51 insertions(+), 113 deletions(-) delete mode 100644 src/components/Login/Captcha.tsx create mode 100644 src/components/Login/tests/MockTurnstile.tsx diff --git a/.vscode/settings.json b/.vscode/settings.json index 73188d834c..885cd7e751 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -73,7 +73,6 @@ "piptools", "Prenoun", "Preverb", - "recaptcha", "reportgenerator", "sched", "signup", diff --git a/Backend/Controllers/UserController.cs b/Backend/Controllers/UserController.cs index d532a740d5..32c69a39e0 100644 --- a/Backend/Controllers/UserController.cs +++ b/Backend/Controllers/UserController.cs @@ -42,11 +42,12 @@ public UserController(IUserRepository userRepo, IPermissionService permissionSer public async Task ValidateTurnstile(string token) { var secret = Environment.GetEnvironmentVariable("TURNSTILE_SECRET_KEY"); - var httpContent = new FormUrlEncodedContent(new KeyValuePair[]{ - new ("response", token), - //new ("secret", secret ?? "1x0000000000000000000000000000000AA"), // pass - new ("secret", secret ?? "2x0000000000000000000000000000000AA"), // fail - //new ("secret", secret ?? "3x0000000000000000000000000000000AA"), // token spent + var httpContent = new FormUrlEncodedContent(new Dictionary{ + {"response", token}, + // https://developers.cloudflare.com/turnstile/troubleshooting/testing/ + {"secret", secret ?? "1x0000000000000000000000000000000AA"}, // pass + //{"secret", secret ?? "2x0000000000000000000000000000000AA"}, // fail + //{"secret", secret ?? "3x0000000000000000000000000000000AA"}, // token spent }); using var result = await new HttpClient().PostAsync(TurnstileVerifyUrl, httpContent); var contentString = await result.Content.ReadAsStringAsync(); diff --git a/docs/user_guide/assets/licenses/frontend_licenses.txt b/docs/user_guide/assets/licenses/frontend_licenses.txt index 34ebc6a2ab..55537ee183 100644 --- a/docs/user_guide/assets/licenses/frontend_licenses.txt +++ b/docs/user_guide/assets/licenses/frontend_licenses.txt @@ -972,29 +972,8 @@ The above copyright notice and this permission notice shall be included in all c THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -@matt-block/react-recaptcha-v2 2.0.1 +@marsidev/react-turnstile 0.7.2 MIT -MIT License - -Copyright (c) 2018-present Matei Bogdan Radu - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. @microsoft/signalr 8.0.0 @@ -43055,30 +43034,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -nanoid 3.3.7 -MIT -The MIT License (MIT) - -Copyright 2017 Andrey Sitnik - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - new-date 1.0.3 null Copyright (c) 2016 Segment.io, Inc. diff --git a/package-lock.json b/package-lock.json index 60c08bd092..f0f734f1ec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,6 @@ "@emotion/styled": "^11.11.0", "@loadable/component": "^5.16.3", "@marsidev/react-turnstile": "^0.7.2", - "@matt-block/react-recaptcha-v2": "^2.0.1", "@microsoft/signalr": "^8.0.0", "@mui/icons-material": "^5.15.7", "@mui/material": "^5.14.16", @@ -4340,17 +4339,6 @@ "react-dom": ">=16.8.0" } }, - "node_modules/@matt-block/react-recaptcha-v2": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@matt-block/react-recaptcha-v2/-/react-recaptcha-v2-2.0.1.tgz", - "integrity": "sha512-nQ1DjdjmfeG5dcKwqprfgBMdBO1MYlFcB4LtfMDsw8kmuxVuRsiVlAHsmARirmGutJ9zKQpvcYZqy2HbIoAH5w==", - "dependencies": { - "nanoid": "^3.3.4" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, "node_modules/@microsoft/signalr": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/@microsoft/signalr/-/signalr-8.0.0.tgz", @@ -22045,6 +22033,7 @@ "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, "funding": [ { "type": "github", diff --git a/package.json b/package.json index 51d2d86835..3fde36ede4 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,6 @@ "@emotion/styled": "^11.11.0", "@loadable/component": "^5.16.3", "@marsidev/react-turnstile": "^0.7.2", - "@matt-block/react-recaptcha-v2": "^2.0.1", "@microsoft/signalr": "^8.0.0", "@mui/icons-material": "^5.15.7", "@mui/material": "^5.14.16", diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 69b9b69a0f..b3324bf110 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -75,6 +75,9 @@ "resetDone": "If you have correctly entered your email or username, a password reset link has been sent to your email address.", "backToLogin": "Back To Login" }, + "turnstile": { + "error": "Cloudflare Turnstile verification failed or expired. Please refresh the page to try again." + }, "speakerMenu": { "none": "No speakers in the project. To attach a speaker to audio recordings, please talk to a project administrator.", "other": "[None of the above]" diff --git a/src/components/Login/Captcha.tsx b/src/components/Login/Captcha.tsx deleted file mode 100644 index d50912cf72..0000000000 --- a/src/components/Login/Captcha.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import ReCaptcha from "@matt-block/react-recaptcha-v2"; -import { Fragment, ReactElement } from "react"; - -import { RuntimeConfig } from "types/runtimeConfig"; - -interface CaptchaProps { - onExpire: () => void; - onSuccess: () => void; -} - -export default function Captcha(props: CaptchaProps): ReactElement { - return RuntimeConfig.getInstance().captchaRequired() ? ( -
- - console.error("Something went wrong; check your connection.") - } - onExpire={props.onExpire} - onSuccess={props.onSuccess} - siteKey={RuntimeConfig.getInstance().captchaSiteKey()} - size="normal" - theme="light" - /> -
- ) : ( - - ); -} diff --git a/src/components/Login/Turnstile.tsx b/src/components/Login/Turnstile.tsx index b9f4c422e3..9067c55506 100644 --- a/src/components/Login/Turnstile.tsx +++ b/src/components/Login/Turnstile.tsx @@ -1,30 +1,49 @@ import { Turnstile as MarsiTurnstile } from "@marsidev/react-turnstile"; import { Fragment, type ReactElement } from "react"; +import { useTranslation } from "react-i18next"; +import { toast } from "react-toastify"; import { validateTurnstile } from "backend"; +import i18n from "i18n"; import { RuntimeConfig } from "types/runtimeConfig"; -interface TurnstileProps { +export interface TurnstileProps { + /** Parent function to call when Turnstile succeeds or fails. */ setSuccess: (success: boolean) => void; } +/** Component wrapper for Cloudflare Turnstile (CAPTCHA replacement). */ export default function Turnstile(props: TurnstileProps): ReactElement { + const { t } = useTranslation(); + const siteKey = process.env.NODE_ENV === "development" - ? //"1x00000000000000000000AA" // visible pass + ? //https://developers.cloudflare.com/turnstile/troubleshooting/testing/ + //"1x00000000000000000000AA" // visible pass //"2x00000000000000000000AB" // visible fail //"1x00000000000000000000BB" // invisible pass //"2x00000000000000000000BB" // invisible fail "3x00000000000000000000FF" // force interactive challenge : "0x4AAAAAAAe9zmM2ysXGSJk1"; // the true site key for deployment + + const fail = (): void => { + props.setSuccess(false); + toast.error(t("turnstile.error")); + }; + const succeed = (): void => { + props.setSuccess(true); + }; + return RuntimeConfig.getInstance().captchaRequired() ? ( props.setSuccess(false)} - onExpire={() => props.setSuccess(false)} + onError={() => fail()} + onExpire={() => fail()} onSuccess={(token: string) => { - validateTurnstile(token).then(props.setSuccess); + validateTurnstile(token).then((success) => + success ? succeed() : fail() + ); }} - options={{ theme: "light" }} + options={{ language: i18n.resolvedLanguage, theme: "light" }} siteKey={siteKey} /> ) : ( diff --git a/src/components/Login/tests/Login.test.tsx b/src/components/Login/tests/Login.test.tsx index 8f14957ee0..1ea5eff03e 100644 --- a/src/components/Login/tests/Login.test.tsx +++ b/src/components/Login/tests/Login.test.tsx @@ -10,19 +10,13 @@ import configureMockStore from "redux-mock-store"; import Login, { LoginId } from "components/Login/Login"; import { defaultState as loginState } from "components/Login/Redux/LoginReduxTypes"; -jest.mock( - "@matt-block/react-recaptcha-v2", - () => - function MockRecaptcha() { - return
Recaptcha
; - } -); jest.mock("backend", () => ({ getBannerText: () => Promise.resolve(""), })); jest.mock("components/Login/Redux/LoginActions", () => ({ asyncLogIn: (...args: any[]) => mockAsyncLogIn(...args), })); +jest.mock("components/Login/Turnstile", () => "div"); jest.mock("router/browserRouter"); jest.mock("rootRedux/hooks", () => { return { diff --git a/src/components/Login/tests/MockTurnstile.tsx b/src/components/Login/tests/MockTurnstile.tsx new file mode 100644 index 0000000000..68777dbbda --- /dev/null +++ b/src/components/Login/tests/MockTurnstile.tsx @@ -0,0 +1,11 @@ +import { type ReactElement, useEffect } from "react"; + +import { type TurnstileProps } from "components/Login/Turnstile"; + +/** Mock Turnstile component that automatically succeeds. */ +export default function MockTurnstile(props: TurnstileProps): ReactElement { + useEffect(() => { + props.setSuccess(true); + }, [props]); + return
; +} diff --git a/src/components/Login/tests/Signup.test.tsx b/src/components/Login/tests/Signup.test.tsx index 15aa9ec0b6..433eab529d 100644 --- a/src/components/Login/tests/Signup.test.tsx +++ b/src/components/Login/tests/Signup.test.tsx @@ -11,19 +11,13 @@ import configureMockStore from "redux-mock-store"; import { defaultState as loginState } from "components/Login/Redux/LoginReduxTypes"; import Signup, { SignupId } from "components/Login/Signup"; -jest.mock( - "@matt-block/react-recaptcha-v2", - () => - function MockRecaptcha() { - return
Recaptcha
; - } -); jest.mock("backend", () => ({ getBannerText: () => Promise.resolve(""), })); jest.mock("components/Login/Redux/LoginActions", () => ({ asyncSignUp: (...args: any[]) => mockAsyncSignUp(...args), })); +jest.mock("components/Login/Turnstile", () => "div"); jest.mock("router/browserRouter"); jest.mock("rootRedux/hooks", () => { return { diff --git a/src/components/PasswordReset/tests/Request.test.tsx b/src/components/PasswordReset/tests/Request.test.tsx index 693ac4e883..a18e316c91 100644 --- a/src/components/PasswordReset/tests/Request.test.tsx +++ b/src/components/PasswordReset/tests/Request.test.tsx @@ -4,6 +4,7 @@ import userEvent from "@testing-library/user-event"; import { act } from "react"; import MockBypassLoadableButton from "components/Buttons/LoadingDoneButton"; +import MockTurnstile from "components/Login/tests/MockTurnstile"; import ResetRequest, { PasswordRequestIds, } from "components/PasswordReset/Request"; @@ -18,6 +19,7 @@ jest.mock("backend", () => ({ jest.mock("components/Buttons", () => ({ LoadingDoneButton: MockBypassLoadableButton, })); +jest.mock("components/Login/Turnstile", () => MockTurnstile); const mockResetPasswordRequest = jest.fn(); From 80ba19684dc9939b39cbd3f368dc62493c40bcbc Mon Sep 17 00:00:00 2001 From: Danny Rorabaugh Date: Tue, 16 Jul 2024 14:52:36 -0400 Subject: [PATCH 03/24] Setup secret key --- .github/workflows/deploy_qa.yml | 2 +- .../thecombine/charts/backend/templates/backend-secrets.yaml | 1 + .../charts/backend/templates/deployment-backend.yaml | 5 +++++ deploy/helm/thecombine/charts/backend/values.yaml | 1 + deploy/helm/thecombine/values.yaml | 1 + deploy/scripts/install-combine.sh | 2 ++ deploy/scripts/setup_files/combine_config.yaml | 2 ++ docs/deploy/README.md | 1 + 8 files changed, 14 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy_qa.yml b/.github/workflows/deploy_qa.yml index f3102f9374..4cc7058ed2 100644 --- a/.github/workflows/deploy_qa.yml +++ b/.github/workflows/deploy_qa.yml @@ -4,7 +4,7 @@ on: push: branches: - master - - turnstile + #- turnstile permissions: contents: read diff --git a/deploy/helm/thecombine/charts/backend/templates/backend-secrets.yaml b/deploy/helm/thecombine/charts/backend/templates/backend-secrets.yaml index 60a8eb8f2e..4761a56e17 100644 --- a/deploy/helm/thecombine/charts/backend/templates/backend-secrets.yaml +++ b/deploy/helm/thecombine/charts/backend/templates/backend-secrets.yaml @@ -9,3 +9,4 @@ data: COMBINE_JWT_SECRET_KEY: {{ .Values.global.combineJwtSecretKey | b64enc }} COMBINE_SMTP_USERNAME: {{ .Values.global.combineSmtpUsername | b64enc }} COMBINE_SMTP_PASSWORD: {{ .Values.global.combineSmtpPassword | b64enc }} + COMBINE_TURNSTILE_SECRET_KEY: {{ .Values.global.combineTurnstileSecretKey | b64enc }} diff --git a/deploy/helm/thecombine/charts/backend/templates/deployment-backend.yaml b/deploy/helm/thecombine/charts/backend/templates/deployment-backend.yaml index 2d4043a1e9..c9a9baa3f7 100644 --- a/deploy/helm/thecombine/charts/backend/templates/deployment-backend.yaml +++ b/deploy/helm/thecombine/charts/backend/templates/deployment-backend.yaml @@ -69,6 +69,11 @@ spec: secretKeyRef: key: COMBINE_SMTP_USERNAME name: env-backend-secrets + - name: COMBINE_TURNSTILE_SECRET_KEY + valueFrom: + secretKeyRef: + key: COMBINE_TURNSTILE_SECRET_KEY + name: env-backend-secrets ports: - containerPort: 5000 resources: diff --git a/deploy/helm/thecombine/charts/backend/values.yaml b/deploy/helm/thecombine/charts/backend/values.yaml index 3bc97872dc..1e35e38e32 100644 --- a/deploy/helm/thecombine/charts/backend/values.yaml +++ b/deploy/helm/thecombine/charts/backend/values.yaml @@ -21,6 +21,7 @@ global: combineJwtSecretKey: "Override" combineSmtpUsername: "Override" combineSmtpPassword: "Override" + combineTurnstileSecretKey: "Override" # Values for pulling container image from image registry imagePullPolicy: "Override" imageTag: "latest" diff --git a/deploy/helm/thecombine/values.yaml b/deploy/helm/thecombine/values.yaml index 49a47078a1..528b3579bf 100644 --- a/deploy/helm/thecombine/values.yaml +++ b/deploy/helm/thecombine/values.yaml @@ -26,6 +26,7 @@ global: combineJwtSecretKey: "Override" combineSmtpUsername: "Override" combineSmtpPassword: "Override" + combineTurnstileSecretKey: "Override" offline: false # Local Storage for fonts fontStorageAccessMode: "ReadWriteOnce" diff --git a/deploy/scripts/install-combine.sh b/deploy/scripts/install-combine.sh index b32cb18d66..e49b1c27f4 100755 --- a/deploy/scripts/install-combine.sh +++ b/deploy/scripts/install-combine.sh @@ -9,11 +9,13 @@ set-combine-env () { # Generate JWT Secret Key COMBINE_JWT_SECRET_KEY=`LC_ALL=C tr -dc 'A-Za-z0-9*\-_@!' ${CONFIG_DIR}/env export COMBINE_JWT_SECRET_KEY="${COMBINE_JWT_SECRET_KEY}" + export COMBINE_TURNSTILE_SECRET_KEY="${COMBINE_TURNSTILE_SECRET_KEY}" export AWS_DEFAULT_REGION="us-east-1" export AWS_ACCESS_KEY_ID="${AWS_ACCESS_KEY_ID}" export AWS_SECRET_ACCESS_KEY="${AWS_SECRET_ACCESS_KEY}" diff --git a/deploy/scripts/setup_files/combine_config.yaml b/deploy/scripts/setup_files/combine_config.yaml index 3ce315b835..70f3c847c7 100644 --- a/deploy/scripts/setup_files/combine_config.yaml +++ b/deploy/scripts/setup_files/combine_config.yaml @@ -156,6 +156,8 @@ charts: env_var: COMBINE_SMTP_USERNAME - config_item: combineSmtpPassword env_var: COMBINE_SMTP_PASSWORD + - config_item: combineTurnstileSecretKey + env_var: COMBINE_TURNSTILE_SECRET_KEY create-admin-user: namespace: thecombine install_langs: false diff --git a/docs/deploy/README.md b/docs/deploy/README.md index b9ee813630..a3ffae9164 100644 --- a/docs/deploy/README.md +++ b/docs/deploy/README.md @@ -288,6 +288,7 @@ The setup scripts require the following environment variables to be set: - COMBINE_JWT_SECRET_KEY - COMBINE_SMTP_USERNAME - COMBINE_SMTP_PASSWORD +- COMBINE_TURNSTILE_SECRET_KEY - COMBINE_ADMIN_USERNAME - COMBINE_ADMIN_PASSWORD - COMBINE_ADMIN_EMAIL From 3dc89a2bf14ca43a4e2e634d07c55175fcfa81e4 Mon Sep 17 00:00:00 2001 From: Danny Rorabaugh Date: Tue, 16 Jul 2024 15:24:53 -0400 Subject: [PATCH 04/24] Fix App tests --- src/components/App/App.test.tsx | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/components/App/App.test.tsx b/src/components/App/App.test.tsx index b584a1bdb5..a41a6a62e4 100644 --- a/src/components/App/App.test.tsx +++ b/src/components/App/App.test.tsx @@ -7,13 +7,6 @@ import thunk from "redux-thunk"; import { defaultState } from "components/App/DefaultState"; import App from "components/App/component"; -jest.mock( - "@matt-block/react-recaptcha-v2", - () => - function MockRecaptcha() { - return
Recaptcha
; - } -); jest.mock("components/AnnouncementBanner/AnnouncementBanner", () => "div"); const createMockStore = configureMockStore([thunk]); From b9f029ab53c7803534bf70ec7a514b5f4729b6c5 Mon Sep 17 00:00:00 2001 From: Danny Rorabaugh Date: Tue, 16 Jul 2024 15:36:59 -0400 Subject: [PATCH 05/24] Fix failing tests --- src/setupTests.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/setupTests.js b/src/setupTests.js index 4c546243b2..1f26acbb6b 100644 --- a/src/setupTests.js +++ b/src/setupTests.js @@ -13,3 +13,6 @@ jest .spyOn(window.HTMLMediaElement.prototype, "pause") .mockImplementation(() => {}); jest.mock("components/Pronunciations/Recorder"); + +// Mock the browser router to short circuit a circular dependency +jest.mock("router/browserRouter", () => ({ navigate: jest.fn() })); From 2674e73b7fbf9ef13574c293d607c883616139aa Mon Sep 17 00:00:00 2001 From: Danny Rorabaugh Date: Tue, 16 Jul 2024 16:07:46 -0400 Subject: [PATCH 06/24] Fix App tests --- src/components/App/App.test.tsx | 2 ++ src/components/Login/tests/Login.test.tsx | 1 - src/components/Login/tests/Signup.test.tsx | 1 - .../Redux/tests/CharacterInventoryActions.test.tsx | 1 - src/setupTests.js | 4 ++-- 5 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/components/App/App.test.tsx b/src/components/App/App.test.tsx index a41a6a62e4..52b24c6cca 100644 --- a/src/components/App/App.test.tsx +++ b/src/components/App/App.test.tsx @@ -7,6 +7,8 @@ import thunk from "redux-thunk"; import { defaultState } from "components/App/DefaultState"; import App from "components/App/component"; +jest.mock("react-router-dom"); + jest.mock("components/AnnouncementBanner/AnnouncementBanner", () => "div"); const createMockStore = configureMockStore([thunk]); diff --git a/src/components/Login/tests/Login.test.tsx b/src/components/Login/tests/Login.test.tsx index 1ea5eff03e..e47334557b 100644 --- a/src/components/Login/tests/Login.test.tsx +++ b/src/components/Login/tests/Login.test.tsx @@ -17,7 +17,6 @@ jest.mock("components/Login/Redux/LoginActions", () => ({ asyncLogIn: (...args: any[]) => mockAsyncLogIn(...args), })); jest.mock("components/Login/Turnstile", () => "div"); -jest.mock("router/browserRouter"); jest.mock("rootRedux/hooks", () => { return { ...jest.requireActual("rootRedux/hooks"), diff --git a/src/components/Login/tests/Signup.test.tsx b/src/components/Login/tests/Signup.test.tsx index 433eab529d..9485db4802 100644 --- a/src/components/Login/tests/Signup.test.tsx +++ b/src/components/Login/tests/Signup.test.tsx @@ -18,7 +18,6 @@ jest.mock("components/Login/Redux/LoginActions", () => ({ asyncSignUp: (...args: any[]) => mockAsyncSignUp(...args), })); jest.mock("components/Login/Turnstile", () => "div"); -jest.mock("router/browserRouter"); jest.mock("rootRedux/hooks", () => { return { ...jest.requireActual("rootRedux/hooks"), diff --git a/src/goals/CharacterInventory/Redux/tests/CharacterInventoryActions.test.tsx b/src/goals/CharacterInventory/Redux/tests/CharacterInventoryActions.test.tsx index e2e323e03c..9e59de3ea9 100644 --- a/src/goals/CharacterInventory/Redux/tests/CharacterInventoryActions.test.tsx +++ b/src/goals/CharacterInventory/Redux/tests/CharacterInventoryActions.test.tsx @@ -39,7 +39,6 @@ jest.mock("goals/Redux/GoalActions", () => ({ addCharInvChangesToGoal: (changes: CharInvChanges) => mockAddCharInvChangesToGoal(changes), })); -jest.mock("router/browserRouter"); const mockAddCharInvChangesToGoal = jest.fn(); const mockAsyncUpdateCurrentProject = jest.fn(); diff --git a/src/setupTests.js b/src/setupTests.js index 1f26acbb6b..4acd208210 100644 --- a/src/setupTests.js +++ b/src/setupTests.js @@ -1,6 +1,6 @@ import "tests/reactI18nextMock"; -// Force tests to fail on console.error and console.warn. +// Force tests to fail on console.error and console.warn global.console.error = (message) => { throw message; }; @@ -14,5 +14,5 @@ jest .mockImplementation(() => {}); jest.mock("components/Pronunciations/Recorder"); -// Mock the browser router to short circuit a circular dependency +// Mock the router to short circuit a circular dependency jest.mock("router/browserRouter", () => ({ navigate: jest.fn() })); From d805fb45c91e754a1aaf42f6f27f9e47860ea8d2 Mon Sep 17 00:00:00 2001 From: Danny Rorabaugh Date: Wed, 17 Jul 2024 09:03:01 -0400 Subject: [PATCH 07/24] Tidy Turnstile component --- Backend/Startup.cs | 3 +++ src/components/Login/Turnstile.tsx | 29 ++++++++++++++--------------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/Backend/Startup.cs b/Backend/Startup.cs index 84a0fc2b08..0141d5a836 100644 --- a/Backend/Startup.cs +++ b/Backend/Startup.cs @@ -178,6 +178,9 @@ public void ConfigureServices(IServiceCollection services) "COMBINE_SMTP_FROM", null, emailServiceFailureMessage); + + // Should we add a COMBINE_TURNSTILE_SECRET_KEY check? + options.PassResetExpireTime = int.Parse(CheckedEnvironmentVariable( "COMBINE_PASSWORD_RESET_EXPIRE_TIME", Settings.DefaultPasswordResetExpireTime.ToString(), diff --git a/src/components/Login/Turnstile.tsx b/src/components/Login/Turnstile.tsx index 9067c55506..7587f53556 100644 --- a/src/components/Login/Turnstile.tsx +++ b/src/components/Login/Turnstile.tsx @@ -17,14 +17,14 @@ export default function Turnstile(props: TurnstileProps): ReactElement { const { t } = useTranslation(); const siteKey = - process.env.NODE_ENV === "development" - ? //https://developers.cloudflare.com/turnstile/troubleshooting/testing/ - //"1x00000000000000000000AA" // visible pass - //"2x00000000000000000000AB" // visible fail - //"1x00000000000000000000BB" // invisible pass - //"2x00000000000000000000BB" // invisible fail - "3x00000000000000000000FF" // force interactive challenge - : "0x4AAAAAAAe9zmM2ysXGSJk1"; // the true site key for deployment + process.env.NODE_ENV === "production" + ? "0x4AAAAAAAe9zmM2ysXGSJk1" // the true site key for deployment + : // https://developers.cloudflare.com/turnstile/troubleshooting/testing/ + //"1x00000000000000000000AA"; // visible pass + //"2x00000000000000000000AB"; // visible fail + //"1x00000000000000000000BB"; // invisible pass + //"2x00000000000000000000BB"; // invisible fail + "3x00000000000000000000FF"; // force interactive challenge const fail = (): void => { props.setSuccess(false); @@ -33,16 +33,15 @@ export default function Turnstile(props: TurnstileProps): ReactElement { const succeed = (): void => { props.setSuccess(true); }; + const validate = (token: string): void => { + validateTurnstile(token).then((isValid) => (isValid ? succeed() : fail())); + }; return RuntimeConfig.getInstance().captchaRequired() ? ( fail()} - onExpire={() => fail()} - onSuccess={(token: string) => { - validateTurnstile(token).then((success) => - success ? succeed() : fail() - ); - }} + onError={fail} + onExpire={fail} + onSuccess={validate} options={{ language: i18n.resolvedLanguage, theme: "light" }} siteKey={siteKey} /> From 1db2d1a33cb12495e429119863077ae85a5cd790 Mon Sep 17 00:00:00 2001 From: Danny Rorabaugh Date: Wed, 17 Jul 2024 17:12:43 -0400 Subject: [PATCH 08/24] Add for Turnstile: environment variables, Context, Service --- .../Controllers/UserControllerTests.cs | 2 +- Backend.Tests/Mocks/TurnstileServiceMock.cs | 13 +++++++ Backend/Contexts/TurnstileContext.cs | 19 ++++++++++ Backend/Controllers/UserController.cs | 24 +++--------- Backend/Interfaces/ITurnstileContext.cs | 8 ++++ Backend/Interfaces/ITurnstileService.cs | 9 +++++ Backend/Properties/launchSettings.json | 10 ++++- Backend/Services/TurnstileService.cs | 38 +++++++++++++++++++ Backend/Startup.cs | 18 ++++++++- README.md | 14 +++---- .../backend/templates/backend-config-map.yaml | 1 + .../backend/templates/backend-secrets.yaml | 2 +- .../backend/templates/deployment-backend.yaml | 9 ++++- .../thecombine/charts/backend/values.yaml | 3 ++ deploy/helm/thecombine/values.yaml | 3 +- deploy/scripts/install-combine.sh | 2 - .../scripts/setup_files/combine_config.yaml | 4 +- docs/deploy/README.md | 2 +- public/locales/en/translation.json | 2 +- 19 files changed, 141 insertions(+), 42 deletions(-) create mode 100644 Backend.Tests/Mocks/TurnstileServiceMock.cs create mode 100644 Backend/Contexts/TurnstileContext.cs create mode 100644 Backend/Interfaces/ITurnstileContext.cs create mode 100644 Backend/Interfaces/ITurnstileService.cs create mode 100644 Backend/Services/TurnstileService.cs diff --git a/Backend.Tests/Controllers/UserControllerTests.cs b/Backend.Tests/Controllers/UserControllerTests.cs index 530325fe31..4f01e37484 100644 --- a/Backend.Tests/Controllers/UserControllerTests.cs +++ b/Backend.Tests/Controllers/UserControllerTests.cs @@ -35,7 +35,7 @@ public void Setup() _userRepo = new UserRepositoryMock(); _permissionService = new PermissionServiceMock(_userRepo); _userController = new UserController(_userRepo, _permissionService, - new EmailServiceMock(), new PasswordResetServiceMock()); + new EmailServiceMock(), new PasswordResetServiceMock(), new TurnstileServiceMock()); } private static User RandomUser() diff --git a/Backend.Tests/Mocks/TurnstileServiceMock.cs b/Backend.Tests/Mocks/TurnstileServiceMock.cs new file mode 100644 index 0000000000..76b8cb4c61 --- /dev/null +++ b/Backend.Tests/Mocks/TurnstileServiceMock.cs @@ -0,0 +1,13 @@ +using System.Threading.Tasks; +using BackendFramework.Interfaces; + +namespace Backend.Tests.Mocks +{ + sealed internal class TurnstileServiceMock : ITurnstileService + { + public Task VerifyToken(string token) + { + return Task.FromResult(true); + } + } +} diff --git a/Backend/Contexts/TurnstileContext.cs b/Backend/Contexts/TurnstileContext.cs new file mode 100644 index 0000000000..7705973120 --- /dev/null +++ b/Backend/Contexts/TurnstileContext.cs @@ -0,0 +1,19 @@ +using System.Diagnostics.CodeAnalysis; +using BackendFramework.Interfaces; +using Microsoft.Extensions.Options; + +namespace BackendFramework.Contexts +{ + [ExcludeFromCodeCoverage] + public class TurnstileContext : ITurnstileContext + { + public string? TurnstileSecretKey { get; } + public string? TurnstileVerifyUrl { get; } + + public TurnstileContext(IOptions options) + { + TurnstileSecretKey = options.Value.TurnstileSecretKey; + TurnstileVerifyUrl = options.Value.TurnstileVerifyUrl; + } + } +} diff --git a/Backend/Controllers/UserController.cs b/Backend/Controllers/UserController.cs index 32c69a39e0..64e6603fa7 100644 --- a/Backend/Controllers/UserController.cs +++ b/Backend/Controllers/UserController.cs @@ -1,7 +1,5 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -using System.Net.Http; using System.Threading.Tasks; using BackendFramework.Helper; using BackendFramework.Interfaces; @@ -23,16 +21,16 @@ public class UserController : Controller private readonly IEmailService _emailService; private readonly IPasswordResetService _passwordResetService; private readonly IPermissionService _permissionService; - - private const string TurnstileVerifyUrl = "https://challenges.cloudflare.com/turnstile/v0/siteverify"; + private readonly ITurnstileService _turnstileService; public UserController(IUserRepository userRepo, IPermissionService permissionService, - IEmailService emailService, IPasswordResetService passwordResetService) + IEmailService emailService, IPasswordResetService passwordResetService, ITurnstileService turnstileService) { _userRepo = userRepo; _emailService = emailService; _passwordResetService = passwordResetService; _permissionService = permissionService; + _turnstileService = turnstileService; } /// Validates a Cloudflare Turnstile token @@ -41,21 +39,9 @@ public UserController(IUserRepository userRepo, IPermissionService permissionSer [ProducesResponseType(StatusCodes.Status200OK)] public async Task ValidateTurnstile(string token) { - var secret = Environment.GetEnvironmentVariable("TURNSTILE_SECRET_KEY"); - var httpContent = new FormUrlEncodedContent(new Dictionary{ - {"response", token}, - // https://developers.cloudflare.com/turnstile/troubleshooting/testing/ - {"secret", secret ?? "1x0000000000000000000000000000000AA"}, // pass - //{"secret", secret ?? "2x0000000000000000000000000000000AA"}, // fail - //{"secret", secret ?? "3x0000000000000000000000000000000AA"}, // token spent - }); - using var result = await new HttpClient().PostAsync(TurnstileVerifyUrl, httpContent); - var contentString = await result.Content.ReadAsStringAsync(); - Console.WriteLine($"content: {contentString}"); - return contentString.Contains("\"success\":true") ? Ok() : BadRequest(); + return await _turnstileService.VerifyToken(token) ? Ok() : BadRequest(); } - /// Sends a password reset request [AllowAnonymous] [HttpPost("forgot", Name = "ResetPasswordRequest")] diff --git a/Backend/Interfaces/ITurnstileContext.cs b/Backend/Interfaces/ITurnstileContext.cs new file mode 100644 index 0000000000..d0e19e0b91 --- /dev/null +++ b/Backend/Interfaces/ITurnstileContext.cs @@ -0,0 +1,8 @@ +namespace BackendFramework.Interfaces +{ + public interface ITurnstileContext + { + string? TurnstileSecretKey { get; } + string? TurnstileVerifyUrl { get; } + } +} diff --git a/Backend/Interfaces/ITurnstileService.cs b/Backend/Interfaces/ITurnstileService.cs new file mode 100644 index 0000000000..5cf5ade54a --- /dev/null +++ b/Backend/Interfaces/ITurnstileService.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace BackendFramework.Interfaces +{ + public interface ITurnstileService + { + Task VerifyToken(string token); + } +} diff --git a/Backend/Properties/launchSettings.json b/Backend/Properties/launchSettings.json index 00e83c8bed..ff4248861d 100644 --- a/Backend/Properties/launchSettings.json +++ b/Backend/Properties/launchSettings.json @@ -14,7 +14,10 @@ "launchBrowser": true, "launchUrl": "v1", "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" + "ASPNETCORE_ENVIRONMENT": "Development", + "COMBINE_JWT_SECRET_KEY": "0123456789abcdefghijklmnopqrstuvwxyz", + "TURNSTILE_SECRET_KEY": "1x0000000000000000000000000000000AA", + "TURNSTILE_VERIFY_URL": "challenges.cloudflare.com/turnstile/v0/siteverify" } }, "BackendFramework": { @@ -23,7 +26,10 @@ "launchUrl": "v1/", "environmentVariables": { "Key": "Value", - "ASPNETCORE_ENVIRONMENT": "Development" + "ASPNETCORE_ENVIRONMENT": "Development", + "COMBINE_JWT_SECRET_KEY": "0123456789abcdefghijklmnopqrstuvwxyz", + "TURNSTILE_SECRET_KEY": "1x0000000000000000000000000000000AA", + "TURNSTILE_VERIFY_URL": "challenges.cloudflare.com/turnstile/v0/siteverify" }, "applicationUrl": "http://localhost:5000", "hotReloadProfile": "aspnetcore" diff --git a/Backend/Services/TurnstileService.cs b/Backend/Services/TurnstileService.cs new file mode 100644 index 0000000000..3d6f0306dd --- /dev/null +++ b/Backend/Services/TurnstileService.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Net.Http; +using System.Threading.Tasks; +using BackendFramework.Interfaces; + +namespace BackendFramework.Services +{ + [ExcludeFromCodeCoverage] + public class TurnstileService : ITurnstileService + { + private readonly ITurnstileContext _turnstileContext; + + public TurnstileService(ITurnstileContext turnstileContext) + { + _turnstileContext = turnstileContext; + } + + public async Task VerifyToken(string token) + { + var secret = _turnstileContext.TurnstileSecretKey; + var verifyUrl = _turnstileContext.TurnstileVerifyUrl; + if (string.IsNullOrEmpty(secret) || string.IsNullOrEmpty(verifyUrl)) + { + return false; + } + var httpContent = new FormUrlEncodedContent(new Dictionary{ + {"response", token}, + {"secret", secret}, + }); + using var result = await new HttpClient().PostAsync(verifyUrl, httpContent); + var contentString = await result.Content.ReadAsStringAsync(); + Console.WriteLine($"content: {contentString}"); + return contentString.Contains("\"success\":true"); + } + } +} diff --git a/Backend/Startup.cs b/Backend/Startup.cs index 0141d5a836..bd02452d00 100644 --- a/Backend/Startup.cs +++ b/Backend/Startup.cs @@ -48,6 +48,8 @@ public class Settings public string? SmtpAddress { get; set; } public string? SmtpFrom { get; set; } public int PassResetExpireTime { get; set; } + public string? TurnstileSecretKey { get; set; } + public string? TurnstileVerifyUrl { get; set; } public Settings() { @@ -179,12 +181,20 @@ public void ConfigureServices(IServiceCollection services) null, emailServiceFailureMessage); - // Should we add a COMBINE_TURNSTILE_SECRET_KEY check? - options.PassResetExpireTime = int.Parse(CheckedEnvironmentVariable( "COMBINE_PASSWORD_RESET_EXPIRE_TIME", Settings.DefaultPasswordResetExpireTime.ToString(), $"Using default value: {Settings.DefaultPasswordResetExpireTime}")!); + + const string turnstileFailureMessage = "Turnstile verification will not be available."; + options.TurnstileSecretKey = CheckedEnvironmentVariable( + "TURNSTILE_SECRET_KEY", + null, + turnstileFailureMessage); + options.TurnstileVerifyUrl = CheckedEnvironmentVariable( + "TURNSTILE_VERIFY_URL", + null, + turnstileFailureMessage); }); // Register concrete types for dependency injection @@ -231,6 +241,10 @@ public void ConfigureServices(IServiceCollection services) // Statistics types services.AddSingleton(); + // Turnstile types + services.AddTransient(); + services.AddTransient(); + // User types services.AddTransient(); services.AddTransient(); diff --git a/README.md b/README.md index 7aff0f3790..6c8e6d83d8 100644 --- a/README.md +++ b/README.md @@ -142,11 +142,9 @@ A rapid word collection tool. See the [User Guide](https://sillsdev.github.io/Th ### Prepare the Environment -1. Set the environment variable `COMBINE_JWT_SECRET_KEY` to a string **containing at least 32 characters**, such as - _This is a secret key that is longer_. Set it in your `.profile` (Linux or Mac 10.14-), your `.zprofile` (Mac - 10.15+), or the _System_ app (Windows). -2. If you want the email services to work you will need to set the following environment variables. These values must be - kept secret, so ask your email administrator to supply them. +1. If you want the email services to work you will need to set the following environment variables. These values must be + kept secret, so ask your email administrator to supply them. Set them in your `.profile` (Linux or Mac 10.14-), your + `.zprofile` (Mac 10.15+), or the _System_ app (Windows). - `COMBINE_SMTP_SERVER` - `COMBINE_SMTP_PORT` @@ -155,16 +153,16 @@ A rapid word collection tool. See the [User Guide](https://sillsdev.github.io/Th - `COMBINE_SMTP_ADDRESS` - `COMBINE_SMTP_FROM` -3. _(Optional)_ To opt in to segment.com analytics to test the analytics during development: +2. _(Optional)_ To opt in to segment.com analytics to test the analytics during development: ```bash # For Windows, use `copy`. cp .env.local.template .env.local ``` -4. Run `npm start` from the project directory to install dependencies and start the project. +3. Run `npm start` from the project directory to install dependencies and start the project. -5. Consult our [C#](docs/style_guide/c_sharp_style_guide.md) and [TypeScript](docs/style_guide/ts_style_guide.md) style +4. Consult our [C#](docs/style_guide/c_sharp_style_guide.md) and [TypeScript](docs/style_guide/ts_style_guide.md) style guides for best coding practices in this project. [chocolatey]: https://chocolatey.org/ diff --git a/deploy/helm/thecombine/charts/backend/templates/backend-config-map.yaml b/deploy/helm/thecombine/charts/backend/templates/backend-config-map.yaml index 1802815869..06888dc89e 100644 --- a/deploy/helm/thecombine/charts/backend/templates/backend-config-map.yaml +++ b/deploy/helm/thecombine/charts/backend/templates/backend-config-map.yaml @@ -11,3 +11,4 @@ data: COMBINE_SMTP_FROM: {{ .Values.combineSmtpFrom | quote }} COMBINE_SMTP_PORT: {{ .Values.combineSmtpPort | quote }} COMBINE_SMTP_SERVER: {{ .Values.combineSmtpServer | quote }} + TURNSTILE_VERIFY_URL: {{ .Values.turnstileVerifyUrl | quote }} diff --git a/deploy/helm/thecombine/charts/backend/templates/backend-secrets.yaml b/deploy/helm/thecombine/charts/backend/templates/backend-secrets.yaml index 4761a56e17..5a5686358b 100644 --- a/deploy/helm/thecombine/charts/backend/templates/backend-secrets.yaml +++ b/deploy/helm/thecombine/charts/backend/templates/backend-secrets.yaml @@ -9,4 +9,4 @@ data: COMBINE_JWT_SECRET_KEY: {{ .Values.global.combineJwtSecretKey | b64enc }} COMBINE_SMTP_USERNAME: {{ .Values.global.combineSmtpUsername | b64enc }} COMBINE_SMTP_PASSWORD: {{ .Values.global.combineSmtpPassword | b64enc }} - COMBINE_TURNSTILE_SECRET_KEY: {{ .Values.global.combineTurnstileSecretKey | b64enc }} + TURNSTILE_SECRET_KEY: {{ .Values.global.turnstileSecretKey | b64enc }} diff --git a/deploy/helm/thecombine/charts/backend/templates/deployment-backend.yaml b/deploy/helm/thecombine/charts/backend/templates/deployment-backend.yaml index c9a9baa3f7..d4ef7db409 100644 --- a/deploy/helm/thecombine/charts/backend/templates/deployment-backend.yaml +++ b/deploy/helm/thecombine/charts/backend/templates/deployment-backend.yaml @@ -69,11 +69,16 @@ spec: secretKeyRef: key: COMBINE_SMTP_USERNAME name: env-backend-secrets - - name: COMBINE_TURNSTILE_SECRET_KEY + - name: TURNSTILE_SECRET_KEY valueFrom: secretKeyRef: - key: COMBINE_TURNSTILE_SECRET_KEY + key: TURNSTILE_SECRET_KEY name: env-backend-secrets + - name: TURNSTILE_VERIFY_URL + valueFrom: + configMapKeyRef: + key: TURNSTILE_VERIFY_URL + name: env-backend ports: - containerPort: 5000 resources: diff --git a/deploy/helm/thecombine/charts/backend/values.yaml b/deploy/helm/thecombine/charts/backend/values.yaml index 1e35e38e32..78ef81e7d1 100644 --- a/deploy/helm/thecombine/charts/backend/values.yaml +++ b/deploy/helm/thecombine/charts/backend/values.yaml @@ -27,6 +27,8 @@ global: imageTag: "latest" # Define the image registry to use (may be blank for local images) imageRegistry: "" + # Cloudflare Turnstile verification + turnstileSecretKey: "Override" persistentVolumeSize: 32Gi combinePasswordResetTime: 15 @@ -35,3 +37,4 @@ combineSmtpFrom: "The Combine" combineSmtpPort: 587 combineSmtpServer: "email-smtp.us-east-1.amazonaws.com" imageName: combine_backend +turnstileVerifyUrl: "challenges.cloudflare.com/turnstile/v0/siteverify" diff --git a/deploy/helm/thecombine/values.yaml b/deploy/helm/thecombine/values.yaml index 528b3579bf..9c8ac81895 100644 --- a/deploy/helm/thecombine/values.yaml +++ b/deploy/helm/thecombine/values.yaml @@ -26,7 +26,6 @@ global: combineJwtSecretKey: "Override" combineSmtpUsername: "Override" combineSmtpPassword: "Override" - combineTurnstileSecretKey: "Override" offline: false # Local Storage for fonts fontStorageAccessMode: "ReadWriteOnce" @@ -39,6 +38,8 @@ global: pullSecretName: aws-login-credentials # Update strategy should be "Recreate" or "Rolling Update" updateStrategy: Recreate + # Cloudflare Turnstile verification + turnstileSecretKey: "Override" includeResourceLimits: false diff --git a/deploy/scripts/install-combine.sh b/deploy/scripts/install-combine.sh index e49b1c27f4..b32cb18d66 100755 --- a/deploy/scripts/install-combine.sh +++ b/deploy/scripts/install-combine.sh @@ -9,13 +9,11 @@ set-combine-env () { # Generate JWT Secret Key COMBINE_JWT_SECRET_KEY=`LC_ALL=C tr -dc 'A-Za-z0-9*\-_@!' ${CONFIG_DIR}/env export COMBINE_JWT_SECRET_KEY="${COMBINE_JWT_SECRET_KEY}" - export COMBINE_TURNSTILE_SECRET_KEY="${COMBINE_TURNSTILE_SECRET_KEY}" export AWS_DEFAULT_REGION="us-east-1" export AWS_ACCESS_KEY_ID="${AWS_ACCESS_KEY_ID}" export AWS_SECRET_ACCESS_KEY="${AWS_SECRET_ACCESS_KEY}" diff --git a/deploy/scripts/setup_files/combine_config.yaml b/deploy/scripts/setup_files/combine_config.yaml index 70f3c847c7..c610e3558a 100644 --- a/deploy/scripts/setup_files/combine_config.yaml +++ b/deploy/scripts/setup_files/combine_config.yaml @@ -156,8 +156,8 @@ charts: env_var: COMBINE_SMTP_USERNAME - config_item: combineSmtpPassword env_var: COMBINE_SMTP_PASSWORD - - config_item: combineTurnstileSecretKey - env_var: COMBINE_TURNSTILE_SECRET_KEY + - config_item: turnstileSecretKey + env_var: TURNSTILE_SECRET_KEY create-admin-user: namespace: thecombine install_langs: false diff --git a/docs/deploy/README.md b/docs/deploy/README.md index a3ffae9164..675548ad97 100644 --- a/docs/deploy/README.md +++ b/docs/deploy/README.md @@ -288,10 +288,10 @@ The setup scripts require the following environment variables to be set: - COMBINE_JWT_SECRET_KEY - COMBINE_SMTP_USERNAME - COMBINE_SMTP_PASSWORD -- COMBINE_TURNSTILE_SECRET_KEY - COMBINE_ADMIN_USERNAME - COMBINE_ADMIN_PASSWORD - COMBINE_ADMIN_EMAIL +- TURNSTILE_SECRET_KEY You may also set the KUBECONFIG environment variable to the location of the `kubectl` configuration file. This is not necessary if the configuration file is at `${HOME}/.kube/config`. diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index b3324bf110..c1755a9d5a 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -76,7 +76,7 @@ "backToLogin": "Back To Login" }, "turnstile": { - "error": "Cloudflare Turnstile verification failed or expired. Please refresh the page to try again." + "error": "Page verification failed or expired. Please refresh the page." }, "speakerMenu": { "none": "No speakers in the project. To attach a speaker to audio recordings, please talk to a project administrator.", From b2eab3ffd3ae11a7137843230da4c4c7ee85ec1b Mon Sep 17 00:00:00 2001 From: Danny Rorabaugh Date: Wed, 17 Jul 2024 17:23:13 -0400 Subject: [PATCH 09/24] Replace frontend CAPTCHA configs with Turnstile config --- .../templates/deployment-frontend.yaml | 9 ++------ .../templates/env-frontend-configmap.yaml | 3 +-- .../thecombine/charts/frontend/values.yaml | 3 +-- deploy/helm/thecombine/values.yaml | 3 +-- .../scripts/setup_files/combine_config.yaml | 6 ++---- deploy/scripts/setup_files/profiles/dev.yaml | 3 +-- nginx/init/25-combine-runtime-config.sh | 3 +-- src/components/Login/Login.tsx | 2 +- src/components/Login/Signup.tsx | 2 +- src/components/Login/Turnstile.tsx | 2 +- src/components/PasswordReset/Request.tsx | 2 +- src/types/runtimeConfig.ts | 21 ++++++------------- 12 files changed, 19 insertions(+), 40 deletions(-) diff --git a/deploy/helm/thecombine/charts/frontend/templates/deployment-frontend.yaml b/deploy/helm/thecombine/charts/frontend/templates/deployment-frontend.yaml index 1f350c41a3..fa00964d48 100644 --- a/deploy/helm/thecombine/charts/frontend/templates/deployment-frontend.yaml +++ b/deploy/helm/thecombine/charts/frontend/templates/deployment-frontend.yaml @@ -34,15 +34,10 @@ spec: configMapKeyRef: key: CERT_ADDL_DOMAINS name: env-frontend - - name: CONFIG_CAPTCHA_REQD + - name: CONFIG_TURNSTILE_REQUIRED valueFrom: configMapKeyRef: - key: CONFIG_CAPTCHA_REQD - name: env-frontend - - name: CONFIG_CAPTCHA_SITE_KEY - valueFrom: - configMapKeyRef: - key: CONFIG_CAPTCHA_SITE_KEY + key: CONFIG_TURNSTILE_REQUIRED name: env-frontend - name: CONFIG_USE_CONNECTION_URL valueFrom: diff --git a/deploy/helm/thecombine/charts/frontend/templates/env-frontend-configmap.yaml b/deploy/helm/thecombine/charts/frontend/templates/env-frontend-configmap.yaml index 5b9036f554..89b28ec4de 100644 --- a/deploy/helm/thecombine/charts/frontend/templates/env-frontend-configmap.yaml +++ b/deploy/helm/thecombine/charts/frontend/templates/env-frontend-configmap.yaml @@ -8,8 +8,7 @@ data: SERVER_NAME: {{ .Values.global.serverName }} CERT_ADDL_DOMAINS: {{ .Values.combineAddlDomainList | quote }} CONFIG_USE_CONNECTION_URL: "true" - CONFIG_CAPTCHA_REQD: {{ .Values.configCaptchaRequired | quote }} - CONFIG_CAPTCHA_SITE_KEY: {{ .Values.configCaptchaSiteKey | quote }} + CONFIG_TURNSTILE_REQUIRED: {{ .Values.configTurnstileRequired | quote }} CONFIG_OFFLINE: {{ .Values.configOffline | quote }} CONFIG_EMAIL_ENABLED: {{ and .Values.configEmailEnabled (empty .Values.global.combineSmtpUsername | not) | quote }} CONFIG_SHOW_CERT_EXPIRATION: {{ .Values.configShowCertExpiration | quote }} diff --git a/deploy/helm/thecombine/charts/frontend/values.yaml b/deploy/helm/thecombine/charts/frontend/values.yaml index 145e78a047..e24386bc16 100644 --- a/deploy/helm/thecombine/charts/frontend/values.yaml +++ b/deploy/helm/thecombine/charts/frontend/values.yaml @@ -18,8 +18,7 @@ imageName: combine_frontend # The additional domain list is a space-separated string list of domains combineAddlDomainList: "" -configCaptchaRequired: "false" -configCaptchaSiteKey: "None - from frontend chart" +configTurnstileRequired: "false" configOffline: "false" configEmailEnabled: "true" configShowCertExpiration: "false" diff --git a/deploy/helm/thecombine/values.yaml b/deploy/helm/thecombine/values.yaml index 9c8ac81895..056a6a644a 100644 --- a/deploy/helm/thecombine/values.yaml +++ b/deploy/helm/thecombine/values.yaml @@ -56,8 +56,7 @@ certManager: frontend: configShowCertExpiration: false configAnalyticsWriteKey: "" - configCaptchaRequired: false - configCaptchaSiteKey: "None" + configTurnstileRequired: false # Maintenance configuration items maintenance: diff --git a/deploy/scripts/setup_files/combine_config.yaml b/deploy/scripts/setup_files/combine_config.yaml index c610e3558a..b7c620e77d 100644 --- a/deploy/scripts/setup_files/combine_config.yaml +++ b/deploy/scripts/setup_files/combine_config.yaml @@ -80,8 +80,7 @@ targets: global: serverName: qa-kube.thecombine.app frontend: - configCaptchaRequired: "true" - configCaptchaSiteKey: "6LdZIlkaAAAAAES4FZ5d01Shj5G4X0e2CHYg0D5t" + configTurnstileRequired: "true" prod: profile: prod env_vars_required: true @@ -91,8 +90,7 @@ targets: global: serverName: thecombine.app frontend: - configCaptchaRequired: "true" - configCaptchaSiteKey: "6LdZIlkaAAAAAES4FZ5d01Shj5G4X0e2CHYg0D5t" + configTurnstileRequired: "true" # Set of profiles # Each key of 'profiles' defines one of the profiles used by the set of targets. diff --git a/deploy/scripts/setup_files/profiles/dev.yaml b/deploy/scripts/setup_files/profiles/dev.yaml index 9f1e3feb88..c02fd9a18c 100644 --- a/deploy/scripts/setup_files/profiles/dev.yaml +++ b/deploy/scripts/setup_files/profiles/dev.yaml @@ -11,8 +11,7 @@ charts: enabled: false frontend: - configCaptchaRequired: "true" - configCaptchaSiteKey: "6Le6BL0UAAAAAMjSs1nINeB5hqDZ4m3mMg3k67x3" + configTurnstileRequired: "true" global: imageRegistry: "" diff --git a/nginx/init/25-combine-runtime-config.sh b/nginx/init/25-combine-runtime-config.sh index 0ccf48111e..537ccd37ef 100755 --- a/nginx/init/25-combine-runtime-config.sh +++ b/nginx/init/25-combine-runtime-config.sh @@ -49,8 +49,7 @@ OUTFILE=${FRONTEND_HOST_DIR}/scripts/config.js declare -A env_map env_map=( ["CONFIG_USE_CONNECTION_URL"]="useConnectionBaseUrlForApi" - ["CONFIG_CAPTCHA_REQD"]="captchaRequired" - ["CONFIG_CAPTCHA_SITE_KEY"]="captchaSiteKey" + ["CONFIG_TURNSTILE_REQUIRED"]="turnstileRequired" ["CONFIG_ANALYTICS_WRITE_KEY"]="analyticsWriteKey" ["CONFIG_OFFLINE"]="offline" ["CONFIG_EMAIL_ENABLED"]="emailServicesEnabled" diff --git a/src/components/Login/Login.tsx b/src/components/Login/Login.tsx index b9f1ea36c2..b0e81e8549 100644 --- a/src/components/Login/Login.tsx +++ b/src/components/Login/Login.tsx @@ -52,7 +52,7 @@ export default function Login(): ReactElement { const [banner, setBanner] = useState(""); const [isVerified, setIsVerified] = useState( - !RuntimeConfig.getInstance().captchaRequired() + !RuntimeConfig.getInstance().turnstileRequired() ); const [password, setPassword] = useState(""); const [passwordError, setPasswordError] = useState(false); diff --git a/src/components/Login/Signup.tsx b/src/components/Login/Signup.tsx index ec38e96aa3..37fa6ef939 100644 --- a/src/components/Login/Signup.tsx +++ b/src/components/Login/Signup.tsx @@ -88,7 +88,7 @@ export default function Signup(props: SignupProps): ReactElement { const [fieldError, setFieldError] = useState(defaultSignupError); const [fieldText, setFieldText] = useState(defaultSignupText); const [isVerified, setIsVerified] = useState( - !RuntimeConfig.getInstance().captchaRequired() + !RuntimeConfig.getInstance().turnstileRequired() ); const { t } = useTranslation(); diff --git a/src/components/Login/Turnstile.tsx b/src/components/Login/Turnstile.tsx index 7587f53556..a7647dee76 100644 --- a/src/components/Login/Turnstile.tsx +++ b/src/components/Login/Turnstile.tsx @@ -37,7 +37,7 @@ export default function Turnstile(props: TurnstileProps): ReactElement { validateTurnstile(token).then((isValid) => (isValid ? succeed() : fail())); }; - return RuntimeConfig.getInstance().captchaRequired() ? ( + return RuntimeConfig.getInstance().turnstileRequired() ? ( Date: Thu, 18 Jul 2024 09:23:34 -0400 Subject: [PATCH 10/24] Clean up residue --- Backend/Properties/launchSettings.json | 4 ++-- Backend/Services/TurnstileService.cs | 2 -- deploy/helm/thecombine/charts/backend/values.yaml | 3 +-- src/components/Login/Login.tsx | 4 +--- src/components/Login/Signup.tsx | 5 +---- src/components/Login/Turnstile.tsx | 14 ++++++++++---- src/components/PasswordReset/Request.tsx | 5 +---- 7 files changed, 16 insertions(+), 21 deletions(-) diff --git a/Backend/Properties/launchSettings.json b/Backend/Properties/launchSettings.json index ff4248861d..4fff92abf1 100644 --- a/Backend/Properties/launchSettings.json +++ b/Backend/Properties/launchSettings.json @@ -17,7 +17,7 @@ "ASPNETCORE_ENVIRONMENT": "Development", "COMBINE_JWT_SECRET_KEY": "0123456789abcdefghijklmnopqrstuvwxyz", "TURNSTILE_SECRET_KEY": "1x0000000000000000000000000000000AA", - "TURNSTILE_VERIFY_URL": "challenges.cloudflare.com/turnstile/v0/siteverify" + "TURNSTILE_VERIFY_URL": "https://challenges.cloudflare.com/turnstile/v0/siteverify" } }, "BackendFramework": { @@ -29,7 +29,7 @@ "ASPNETCORE_ENVIRONMENT": "Development", "COMBINE_JWT_SECRET_KEY": "0123456789abcdefghijklmnopqrstuvwxyz", "TURNSTILE_SECRET_KEY": "1x0000000000000000000000000000000AA", - "TURNSTILE_VERIFY_URL": "challenges.cloudflare.com/turnstile/v0/siteverify" + "TURNSTILE_VERIFY_URL": "https://challenges.cloudflare.com/turnstile/v0/siteverify" }, "applicationUrl": "http://localhost:5000", "hotReloadProfile": "aspnetcore" diff --git a/Backend/Services/TurnstileService.cs b/Backend/Services/TurnstileService.cs index 3d6f0306dd..12b9e98773 100644 --- a/Backend/Services/TurnstileService.cs +++ b/Backend/Services/TurnstileService.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Net.Http; @@ -31,7 +30,6 @@ public async Task VerifyToken(string token) }); using var result = await new HttpClient().PostAsync(verifyUrl, httpContent); var contentString = await result.Content.ReadAsStringAsync(); - Console.WriteLine($"content: {contentString}"); return contentString.Contains("\"success\":true"); } } diff --git a/deploy/helm/thecombine/charts/backend/values.yaml b/deploy/helm/thecombine/charts/backend/values.yaml index 78ef81e7d1..2c8730e9b3 100644 --- a/deploy/helm/thecombine/charts/backend/values.yaml +++ b/deploy/helm/thecombine/charts/backend/values.yaml @@ -21,7 +21,6 @@ global: combineJwtSecretKey: "Override" combineSmtpUsername: "Override" combineSmtpPassword: "Override" - combineTurnstileSecretKey: "Override" # Values for pulling container image from image registry imagePullPolicy: "Override" imageTag: "latest" @@ -37,4 +36,4 @@ combineSmtpFrom: "The Combine" combineSmtpPort: 587 combineSmtpServer: "email-smtp.us-east-1.amazonaws.com" imageName: combine_backend -turnstileVerifyUrl: "challenges.cloudflare.com/turnstile/v0/siteverify" +turnstileVerifyUrl: "https://challenges.cloudflare.com/turnstile/v0/siteverify" diff --git a/src/components/Login/Login.tsx b/src/components/Login/Login.tsx index b0e81e8549..fb8b1d12fa 100644 --- a/src/components/Login/Login.tsx +++ b/src/components/Login/Login.tsx @@ -51,9 +51,7 @@ export default function Login(): ReactElement { ); const [banner, setBanner] = useState(""); - const [isVerified, setIsVerified] = useState( - !RuntimeConfig.getInstance().turnstileRequired() - ); + const [isVerified, setIsVerified] = useState(false); const [password, setPassword] = useState(""); const [passwordError, setPasswordError] = useState(false); const [username, setUsername] = useState(""); diff --git a/src/components/Login/Signup.tsx b/src/components/Login/Signup.tsx index 37fa6ef939..a46cbe01ec 100644 --- a/src/components/Login/Signup.tsx +++ b/src/components/Login/Signup.tsx @@ -25,7 +25,6 @@ import { useAppDispatch, useAppSelector } from "rootRedux/hooks"; import { type StoreState } from "rootRedux/types"; import router from "router/browserRouter"; import { Path } from "types/path"; -import { RuntimeConfig } from "types/runtimeConfig"; import { meetsPasswordRequirements, meetsUsernameRequirements, @@ -87,9 +86,7 @@ export default function Signup(props: SignupProps): ReactElement { const [fieldError, setFieldError] = useState(defaultSignupError); const [fieldText, setFieldText] = useState(defaultSignupText); - const [isVerified, setIsVerified] = useState( - !RuntimeConfig.getInstance().turnstileRequired() - ); + const [isVerified, setIsVerified] = useState(false); const { t } = useTranslation(); diff --git a/src/components/Login/Turnstile.tsx b/src/components/Login/Turnstile.tsx index a7647dee76..bad9792ac7 100644 --- a/src/components/Login/Turnstile.tsx +++ b/src/components/Login/Turnstile.tsx @@ -1,5 +1,5 @@ import { Turnstile as MarsiTurnstile } from "@marsidev/react-turnstile"; -import { Fragment, type ReactElement } from "react"; +import { Fragment, type ReactElement, useEffect, useRef } from "react"; import { useTranslation } from "react-i18next"; import { toast } from "react-toastify"; @@ -14,8 +14,14 @@ export interface TurnstileProps { /** Component wrapper for Cloudflare Turnstile (CAPTCHA replacement). */ export default function Turnstile(props: TurnstileProps): ReactElement { + const setSuccess = props.setSuccess; + const isRequired = useRef(RuntimeConfig.getInstance().turnstileRequired()); const { t } = useTranslation(); + useEffect(() => { + setSuccess(isRequired.current ? false : true); + }, [isRequired, setSuccess]); + const siteKey = process.env.NODE_ENV === "production" ? "0x4AAAAAAAe9zmM2ysXGSJk1" // the true site key for deployment @@ -27,17 +33,17 @@ export default function Turnstile(props: TurnstileProps): ReactElement { "3x00000000000000000000FF"; // force interactive challenge const fail = (): void => { - props.setSuccess(false); + setSuccess(false); toast.error(t("turnstile.error")); }; const succeed = (): void => { - props.setSuccess(true); + setSuccess(true); }; const validate = (token: string): void => { validateTurnstile(token).then((isValid) => (isValid ? succeed() : fail())); }; - return RuntimeConfig.getInstance().turnstileRequired() ? ( + return isRequired.current ? ( Date: Thu, 18 Jul 2024 10:21:17 -0400 Subject: [PATCH 11/24] Simplify bool conditional --- src/components/Login/Turnstile.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Login/Turnstile.tsx b/src/components/Login/Turnstile.tsx index bad9792ac7..d2d12822dd 100644 --- a/src/components/Login/Turnstile.tsx +++ b/src/components/Login/Turnstile.tsx @@ -19,7 +19,7 @@ export default function Turnstile(props: TurnstileProps): ReactElement { const { t } = useTranslation(); useEffect(() => { - setSuccess(isRequired.current ? false : true); + setSuccess(!isRequired.current); }, [isRequired, setSuccess]); const siteKey = From cef2a5ea4f2360b7badd3c307b2cd7688598cdb8 Mon Sep 17 00:00:00 2001 From: Danny Rorabaugh Date: Thu, 18 Jul 2024 13:07:11 -0400 Subject: [PATCH 12/24] Add turnstileSiteKey config --- README.md | 19 +++++++++++-------- .../templates/deployment-frontend.yaml | 5 +++++ .../templates/env-frontend-configmap.yaml | 1 + .../thecombine/charts/frontend/values.yaml | 1 + deploy/helm/thecombine/values.yaml | 1 + .../scripts/setup_files/combine_config.yaml | 2 ++ deploy/scripts/setup_files/profiles/dev.yaml | 2 ++ nginx/init/25-combine-runtime-config.sh | 1 + src/components/Login/Turnstile.tsx | 8 ++------ src/types/runtimeConfig.ts | 9 +++++++++ 10 files changed, 35 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 6c8e6d83d8..b3d411cca2 100644 --- a/README.md +++ b/README.md @@ -142,9 +142,9 @@ A rapid word collection tool. See the [User Guide](https://sillsdev.github.io/Th ### Prepare the Environment -1. If you want the email services to work you will need to set the following environment variables. These values must be - kept secret, so ask your email administrator to supply them. Set them in your `.profile` (Linux or Mac 10.14-), your - `.zprofile` (Mac 10.15+), or the _System_ app (Windows). +1. _(Optional)_ If you want the email services to work you will need to set the following environment variables. These + values must be kept secret, so ask your email administrator to supply them. Set them in your `.profile` (Linux or Mac + 10.14-), your `.zprofile` (Mac 10.15+), or the _System_ app (Windows). - `COMBINE_SMTP_SERVER` - `COMBINE_SMTP_PORT` @@ -685,7 +685,12 @@ Notes: ### Setup Environment Variables -_Note: This is optional for Development Environments._ +Before installing _The Combine_ in Kubernetes, you need to set the following environment variables: +`COMBINE_JWT_SECRET_KEY`, `TURNSTILE_SECRET_KEY`. For development environments, you can use the values defined in +`Backend/Properties/launchSettings.json`. Set them in your `.profile` (Linux or Mac 10.14-), your `.zprofile` (Mac +10.15+), or the _System_ app (Windows). + +_Note: The following is optional for Development Environments._ In addition to the environment variables defined in [Prepare the Environment](#prepare-the-environment), you may setup the following environment variables: @@ -695,15 +700,13 @@ the following environment variables: - `AWS_ACCESS_KEY_ID` - `AWS_SECRET_ACCESS_KEY` -These variables will allow the Combine to: +These variables will allow _The Combine_ to: - pull released and QA software images from AWS Elastic Container Registry (ECR); - create backups and push them to AWS S3 storage; and - restore _The Combine's_ database and backend files from a backup stored in AWS S3 storage. -The Combine application will function in a local cluster without these variables set. - -These can be set in your `.profile` (Linux or Mac 10.14-), your `.zprofile` (Mac 10.15+), or the _System_ app (Windows). +The Combine application will function in a local cluster without these `AWS_` variables set. ### Install/Update _The Combine_ diff --git a/deploy/helm/thecombine/charts/frontend/templates/deployment-frontend.yaml b/deploy/helm/thecombine/charts/frontend/templates/deployment-frontend.yaml index fa00964d48..dd73138ea1 100644 --- a/deploy/helm/thecombine/charts/frontend/templates/deployment-frontend.yaml +++ b/deploy/helm/thecombine/charts/frontend/templates/deployment-frontend.yaml @@ -39,6 +39,11 @@ spec: configMapKeyRef: key: CONFIG_TURNSTILE_REQUIRED name: env-frontend + - name: CONFIG_TURNSTILE_SITE_KEY + valueFrom: + configMapKeyRef: + key: CONFIG_TURNSTILE_SITE_KEY + name: env-frontend - name: CONFIG_USE_CONNECTION_URL valueFrom: configMapKeyRef: diff --git a/deploy/helm/thecombine/charts/frontend/templates/env-frontend-configmap.yaml b/deploy/helm/thecombine/charts/frontend/templates/env-frontend-configmap.yaml index 89b28ec4de..c6ae1ff65a 100644 --- a/deploy/helm/thecombine/charts/frontend/templates/env-frontend-configmap.yaml +++ b/deploy/helm/thecombine/charts/frontend/templates/env-frontend-configmap.yaml @@ -9,6 +9,7 @@ data: CERT_ADDL_DOMAINS: {{ .Values.combineAddlDomainList | quote }} CONFIG_USE_CONNECTION_URL: "true" CONFIG_TURNSTILE_REQUIRED: {{ .Values.configTurnstileRequired | quote }} + CONFIG_TURNSTILE_SITE_KEY: {{ .Values.configTurnstileSiteKey | quote }} CONFIG_OFFLINE: {{ .Values.configOffline | quote }} CONFIG_EMAIL_ENABLED: {{ and .Values.configEmailEnabled (empty .Values.global.combineSmtpUsername | not) | quote }} CONFIG_SHOW_CERT_EXPIRATION: {{ .Values.configShowCertExpiration | quote }} diff --git a/deploy/helm/thecombine/charts/frontend/values.yaml b/deploy/helm/thecombine/charts/frontend/values.yaml index e24386bc16..3aec9f3d72 100644 --- a/deploy/helm/thecombine/charts/frontend/values.yaml +++ b/deploy/helm/thecombine/charts/frontend/values.yaml @@ -19,6 +19,7 @@ imageName: combine_frontend # The additional domain list is a space-separated string list of domains combineAddlDomainList: "" configTurnstileRequired: "false" +configTurnstileSiteKey: "None - from frontend chart" configOffline: "false" configEmailEnabled: "true" configShowCertExpiration: "false" diff --git a/deploy/helm/thecombine/values.yaml b/deploy/helm/thecombine/values.yaml index 056a6a644a..77ec445451 100644 --- a/deploy/helm/thecombine/values.yaml +++ b/deploy/helm/thecombine/values.yaml @@ -57,6 +57,7 @@ frontend: configShowCertExpiration: false configAnalyticsWriteKey: "" configTurnstileRequired: false + configTurnstileSiteKey: "None" # Maintenance configuration items maintenance: diff --git a/deploy/scripts/setup_files/combine_config.yaml b/deploy/scripts/setup_files/combine_config.yaml index b7c620e77d..469e626a9b 100644 --- a/deploy/scripts/setup_files/combine_config.yaml +++ b/deploy/scripts/setup_files/combine_config.yaml @@ -81,6 +81,7 @@ targets: serverName: qa-kube.thecombine.app frontend: configTurnstileRequired: "true" + configTurnstileSiteKey: "0x4AAAAAAAe9zmM2ysXGSJk1" prod: profile: prod env_vars_required: true @@ -91,6 +92,7 @@ targets: serverName: thecombine.app frontend: configTurnstileRequired: "true" + configTurnstileSiteKey: "0x4AAAAAAAe9zmM2ysXGSJk1" # Set of profiles # Each key of 'profiles' defines one of the profiles used by the set of targets. diff --git a/deploy/scripts/setup_files/profiles/dev.yaml b/deploy/scripts/setup_files/profiles/dev.yaml index c02fd9a18c..3febed9bd6 100644 --- a/deploy/scripts/setup_files/profiles/dev.yaml +++ b/deploy/scripts/setup_files/profiles/dev.yaml @@ -12,6 +12,8 @@ charts: frontend: configTurnstileRequired: "true" + # https://developers.cloudflare.com/turnstile/troubleshooting/testing/ + configTurnstileSiteKey: "1x00000000000000000000AA" # visible pass global: imageRegistry: "" diff --git a/nginx/init/25-combine-runtime-config.sh b/nginx/init/25-combine-runtime-config.sh index 537ccd37ef..f8a412db34 100755 --- a/nginx/init/25-combine-runtime-config.sh +++ b/nginx/init/25-combine-runtime-config.sh @@ -50,6 +50,7 @@ declare -A env_map env_map=( ["CONFIG_USE_CONNECTION_URL"]="useConnectionBaseUrlForApi" ["CONFIG_TURNSTILE_REQUIRED"]="turnstileRequired" + ["CONFIG_TURNSTILE_SITE_KEY"]="turnstileSiteKey" ["CONFIG_ANALYTICS_WRITE_KEY"]="analyticsWriteKey" ["CONFIG_OFFLINE"]="offline" ["CONFIG_EMAIL_ENABLED"]="emailServicesEnabled" diff --git a/src/components/Login/Turnstile.tsx b/src/components/Login/Turnstile.tsx index d2d12822dd..008ac3d73c 100644 --- a/src/components/Login/Turnstile.tsx +++ b/src/components/Login/Turnstile.tsx @@ -24,13 +24,9 @@ export default function Turnstile(props: TurnstileProps): ReactElement { const siteKey = process.env.NODE_ENV === "production" - ? "0x4AAAAAAAe9zmM2ysXGSJk1" // the true site key for deployment + ? RuntimeConfig.getInstance().turnstileSiteKey() : // https://developers.cloudflare.com/turnstile/troubleshooting/testing/ - //"1x00000000000000000000AA"; // visible pass - //"2x00000000000000000000AB"; // visible fail - //"1x00000000000000000000BB"; // invisible pass - //"2x00000000000000000000BB"; // invisible fail - "3x00000000000000000000FF"; // force interactive challenge + "1x00000000000000000000AA"; // visible pass const fail = (): void => { setSuccess(false); diff --git a/src/types/runtimeConfig.ts b/src/types/runtimeConfig.ts index d782654061..afe2540f8f 100644 --- a/src/types/runtimeConfig.ts +++ b/src/types/runtimeConfig.ts @@ -1,6 +1,7 @@ interface RuntimeConfigItems { baseUrl: string; turnstileRequired: boolean; + turnstileSiteKey: string; offline: boolean; emailServicesEnabled: boolean; showCertExpiration: boolean; @@ -16,6 +17,7 @@ declare global { const defaultConfig: RuntimeConfigItems = { baseUrl: "http://localhost:5000", turnstileRequired: true, + turnstileSiteKey: "0x4AAAAAAAe9zmM2ysXGSJk1", offline: false, emailServicesEnabled: true, showCertExpiration: true, @@ -61,6 +63,13 @@ export class RuntimeConfig { return defaultConfig.turnstileRequired; } + public turnstileSiteKey(): string { + if (window.runtimeConfig.hasOwnProperty("turnstileSiteKey")) { + return window.runtimeConfig.turnstileSiteKey; + } + return defaultConfig.turnstileSiteKey; + } + public emailServicesEnabled(): boolean { if (RuntimeConfig._instance.isOffline()) { return false; From 4f0035b25a477946ad8428e03b3ec99e79dad4df Mon Sep 17 00:00:00 2001 From: Danny Rorabaugh Date: Fri, 19 Jul 2024 10:33:39 -0400 Subject: [PATCH 13/24] Restore frontend Captcha configs; Add backend Enabled configs --- Backend/Contexts/EmailContext.cs | 2 + Backend/Contexts/TurnstileContext.cs | 2 + Backend/Interfaces/IEmailContext.cs | 1 + Backend/Interfaces/ITurnstileContext.cs | 1 + Backend/Properties/launchSettings.json | 2 + Backend/Services/EmailService.cs | 8 ++ Backend/Services/TurnstileService.cs | 8 ++ Backend/Startup.cs | 82 +++++++++++-------- README.md | 11 +-- .../backend/templates/backend-config-map.yaml | 2 + .../backend/templates/deployment-backend.yaml | 10 +++ .../thecombine/charts/backend/values.yaml | 2 + .../templates/deployment-frontend.yaml | 8 +- .../templates/env-frontend-configmap.yaml | 6 +- .../thecombine/charts/frontend/values.yaml | 4 +- deploy/helm/thecombine/values.yaml | 4 +- deploy/scripts/install-combine.sh | 3 +- .../scripts/setup_files/combine_config.yaml | 8 +- .../scripts/setup_files/profiles/desktop.yaml | 2 +- deploy/scripts/setup_files/profiles/dev.yaml | 6 +- deploy/scripts/setup_files/profiles/nuc.yaml | 2 +- .../scripts/setup_files/profiles/nuc_qa.yaml | 2 +- nginx/init/25-combine-runtime-config.sh | 4 +- public/locales/en/translation.json | 2 +- src/backend/index.ts | 2 +- .../Login/{Turnstile.tsx => Captcha.tsx} | 22 ++--- src/components/Login/Login.tsx | 4 +- src/components/Login/Signup.tsx | 4 +- src/components/Login/tests/Login.test.tsx | 2 +- src/components/Login/tests/MockCaptcha.tsx | 11 +++ src/components/Login/tests/MockTurnstile.tsx | 11 --- src/components/Login/tests/Signup.test.tsx | 2 +- src/components/PasswordReset/Request.tsx | 4 +- .../PasswordReset/tests/Request.test.tsx | 4 +- src/types/runtimeConfig.ts | 24 +++--- 35 files changed, 167 insertions(+), 105 deletions(-) rename src/components/Login/{Turnstile.tsx => Captcha.tsx} (62%) create mode 100644 src/components/Login/tests/MockCaptcha.tsx delete mode 100644 src/components/Login/tests/MockTurnstile.tsx diff --git a/Backend/Contexts/EmailContext.cs b/Backend/Contexts/EmailContext.cs index 57a6f24f0d..fe252d7517 100644 --- a/Backend/Contexts/EmailContext.cs +++ b/Backend/Contexts/EmailContext.cs @@ -7,6 +7,7 @@ namespace BackendFramework.Contexts [ExcludeFromCodeCoverage] public class EmailContext : IEmailContext { + public bool EmailEnabled { get; } public string? SmtpServer { get; } public int SmtpPort { get; } public string? SmtpUsername { get; } @@ -16,6 +17,7 @@ public class EmailContext : IEmailContext public EmailContext(IOptions options) { + EmailEnabled = options.Value.EmailEnabled; SmtpServer = options.Value.SmtpServer; SmtpPort = options.Value.SmtpPort ?? IEmailContext.InvalidPort; SmtpUsername = options.Value.SmtpUsername; diff --git a/Backend/Contexts/TurnstileContext.cs b/Backend/Contexts/TurnstileContext.cs index 7705973120..0cceb6c34d 100644 --- a/Backend/Contexts/TurnstileContext.cs +++ b/Backend/Contexts/TurnstileContext.cs @@ -7,11 +7,13 @@ namespace BackendFramework.Contexts [ExcludeFromCodeCoverage] public class TurnstileContext : ITurnstileContext { + public bool TurnstileEnabled { get; } public string? TurnstileSecretKey { get; } public string? TurnstileVerifyUrl { get; } public TurnstileContext(IOptions options) { + TurnstileEnabled = options.Value.TurnstileEnabled; TurnstileSecretKey = options.Value.TurnstileSecretKey; TurnstileVerifyUrl = options.Value.TurnstileVerifyUrl; } diff --git a/Backend/Interfaces/IEmailContext.cs b/Backend/Interfaces/IEmailContext.cs index cae139443c..13892f9df8 100644 --- a/Backend/Interfaces/IEmailContext.cs +++ b/Backend/Interfaces/IEmailContext.cs @@ -6,6 +6,7 @@ public interface IEmailContext /// This is value is set if the user does not supply an SMTP port number. public const int InvalidPort = -1; + bool EmailEnabled { get; } string? SmtpServer { get; } int SmtpPort { get; } string? SmtpUsername { get; } diff --git a/Backend/Interfaces/ITurnstileContext.cs b/Backend/Interfaces/ITurnstileContext.cs index d0e19e0b91..f386d5d27c 100644 --- a/Backend/Interfaces/ITurnstileContext.cs +++ b/Backend/Interfaces/ITurnstileContext.cs @@ -2,6 +2,7 @@ namespace BackendFramework.Interfaces { public interface ITurnstileContext { + bool TurnstileEnabled { get; } string? TurnstileSecretKey { get; } string? TurnstileVerifyUrl { get; } } diff --git a/Backend/Properties/launchSettings.json b/Backend/Properties/launchSettings.json index 4fff92abf1..72309a8875 100644 --- a/Backend/Properties/launchSettings.json +++ b/Backend/Properties/launchSettings.json @@ -16,6 +16,7 @@ "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development", "COMBINE_JWT_SECRET_KEY": "0123456789abcdefghijklmnopqrstuvwxyz", + "TURNSTILE_ENABLED": "True", "TURNSTILE_SECRET_KEY": "1x0000000000000000000000000000000AA", "TURNSTILE_VERIFY_URL": "https://challenges.cloudflare.com/turnstile/v0/siteverify" } @@ -28,6 +29,7 @@ "Key": "Value", "ASPNETCORE_ENVIRONMENT": "Development", "COMBINE_JWT_SECRET_KEY": "0123456789abcdefghijklmnopqrstuvwxyz", + "TURNSTILE_ENABLED": "True", "TURNSTILE_SECRET_KEY": "1x0000000000000000000000000000000AA", "TURNSTILE_VERIFY_URL": "https://challenges.cloudflare.com/turnstile/v0/siteverify" }, diff --git a/Backend/Services/EmailService.cs b/Backend/Services/EmailService.cs index e4ba7d0014..84c29cbb42 100644 --- a/Backend/Services/EmailService.cs +++ b/Backend/Services/EmailService.cs @@ -2,6 +2,7 @@ using BackendFramework.Interfaces; using System.Threading.Tasks; using MimeKit; +using System; namespace BackendFramework.Services { @@ -17,6 +18,11 @@ public EmailService(IEmailContext emailContext) public async Task SendEmail(MimeMessage message) { + if (!_emailContext.EmailEnabled) + { + throw new EmailNotEnabledException(); + } + using var client = new MailKit.Net.Smtp.SmtpClient(); await client.ConnectAsync(_emailContext.SmtpServer, _emailContext.SmtpPort); @@ -33,5 +39,7 @@ public async Task SendEmail(MimeMessage message) await client.DisconnectAsync(true); return true; } + + private sealed class EmailNotEnabledException : Exception { } } } diff --git a/Backend/Services/TurnstileService.cs b/Backend/Services/TurnstileService.cs index 12b9e98773..13f0f3b204 100644 --- a/Backend/Services/TurnstileService.cs +++ b/Backend/Services/TurnstileService.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Net.Http; @@ -18,6 +19,11 @@ public TurnstileService(ITurnstileContext turnstileContext) public async Task VerifyToken(string token) { + if (!_turnstileContext.TurnstileEnabled) + { + throw new TurnstileNotEnabledException(); + } + var secret = _turnstileContext.TurnstileSecretKey; var verifyUrl = _turnstileContext.TurnstileVerifyUrl; if (string.IsNullOrEmpty(secret) || string.IsNullOrEmpty(verifyUrl)) @@ -32,5 +38,7 @@ public async Task VerifyToken(string token) var contentString = await result.Content.ReadAsStringAsync(); return contentString.Contains("\"success\":true"); } + + private sealed class TurnstileNotEnabledException : Exception { } } } diff --git a/Backend/Startup.cs b/Backend/Startup.cs index bd02452d00..6bda85c100 100644 --- a/Backend/Startup.cs +++ b/Backend/Startup.cs @@ -41,6 +41,7 @@ public class Settings public string ConnectionString { get; set; } public string CombineDatabase { get; set; } + public bool EmailEnabled { get; set; } public string? SmtpServer { get; set; } public int? SmtpPort { get; set; } public string? SmtpUsername { get; set; } @@ -48,6 +49,7 @@ public class Settings public string? SmtpAddress { get; set; } public string? SmtpFrom { get; set; } public int PassResetExpireTime { get; set; } + public bool TurnstileEnabled { get; set; } public string? TurnstileSecretKey { get; set; } public string? TurnstileVerifyUrl { get; set; } @@ -55,7 +57,9 @@ public Settings() { ConnectionString = ""; CombineDatabase = ""; + EmailEnabled = false; PassResetExpireTime = DefaultPasswordResetExpireTime; + TurnstileEnabled = true; } } @@ -156,45 +160,59 @@ public void ConfigureServices(IServiceCollection services) ?? throw new EnvironmentNotConfiguredException(); const string emailServiceFailureMessage = "Email services will not work."; - options.SmtpServer = CheckedEnvironmentVariable( - "COMBINE_SMTP_SERVER", - null, - emailServiceFailureMessage); - options.SmtpPort = int.Parse(CheckedEnvironmentVariable( - "COMBINE_SMTP_PORT", - IEmailContext.InvalidPort.ToString(), + options.EmailEnabled = bool.Parse(CheckedEnvironmentVariable( + "COMBINE_EMAIL_ENABLED", + bool.FalseString, // "False" emailServiceFailureMessage)!); - options.SmtpUsername = CheckedEnvironmentVariable( - "COMBINE_SMTP_USERNAME", - null, - emailServiceFailureMessage); - options.SmtpPassword = CheckedEnvironmentVariable( - "COMBINE_SMTP_PASSWORD", - null, - emailServiceFailureMessage); - options.SmtpAddress = CheckedEnvironmentVariable( - "COMBINE_SMTP_ADDRESS", - null, - emailServiceFailureMessage); - options.SmtpFrom = CheckedEnvironmentVariable( - "COMBINE_SMTP_FROM", - null, - emailServiceFailureMessage); + if (options.EmailEnabled) + { + options.SmtpServer = CheckedEnvironmentVariable( + "COMBINE_SMTP_SERVER", + null, + emailServiceFailureMessage); + options.SmtpPort = int.Parse(CheckedEnvironmentVariable( + "COMBINE_SMTP_PORT", + IEmailContext.InvalidPort.ToString(), + emailServiceFailureMessage)!); + options.SmtpUsername = CheckedEnvironmentVariable( + "COMBINE_SMTP_USERNAME", + null, + emailServiceFailureMessage); + options.SmtpPassword = CheckedEnvironmentVariable( + "COMBINE_SMTP_PASSWORD", + null, + emailServiceFailureMessage); + options.SmtpAddress = CheckedEnvironmentVariable( + "COMBINE_SMTP_ADDRESS", + null, + emailServiceFailureMessage); + options.SmtpFrom = CheckedEnvironmentVariable( + "COMBINE_SMTP_FROM", + null, + emailServiceFailureMessage); + } options.PassResetExpireTime = int.Parse(CheckedEnvironmentVariable( "COMBINE_PASSWORD_RESET_EXPIRE_TIME", Settings.DefaultPasswordResetExpireTime.ToString(), $"Using default value: {Settings.DefaultPasswordResetExpireTime}")!); - const string turnstileFailureMessage = "Turnstile verification will not be available."; - options.TurnstileSecretKey = CheckedEnvironmentVariable( - "TURNSTILE_SECRET_KEY", - null, - turnstileFailureMessage); - options.TurnstileVerifyUrl = CheckedEnvironmentVariable( - "TURNSTILE_VERIFY_URL", - null, - turnstileFailureMessage); + + options.TurnstileEnabled = bool.Parse(CheckedEnvironmentVariable( + "TURNSTILE_ENABLED", + bool.TrueString, // "True" + "Turnstile should be explicitly enabled or disabled.")!); + if (options.TurnstileEnabled) + { + options.TurnstileSecretKey = CheckedEnvironmentVariable( + "TURNSTILE_SECRET_KEY", + null, + "Turnstile secret key required."); + options.TurnstileVerifyUrl = CheckedEnvironmentVariable( + "TURNSTILE_VERIFY_URL", + null, + "Turnstile verification URL required."); + } }); // Register concrete types for dependency injection diff --git a/README.md b/README.md index b3d411cca2..a774598649 100644 --- a/README.md +++ b/README.md @@ -143,9 +143,10 @@ A rapid word collection tool. See the [User Guide](https://sillsdev.github.io/Th ### Prepare the Environment 1. _(Optional)_ If you want the email services to work you will need to set the following environment variables. These - values must be kept secret, so ask your email administrator to supply them. Set them in your `.profile` (Linux or Mac - 10.14-), your `.zprofile` (Mac 10.15+), or the _System_ app (Windows). + SMTP values must be kept secret, so ask your email administrator to supply them. Set them in your `.profile` (Linux + or Mac 10.14-), your `.zprofile` (Mac 10.15+), or the _System_ app (Windows). + - `COMBINE_EMAIL_ENABLED=True` - `COMBINE_SMTP_SERVER` - `COMBINE_SMTP_PORT` - `COMBINE_SMTP_USERNAME` @@ -686,9 +687,9 @@ Notes: ### Setup Environment Variables Before installing _The Combine_ in Kubernetes, you need to set the following environment variables: -`COMBINE_JWT_SECRET_KEY`, `TURNSTILE_SECRET_KEY`. For development environments, you can use the values defined in -`Backend/Properties/launchSettings.json`. Set them in your `.profile` (Linux or Mac 10.14-), your `.zprofile` (Mac -10.15+), or the _System_ app (Windows). +`COMBINE_JWT_SECRET_KEY`, `TURNSTILE_ENABLED`, `TURNSTILE_SECRET_KEY`. For development environments, you can use the +values defined in `Backend/Properties/launchSettings.json`. Set them in your `.profile` (Linux or Mac 10.14-), your +`.zprofile` (Mac 10.15+), or the _System_ app (Windows). _Note: The following is optional for Development Environments._ diff --git a/deploy/helm/thecombine/charts/backend/templates/backend-config-map.yaml b/deploy/helm/thecombine/charts/backend/templates/backend-config-map.yaml index 06888dc89e..d30c9bf3f5 100644 --- a/deploy/helm/thecombine/charts/backend/templates/backend-config-map.yaml +++ b/deploy/helm/thecombine/charts/backend/templates/backend-config-map.yaml @@ -7,8 +7,10 @@ metadata: data: COMBINE_PASSWORD_RESET_EXPIRE_TIME: {{ .Values.combinePasswordResetTime | quote }} + COMBINE_EMAIL_ENABLED: {{ .Values.combineEmailEnabled | quote }} COMBINE_SMTP_ADDRESS: {{ .Values.combineSmtpAddress | quote }} COMBINE_SMTP_FROM: {{ .Values.combineSmtpFrom | quote }} COMBINE_SMTP_PORT: {{ .Values.combineSmtpPort | quote }} COMBINE_SMTP_SERVER: {{ .Values.combineSmtpServer | quote }} + TURNSTILE_ENABLED: {{ .Values.turnstileEnabled | quote }} TURNSTILE_VERIFY_URL: {{ .Values.turnstileVerifyUrl | quote }} diff --git a/deploy/helm/thecombine/charts/backend/templates/deployment-backend.yaml b/deploy/helm/thecombine/charts/backend/templates/deployment-backend.yaml index d4ef7db409..193d999e84 100644 --- a/deploy/helm/thecombine/charts/backend/templates/deployment-backend.yaml +++ b/deploy/helm/thecombine/charts/backend/templates/deployment-backend.yaml @@ -39,6 +39,11 @@ spec: configMapKeyRef: key: COMBINE_PASSWORD_RESET_EXPIRE_TIME name: env-backend + - name: COMBINE_EMAIL_ENABLED + valueFrom: + configMapKeyRef: + key: COMBINE_EMAIL_ENABLED + name: env-backend - name: COMBINE_SMTP_ADDRESS valueFrom: configMapKeyRef: @@ -69,6 +74,11 @@ spec: secretKeyRef: key: COMBINE_SMTP_USERNAME name: env-backend-secrets + - name: TURNSTILE_ENABLED + valueFrom: + configMapKeyRef: + key: TURNSTILE_ENABLED + name: env-backend - name: TURNSTILE_SECRET_KEY valueFrom: secretKeyRef: diff --git a/deploy/helm/thecombine/charts/backend/values.yaml b/deploy/helm/thecombine/charts/backend/values.yaml index 2c8730e9b3..3e7c890f82 100644 --- a/deploy/helm/thecombine/charts/backend/values.yaml +++ b/deploy/helm/thecombine/charts/backend/values.yaml @@ -31,9 +31,11 @@ global: persistentVolumeSize: 32Gi combinePasswordResetTime: 15 +combineEmailEnabled: "True" combineSmtpAddress: no-reply@thecombine.app combineSmtpFrom: "The Combine" combineSmtpPort: 587 combineSmtpServer: "email-smtp.us-east-1.amazonaws.com" imageName: combine_backend +turnstileEnabled: "True" turnstileVerifyUrl: "https://challenges.cloudflare.com/turnstile/v0/siteverify" diff --git a/deploy/helm/thecombine/charts/frontend/templates/deployment-frontend.yaml b/deploy/helm/thecombine/charts/frontend/templates/deployment-frontend.yaml index dd73138ea1..739efab059 100644 --- a/deploy/helm/thecombine/charts/frontend/templates/deployment-frontend.yaml +++ b/deploy/helm/thecombine/charts/frontend/templates/deployment-frontend.yaml @@ -34,15 +34,15 @@ spec: configMapKeyRef: key: CERT_ADDL_DOMAINS name: env-frontend - - name: CONFIG_TURNSTILE_REQUIRED + - name: CONFIG_CAPTCHA_REQUIRED valueFrom: configMapKeyRef: - key: CONFIG_TURNSTILE_REQUIRED + key: CONFIG_CAPTCHA_REQUIRED name: env-frontend - - name: CONFIG_TURNSTILE_SITE_KEY + - name: CONFIG_CAPTCHA_SITE_KEY valueFrom: configMapKeyRef: - key: CONFIG_TURNSTILE_SITE_KEY + key: CONFIG_CAPTCHA_SITE_KEY name: env-frontend - name: CONFIG_USE_CONNECTION_URL valueFrom: diff --git a/deploy/helm/thecombine/charts/frontend/templates/env-frontend-configmap.yaml b/deploy/helm/thecombine/charts/frontend/templates/env-frontend-configmap.yaml index c6ae1ff65a..bf92b19546 100644 --- a/deploy/helm/thecombine/charts/frontend/templates/env-frontend-configmap.yaml +++ b/deploy/helm/thecombine/charts/frontend/templates/env-frontend-configmap.yaml @@ -8,10 +8,10 @@ data: SERVER_NAME: {{ .Values.global.serverName }} CERT_ADDL_DOMAINS: {{ .Values.combineAddlDomainList | quote }} CONFIG_USE_CONNECTION_URL: "true" - CONFIG_TURNSTILE_REQUIRED: {{ .Values.configTurnstileRequired | quote }} - CONFIG_TURNSTILE_SITE_KEY: {{ .Values.configTurnstileSiteKey | quote }} + CONFIG_CAPTCHA_REQUIRED: {{ .Values.configCaptchaRequired | quote }} + CONFIG_CAPTCHA_SITE_KEY: {{ .Values.configCaptchaSiteKey | quote }} CONFIG_OFFLINE: {{ .Values.configOffline | quote }} - CONFIG_EMAIL_ENABLED: {{ and .Values.configEmailEnabled (empty .Values.global.combineSmtpUsername | not) | quote }} + CONFIG_EMAIL_ENABLED: {{ and .Values.configEmailEnabled (eq .Values.global.combineEmailEnabled "True") | quote }} CONFIG_SHOW_CERT_EXPIRATION: {{ .Values.configShowCertExpiration | quote }} {{- if .Values.configAnalyticsWriteKey }} CONFIG_ANALYTICS_WRITE_KEY: {{ .Values.configAnalyticsWriteKey | quote }} diff --git a/deploy/helm/thecombine/charts/frontend/values.yaml b/deploy/helm/thecombine/charts/frontend/values.yaml index 3aec9f3d72..145e78a047 100644 --- a/deploy/helm/thecombine/charts/frontend/values.yaml +++ b/deploy/helm/thecombine/charts/frontend/values.yaml @@ -18,8 +18,8 @@ imageName: combine_frontend # The additional domain list is a space-separated string list of domains combineAddlDomainList: "" -configTurnstileRequired: "false" -configTurnstileSiteKey: "None - from frontend chart" +configCaptchaRequired: "false" +configCaptchaSiteKey: "None - from frontend chart" configOffline: "false" configEmailEnabled: "true" configShowCertExpiration: "false" diff --git a/deploy/helm/thecombine/values.yaml b/deploy/helm/thecombine/values.yaml index 77ec445451..9c8ac81895 100644 --- a/deploy/helm/thecombine/values.yaml +++ b/deploy/helm/thecombine/values.yaml @@ -56,8 +56,8 @@ certManager: frontend: configShowCertExpiration: false configAnalyticsWriteKey: "" - configTurnstileRequired: false - configTurnstileSiteKey: "None" + configCaptchaRequired: false + configCaptchaSiteKey: "None" # Maintenance configuration items maintenance: diff --git a/deploy/scripts/install-combine.sh b/deploy/scripts/install-combine.sh index b32cb18d66..0091f41961 100755 --- a/deploy/scripts/install-combine.sh +++ b/deploy/scripts/install-combine.sh @@ -17,7 +17,8 @@ set-combine-env () { export AWS_DEFAULT_REGION="us-east-1" export AWS_ACCESS_KEY_ID="${AWS_ACCESS_KEY_ID}" export AWS_SECRET_ACCESS_KEY="${AWS_SECRET_ACCESS_KEY}" - export COMBINE_SMTP_USERNAME="nobody" + export COMBINE_EMAIL_ENABLED="False" + export COMBINE_TURNSTILE_ENABLED="False" .EOF chmod 600 ${CONFIG_DIR}/env fi diff --git a/deploy/scripts/setup_files/combine_config.yaml b/deploy/scripts/setup_files/combine_config.yaml index 469e626a9b..ceab14facc 100644 --- a/deploy/scripts/setup_files/combine_config.yaml +++ b/deploy/scripts/setup_files/combine_config.yaml @@ -80,8 +80,8 @@ targets: global: serverName: qa-kube.thecombine.app frontend: - configTurnstileRequired: "true" - configTurnstileSiteKey: "0x4AAAAAAAe9zmM2ysXGSJk1" + configCaptchaRequired: "true" + configCaptchaSiteKey: "0x4AAAAAAAe9zmM2ysXGSJk1" prod: profile: prod env_vars_required: true @@ -91,8 +91,8 @@ targets: global: serverName: thecombine.app frontend: - configTurnstileRequired: "true" - configTurnstileSiteKey: "0x4AAAAAAAe9zmM2ysXGSJk1" + configCaptchaRequired: "true" + configCaptchaSiteKey: "0x4AAAAAAAe9zmM2ysXGSJk1" # Set of profiles # Each key of 'profiles' defines one of the profiles used by the set of targets. diff --git a/deploy/scripts/setup_files/profiles/desktop.yaml b/deploy/scripts/setup_files/profiles/desktop.yaml index 7a30eaab80..b19dc37313 100644 --- a/deploy/scripts/setup_files/profiles/desktop.yaml +++ b/deploy/scripts/setup_files/profiles/desktop.yaml @@ -13,7 +13,7 @@ charts: enabled: false global: awsS3Location: local.thecombine.app - combineSmtpUsername: "" + combineEmailEnabled: "False" imagePullPolicy: IfNotPresent pullSecretName: None frontend: diff --git a/deploy/scripts/setup_files/profiles/dev.yaml b/deploy/scripts/setup_files/profiles/dev.yaml index 3febed9bd6..5462380da3 100644 --- a/deploy/scripts/setup_files/profiles/dev.yaml +++ b/deploy/scripts/setup_files/profiles/dev.yaml @@ -11,9 +11,11 @@ charts: enabled: false frontend: - configTurnstileRequired: "true" + configCaptchaRequired: "true" # https://developers.cloudflare.com/turnstile/troubleshooting/testing/ - configTurnstileSiteKey: "1x00000000000000000000AA" # visible pass + # has dummy secret keys for development and testing; options are + # invisible pass and fail, visible pass and fail, and forced interaction + configCaptchaSiteKey: "1x00000000000000000000AA" # visible pass global: imageRegistry: "" diff --git a/deploy/scripts/setup_files/profiles/nuc.yaml b/deploy/scripts/setup_files/profiles/nuc.yaml index 9eb2f9de62..2d4bbba15b 100644 --- a/deploy/scripts/setup_files/profiles/nuc.yaml +++ b/deploy/scripts/setup_files/profiles/nuc.yaml @@ -13,7 +13,7 @@ charts: enabled: false global: awsS3Location: prod.thecombine.app - combineSmtpUsername: "" + combineEmailEnabled: "False" imagePullPolicy: IfNotPresent pullSecretName: None frontend: diff --git a/deploy/scripts/setup_files/profiles/nuc_qa.yaml b/deploy/scripts/setup_files/profiles/nuc_qa.yaml index a3c8d8e106..c08d62bed6 100644 --- a/deploy/scripts/setup_files/profiles/nuc_qa.yaml +++ b/deploy/scripts/setup_files/profiles/nuc_qa.yaml @@ -11,7 +11,7 @@ charts: enabled: true global: awsS3Location: prod.thecombine.app - combineSmtpUsername: "" + combineEmailEnabled: "False" imagePullPolicy: Always frontend: configOffline: true diff --git a/nginx/init/25-combine-runtime-config.sh b/nginx/init/25-combine-runtime-config.sh index f8a412db34..62fa83e8e1 100755 --- a/nginx/init/25-combine-runtime-config.sh +++ b/nginx/init/25-combine-runtime-config.sh @@ -49,8 +49,8 @@ OUTFILE=${FRONTEND_HOST_DIR}/scripts/config.js declare -A env_map env_map=( ["CONFIG_USE_CONNECTION_URL"]="useConnectionBaseUrlForApi" - ["CONFIG_TURNSTILE_REQUIRED"]="turnstileRequired" - ["CONFIG_TURNSTILE_SITE_KEY"]="turnstileSiteKey" + ["CONFIG_CAPTCHA_REQUIRED"]="captchaRequired" + ["CONFIG_CAPTCHA_SITE_KEY"]="captchaSiteKey" ["CONFIG_ANALYTICS_WRITE_KEY"]="analyticsWriteKey" ["CONFIG_OFFLINE"]="offline" ["CONFIG_EMAIL_ENABLED"]="emailServicesEnabled" diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index c1755a9d5a..bbdd646f39 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -75,7 +75,7 @@ "resetDone": "If you have correctly entered your email or username, a password reset link has been sent to your email address.", "backToLogin": "Back To Login" }, - "turnstile": { + "captcha": { "error": "Page verification failed or expired. Please refresh the page." }, "speakerMenu": { diff --git a/src/backend/index.ts b/src/backend/index.ts index 83cc9f7bf6..b5d9dcb773 100644 --- a/src/backend/index.ts +++ b/src/backend/index.ts @@ -612,7 +612,7 @@ export async function getProgressEstimationLineChartRoot( /* UserController.cs */ -export async function validateTurnstile(token: string): Promise { +export async function validateCaptcha(token: string): Promise { return await userApi .validateTurnstile({ token }) .then(() => true) diff --git a/src/components/Login/Turnstile.tsx b/src/components/Login/Captcha.tsx similarity index 62% rename from src/components/Login/Turnstile.tsx rename to src/components/Login/Captcha.tsx index 008ac3d73c..126e1c4e40 100644 --- a/src/components/Login/Turnstile.tsx +++ b/src/components/Login/Captcha.tsx @@ -1,21 +1,21 @@ -import { Turnstile as MarsiTurnstile } from "@marsidev/react-turnstile"; +import { Turnstile } from "@marsidev/react-turnstile"; import { Fragment, type ReactElement, useEffect, useRef } from "react"; import { useTranslation } from "react-i18next"; import { toast } from "react-toastify"; -import { validateTurnstile } from "backend"; +import { validateCaptcha } from "backend"; import i18n from "i18n"; import { RuntimeConfig } from "types/runtimeConfig"; -export interface TurnstileProps { - /** Parent function to call when Turnstile succeeds or fails. */ +export interface CaptchaProps { + /** Parent function to call when CAPTCHA succeeds or fails. */ setSuccess: (success: boolean) => void; } /** Component wrapper for Cloudflare Turnstile (CAPTCHA replacement). */ -export default function Turnstile(props: TurnstileProps): ReactElement { +export default function Captcha(props: CaptchaProps): ReactElement { const setSuccess = props.setSuccess; - const isRequired = useRef(RuntimeConfig.getInstance().turnstileRequired()); + const isRequired = useRef(RuntimeConfig.getInstance().captchaRequired()); const { t } = useTranslation(); useEffect(() => { @@ -24,23 +24,25 @@ export default function Turnstile(props: TurnstileProps): ReactElement { const siteKey = process.env.NODE_ENV === "production" - ? RuntimeConfig.getInstance().turnstileSiteKey() + ? RuntimeConfig.getInstance().captchaSiteKey() : // https://developers.cloudflare.com/turnstile/troubleshooting/testing/ + // has dummy site keys for development and testing; options are + // invisible pass and fail, visible pass and fail, and forced interaction "1x00000000000000000000AA"; // visible pass const fail = (): void => { setSuccess(false); - toast.error(t("turnstile.error")); + toast.error(t("captcha.error")); }; const succeed = (): void => { setSuccess(true); }; const validate = (token: string): void => { - validateTurnstile(token).then((isValid) => (isValid ? succeed() : fail())); + validateCaptcha(token).then((isValid) => (isValid ? succeed() : fail())); }; return isRequired.current ? ( - )} - + {/* User Guide, Sign Up, and Log In buttons */} diff --git a/src/components/Login/Signup.tsx b/src/components/Login/Signup.tsx index a46cbe01ec..b25c6cc015 100644 --- a/src/components/Login/Signup.tsx +++ b/src/components/Login/Signup.tsx @@ -17,9 +17,9 @@ import { import { useTranslation } from "react-i18next"; import { LoadingDoneButton } from "components/Buttons"; +import Captcha from "components/Login/Captcha"; import { asyncSignUp } from "components/Login/Redux/LoginActions"; import { LoginStatus } from "components/Login/Redux/LoginReduxTypes"; -import Turnstile from "components/Login/Turnstile"; import { reset } from "rootRedux/actions"; import { useAppDispatch, useAppSelector } from "rootRedux/hooks"; import { type StoreState } from "rootRedux/types"; @@ -254,7 +254,7 @@ export default function Signup(props: SignupProps): ReactElement { )} - + {/* Sign Up and Log In buttons */} diff --git a/src/components/Login/tests/Login.test.tsx b/src/components/Login/tests/Login.test.tsx index e47334557b..03c89992fb 100644 --- a/src/components/Login/tests/Login.test.tsx +++ b/src/components/Login/tests/Login.test.tsx @@ -13,10 +13,10 @@ import { defaultState as loginState } from "components/Login/Redux/LoginReduxTyp jest.mock("backend", () => ({ getBannerText: () => Promise.resolve(""), })); +jest.mock("components/Login/Captcha", () => "div"); jest.mock("components/Login/Redux/LoginActions", () => ({ asyncLogIn: (...args: any[]) => mockAsyncLogIn(...args), })); -jest.mock("components/Login/Turnstile", () => "div"); jest.mock("rootRedux/hooks", () => { return { ...jest.requireActual("rootRedux/hooks"), diff --git a/src/components/Login/tests/MockCaptcha.tsx b/src/components/Login/tests/MockCaptcha.tsx new file mode 100644 index 0000000000..aa3dbc89e8 --- /dev/null +++ b/src/components/Login/tests/MockCaptcha.tsx @@ -0,0 +1,11 @@ +import { type ReactElement, useEffect } from "react"; + +import { type CaptchaProps } from "components/Login/Captcha"; + +/** Mock CAPTCHA component that automatically succeeds. */ +export default function MockCaptcha(props: CaptchaProps): ReactElement { + useEffect(() => { + props.setSuccess(true); + }, [props]); + return
; +} diff --git a/src/components/Login/tests/MockTurnstile.tsx b/src/components/Login/tests/MockTurnstile.tsx deleted file mode 100644 index 68777dbbda..0000000000 --- a/src/components/Login/tests/MockTurnstile.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { type ReactElement, useEffect } from "react"; - -import { type TurnstileProps } from "components/Login/Turnstile"; - -/** Mock Turnstile component that automatically succeeds. */ -export default function MockTurnstile(props: TurnstileProps): ReactElement { - useEffect(() => { - props.setSuccess(true); - }, [props]); - return
; -} diff --git a/src/components/Login/tests/Signup.test.tsx b/src/components/Login/tests/Signup.test.tsx index 9485db4802..45d53bada6 100644 --- a/src/components/Login/tests/Signup.test.tsx +++ b/src/components/Login/tests/Signup.test.tsx @@ -14,10 +14,10 @@ import Signup, { SignupId } from "components/Login/Signup"; jest.mock("backend", () => ({ getBannerText: () => Promise.resolve(""), })); +jest.mock("components/Login/Captcha", () => "div"); jest.mock("components/Login/Redux/LoginActions", () => ({ asyncSignUp: (...args: any[]) => mockAsyncSignUp(...args), })); -jest.mock("components/Login/Turnstile", () => "div"); jest.mock("rootRedux/hooks", () => { return { ...jest.requireActual("rootRedux/hooks"), diff --git a/src/components/PasswordReset/Request.tsx b/src/components/PasswordReset/Request.tsx index 6924a4f346..763b0eb7f0 100644 --- a/src/components/PasswordReset/Request.tsx +++ b/src/components/PasswordReset/Request.tsx @@ -5,7 +5,7 @@ import { useNavigate } from "react-router-dom"; import { resetPasswordRequest } from "backend"; import { LoadingDoneButton } from "components/Buttons"; -import Turnstile from "components/Login/Turnstile"; +import Captcha from "components/Login/Captcha"; import { Path } from "types/path"; export enum PasswordRequestIds { @@ -83,7 +83,7 @@ export default function ResetRequest(): ReactElement { /> - + ({ jest.mock("components/Buttons", () => ({ LoadingDoneButton: MockBypassLoadableButton, })); -jest.mock("components/Login/Turnstile", () => MockTurnstile); +jest.mock("components/Login/Captcha", () => MockCaptcha); const mockResetPasswordRequest = jest.fn(); diff --git a/src/types/runtimeConfig.ts b/src/types/runtimeConfig.ts index afe2540f8f..27369c2b6e 100644 --- a/src/types/runtimeConfig.ts +++ b/src/types/runtimeConfig.ts @@ -1,7 +1,7 @@ interface RuntimeConfigItems { baseUrl: string; - turnstileRequired: boolean; - turnstileSiteKey: string; + captchaRequired: boolean; + captchaSiteKey: string; offline: boolean; emailServicesEnabled: boolean; showCertExpiration: boolean; @@ -16,8 +16,8 @@ declare global { const defaultConfig: RuntimeConfigItems = { baseUrl: "http://localhost:5000", - turnstileRequired: true, - turnstileSiteKey: "0x4AAAAAAAe9zmM2ysXGSJk1", + captchaRequired: true, + captchaSiteKey: "0x4AAAAAAAe9zmM2ysXGSJk1", offline: false, emailServicesEnabled: true, showCertExpiration: true, @@ -56,18 +56,18 @@ export class RuntimeConfig { return "v0.0.0-default.0"; } - public turnstileRequired(): boolean { - if (window.runtimeConfig.hasOwnProperty("turnstileRequired")) { - return window.runtimeConfig.turnstileRequired; + public captchaRequired(): boolean { + if (window.runtimeConfig.hasOwnProperty("captchaRequired")) { + return window.runtimeConfig.captchaRequired; } - return defaultConfig.turnstileRequired; + return defaultConfig.captchaRequired; } - public turnstileSiteKey(): string { - if (window.runtimeConfig.hasOwnProperty("turnstileSiteKey")) { - return window.runtimeConfig.turnstileSiteKey; + public captchaSiteKey(): string { + if (window.runtimeConfig.hasOwnProperty("captchaSiteKey")) { + return window.runtimeConfig.captchaSiteKey; } - return defaultConfig.turnstileSiteKey; + return defaultConfig.captchaSiteKey; } public emailServicesEnabled(): boolean { From caeccde47ed7f37e627247d5261747710cb503a2 Mon Sep 17 00:00:00 2001 From: Danny Rorabaugh Date: Fri, 19 Jul 2024 11:45:37 -0400 Subject: [PATCH 14/24] Unify frontend and backend captcha/email configs --- .../Controllers/UserControllerTests.cs | 2 +- ...leServiceMock.cs => CaptchaServiceMock.cs} | 2 +- Backend/Contexts/CaptchaContext.cs | 21 +++++++++++ Backend/Contexts/TurnstileContext.cs | 21 ----------- Backend/Controllers/UserController.cs | 14 ++++---- Backend/Interfaces/ICaptchaContext.cs | 9 +++++ ...TurnstileService.cs => ICaptchaService.cs} | 2 +- Backend/Interfaces/ITurnstileContext.cs | 9 ----- Backend/Properties/launchSettings.json | 12 +++---- ...{TurnstileService.cs => CaptchaService.cs} | 14 ++++---- Backend/Startup.cs | 36 +++++++++---------- README.md | 6 ++-- .../backend/templates/backend-config-map.yaml | 14 ++++++-- .../backend/templates/backend-secrets.yaml | 2 +- .../backend/templates/deployment-backend.yaml | 12 +++---- .../thecombine/charts/backend/values.yaml | 11 +++--- .../templates/env-frontend-configmap.yaml | 4 +-- .../thecombine/charts/frontend/values.yaml | 6 ++-- deploy/helm/thecombine/values.yaml | 6 ++-- deploy/scripts/install-combine.sh | 2 +- .../scripts/setup_files/combine_config.yaml | 14 ++++---- .../scripts/setup_files/profiles/desktop.yaml | 4 +-- deploy/scripts/setup_files/profiles/dev.yaml | 2 +- deploy/scripts/setup_files/profiles/nuc.yaml | 4 +-- .../scripts/setup_files/profiles/nuc_qa.yaml | 4 +-- src/api/api/user-api.ts | 30 ++++++++-------- src/api/models/sense.ts | 12 +++---- src/backend/index.ts | 6 ++-- src/components/Login/Captcha.tsx | 10 +++--- 29 files changed, 153 insertions(+), 138 deletions(-) rename Backend.Tests/Mocks/{TurnstileServiceMock.cs => CaptchaServiceMock.cs} (77%) create mode 100644 Backend/Contexts/CaptchaContext.cs delete mode 100644 Backend/Contexts/TurnstileContext.cs create mode 100644 Backend/Interfaces/ICaptchaContext.cs rename Backend/Interfaces/{ITurnstileService.cs => ICaptchaService.cs} (77%) delete mode 100644 Backend/Interfaces/ITurnstileContext.cs rename Backend/Services/{TurnstileService.cs => CaptchaService.cs} (71%) diff --git a/Backend.Tests/Controllers/UserControllerTests.cs b/Backend.Tests/Controllers/UserControllerTests.cs index 4f01e37484..4906558a86 100644 --- a/Backend.Tests/Controllers/UserControllerTests.cs +++ b/Backend.Tests/Controllers/UserControllerTests.cs @@ -35,7 +35,7 @@ public void Setup() _userRepo = new UserRepositoryMock(); _permissionService = new PermissionServiceMock(_userRepo); _userController = new UserController(_userRepo, _permissionService, - new EmailServiceMock(), new PasswordResetServiceMock(), new TurnstileServiceMock()); + new CaptchaServiceMock(), new EmailServiceMock(), new PasswordResetServiceMock()); } private static User RandomUser() diff --git a/Backend.Tests/Mocks/TurnstileServiceMock.cs b/Backend.Tests/Mocks/CaptchaServiceMock.cs similarity index 77% rename from Backend.Tests/Mocks/TurnstileServiceMock.cs rename to Backend.Tests/Mocks/CaptchaServiceMock.cs index 76b8cb4c61..06095f2814 100644 --- a/Backend.Tests/Mocks/TurnstileServiceMock.cs +++ b/Backend.Tests/Mocks/CaptchaServiceMock.cs @@ -3,7 +3,7 @@ namespace Backend.Tests.Mocks { - sealed internal class TurnstileServiceMock : ITurnstileService + sealed internal class CaptchaServiceMock : ICaptchaService { public Task VerifyToken(string token) { diff --git a/Backend/Contexts/CaptchaContext.cs b/Backend/Contexts/CaptchaContext.cs new file mode 100644 index 0000000000..f262e30000 --- /dev/null +++ b/Backend/Contexts/CaptchaContext.cs @@ -0,0 +1,21 @@ +using System.Diagnostics.CodeAnalysis; +using BackendFramework.Interfaces; +using Microsoft.Extensions.Options; + +namespace BackendFramework.Contexts +{ + [ExcludeFromCodeCoverage] + public class CaptchaContext : ICaptchaContext + { + public bool CaptchaEnabled { get; } + public string? CaptchaSecretKey { get; } + public string? CaptchaVerifyUrl { get; } + + public CaptchaContext(IOptions options) + { + CaptchaEnabled = options.Value.CaptchaEnabled; + CaptchaSecretKey = options.Value.CaptchaSecretKey; + CaptchaVerifyUrl = options.Value.CaptchaVerifyUrl; + } + } +} diff --git a/Backend/Contexts/TurnstileContext.cs b/Backend/Contexts/TurnstileContext.cs deleted file mode 100644 index 0cceb6c34d..0000000000 --- a/Backend/Contexts/TurnstileContext.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using BackendFramework.Interfaces; -using Microsoft.Extensions.Options; - -namespace BackendFramework.Contexts -{ - [ExcludeFromCodeCoverage] - public class TurnstileContext : ITurnstileContext - { - public bool TurnstileEnabled { get; } - public string? TurnstileSecretKey { get; } - public string? TurnstileVerifyUrl { get; } - - public TurnstileContext(IOptions options) - { - TurnstileEnabled = options.Value.TurnstileEnabled; - TurnstileSecretKey = options.Value.TurnstileSecretKey; - TurnstileVerifyUrl = options.Value.TurnstileVerifyUrl; - } - } -} diff --git a/Backend/Controllers/UserController.cs b/Backend/Controllers/UserController.cs index 64e6603fa7..32c9c3ce47 100644 --- a/Backend/Controllers/UserController.cs +++ b/Backend/Controllers/UserController.cs @@ -18,28 +18,28 @@ namespace BackendFramework.Controllers public class UserController : Controller { private readonly IUserRepository _userRepo; + private readonly ICaptchaService _captchaService; private readonly IEmailService _emailService; private readonly IPasswordResetService _passwordResetService; private readonly IPermissionService _permissionService; - private readonly ITurnstileService _turnstileService; public UserController(IUserRepository userRepo, IPermissionService permissionService, - IEmailService emailService, IPasswordResetService passwordResetService, ITurnstileService turnstileService) + ICaptchaService captchaService, IEmailService emailService, IPasswordResetService passwordResetService) { _userRepo = userRepo; + _captchaService = captchaService; _emailService = emailService; _passwordResetService = passwordResetService; _permissionService = permissionService; - _turnstileService = turnstileService; } - /// Validates a Cloudflare Turnstile token + /// Verifies a CAPTCHA token [AllowAnonymous] - [HttpGet("turnstile/{token}", Name = "ValidateTurnstile")] + [HttpGet("captcha/{token}", Name = "VerifyCaptchaToken")] [ProducesResponseType(StatusCodes.Status200OK)] - public async Task ValidateTurnstile(string token) + public async Task VerifyCaptchaToken(string token) { - return await _turnstileService.VerifyToken(token) ? Ok() : BadRequest(); + return await _captchaService.VerifyToken(token) ? Ok() : BadRequest(); } /// Sends a password reset request diff --git a/Backend/Interfaces/ICaptchaContext.cs b/Backend/Interfaces/ICaptchaContext.cs new file mode 100644 index 0000000000..742f8c123a --- /dev/null +++ b/Backend/Interfaces/ICaptchaContext.cs @@ -0,0 +1,9 @@ +namespace BackendFramework.Interfaces +{ + public interface ICaptchaContext + { + bool CaptchaEnabled { get; } + string? CaptchaSecretKey { get; } + string? CaptchaVerifyUrl { get; } + } +} diff --git a/Backend/Interfaces/ITurnstileService.cs b/Backend/Interfaces/ICaptchaService.cs similarity index 77% rename from Backend/Interfaces/ITurnstileService.cs rename to Backend/Interfaces/ICaptchaService.cs index 5cf5ade54a..efcb7bdc5a 100644 --- a/Backend/Interfaces/ITurnstileService.cs +++ b/Backend/Interfaces/ICaptchaService.cs @@ -2,7 +2,7 @@ namespace BackendFramework.Interfaces { - public interface ITurnstileService + public interface ICaptchaService { Task VerifyToken(string token); } diff --git a/Backend/Interfaces/ITurnstileContext.cs b/Backend/Interfaces/ITurnstileContext.cs deleted file mode 100644 index f386d5d27c..0000000000 --- a/Backend/Interfaces/ITurnstileContext.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace BackendFramework.Interfaces -{ - public interface ITurnstileContext - { - bool TurnstileEnabled { get; } - string? TurnstileSecretKey { get; } - string? TurnstileVerifyUrl { get; } - } -} diff --git a/Backend/Properties/launchSettings.json b/Backend/Properties/launchSettings.json index 72309a8875..f4388940e3 100644 --- a/Backend/Properties/launchSettings.json +++ b/Backend/Properties/launchSettings.json @@ -16,9 +16,9 @@ "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development", "COMBINE_JWT_SECRET_KEY": "0123456789abcdefghijklmnopqrstuvwxyz", - "TURNSTILE_ENABLED": "True", - "TURNSTILE_SECRET_KEY": "1x0000000000000000000000000000000AA", - "TURNSTILE_VERIFY_URL": "https://challenges.cloudflare.com/turnstile/v0/siteverify" + "COMBINE_CAPTCHA_REQUIRED": "True", + "COMBINE_CAPTCHA_SECRET_KEY": "1x0000000000000000000000000000000AA", + "COMBINE_CAPTCHA_VERIFY_URL": "https://challenges.cloudflare.com/turnstile/v0/siteverify" } }, "BackendFramework": { @@ -29,9 +29,9 @@ "Key": "Value", "ASPNETCORE_ENVIRONMENT": "Development", "COMBINE_JWT_SECRET_KEY": "0123456789abcdefghijklmnopqrstuvwxyz", - "TURNSTILE_ENABLED": "True", - "TURNSTILE_SECRET_KEY": "1x0000000000000000000000000000000AA", - "TURNSTILE_VERIFY_URL": "https://challenges.cloudflare.com/turnstile/v0/siteverify" + "COMBINE_CAPTCHA_REQUIRED": "True", + "COMBINE_CAPTCHA_SECRET_KEY": "1x0000000000000000000000000000000AA", + "COMBINE_CAPTCHA_VERIFY_URL": "https://challenges.cloudflare.com/turnstile/v0/siteverify" }, "applicationUrl": "http://localhost:5000", "hotReloadProfile": "aspnetcore" diff --git a/Backend/Services/TurnstileService.cs b/Backend/Services/CaptchaService.cs similarity index 71% rename from Backend/Services/TurnstileService.cs rename to Backend/Services/CaptchaService.cs index 13f0f3b204..36dcc9900c 100644 --- a/Backend/Services/TurnstileService.cs +++ b/Backend/Services/CaptchaService.cs @@ -8,24 +8,24 @@ namespace BackendFramework.Services { [ExcludeFromCodeCoverage] - public class TurnstileService : ITurnstileService + public class CaptchaService : ICaptchaService { - private readonly ITurnstileContext _turnstileContext; + private readonly ICaptchaContext _captchaContext; - public TurnstileService(ITurnstileContext turnstileContext) + public CaptchaService(ICaptchaContext captchaContext) { - _turnstileContext = turnstileContext; + _captchaContext = captchaContext; } public async Task VerifyToken(string token) { - if (!_turnstileContext.TurnstileEnabled) + if (!_captchaContext.CaptchaEnabled) { throw new TurnstileNotEnabledException(); } - var secret = _turnstileContext.TurnstileSecretKey; - var verifyUrl = _turnstileContext.TurnstileVerifyUrl; + var secret = _captchaContext.CaptchaSecretKey; + var verifyUrl = _captchaContext.CaptchaVerifyUrl; if (string.IsNullOrEmpty(secret) || string.IsNullOrEmpty(verifyUrl)) { return false; diff --git a/Backend/Startup.cs b/Backend/Startup.cs index 6bda85c100..1eef1c1d71 100644 --- a/Backend/Startup.cs +++ b/Backend/Startup.cs @@ -49,9 +49,9 @@ public class Settings public string? SmtpAddress { get; set; } public string? SmtpFrom { get; set; } public int PassResetExpireTime { get; set; } - public bool TurnstileEnabled { get; set; } - public string? TurnstileSecretKey { get; set; } - public string? TurnstileVerifyUrl { get; set; } + public bool CaptchaEnabled { get; set; } + public string? CaptchaSecretKey { get; set; } + public string? CaptchaVerifyUrl { get; set; } public Settings() { @@ -59,7 +59,7 @@ public Settings() CombineDatabase = ""; EmailEnabled = false; PassResetExpireTime = DefaultPasswordResetExpireTime; - TurnstileEnabled = true; + CaptchaEnabled = true; } } @@ -198,20 +198,20 @@ public void ConfigureServices(IServiceCollection services) $"Using default value: {Settings.DefaultPasswordResetExpireTime}")!); - options.TurnstileEnabled = bool.Parse(CheckedEnvironmentVariable( - "TURNSTILE_ENABLED", + options.CaptchaEnabled = bool.Parse(CheckedEnvironmentVariable( + "COMBINE_CAPTCHA_REQUIRED", bool.TrueString, // "True" - "Turnstile should be explicitly enabled or disabled.")!); - if (options.TurnstileEnabled) + "CAPTCHA should be explicitly enabled or disabled.")!); + if (options.CaptchaEnabled) { - options.TurnstileSecretKey = CheckedEnvironmentVariable( - "TURNSTILE_SECRET_KEY", + options.CaptchaSecretKey = CheckedEnvironmentVariable( + "COMBINE_CAPTCHA_SECRET_KEY", null, - "Turnstile secret key required."); - options.TurnstileVerifyUrl = CheckedEnvironmentVariable( - "TURNSTILE_VERIFY_URL", + "CAPTCHA secret key required."); + options.CaptchaVerifyUrl = CheckedEnvironmentVariable( + "COMBINE_CAPTCHA_VERIFY_URL", null, - "Turnstile verification URL required."); + "CAPTCHA verification URL required."); } }); @@ -221,6 +221,10 @@ public void ConfigureServices(IServiceCollection services) services.AddTransient(); services.AddTransient(); + // CAPTCHA types + services.AddTransient(); + services.AddTransient(); + // Email types services.AddTransient(); services.AddTransient(); @@ -259,10 +263,6 @@ public void ConfigureServices(IServiceCollection services) // Statistics types services.AddSingleton(); - // Turnstile types - services.AddTransient(); - services.AddTransient(); - // User types services.AddTransient(); services.AddTransient(); diff --git a/README.md b/README.md index a774598649..2d89946688 100644 --- a/README.md +++ b/README.md @@ -687,9 +687,9 @@ Notes: ### Setup Environment Variables Before installing _The Combine_ in Kubernetes, you need to set the following environment variables: -`COMBINE_JWT_SECRET_KEY`, `TURNSTILE_ENABLED`, `TURNSTILE_SECRET_KEY`. For development environments, you can use the -values defined in `Backend/Properties/launchSettings.json`. Set them in your `.profile` (Linux or Mac 10.14-), your -`.zprofile` (Mac 10.15+), or the _System_ app (Windows). +`COMBINE_JWT_SECRET_KEY`, `COMBINE_CAPTCHA_REQUIRED`, `COMBINE_CAPTCHA_SECRET_KEY`. For development environments, you +can use the values defined in `Backend/Properties/launchSettings.json`. Set them in your `.profile` (Linux or Mac +10.14-), your `.zprofile` (Mac 10.15+), or the _System_ app (Windows). _Note: The following is optional for Development Environments._ diff --git a/deploy/helm/thecombine/charts/backend/templates/backend-config-map.yaml b/deploy/helm/thecombine/charts/backend/templates/backend-config-map.yaml index d30c9bf3f5..c59ebb0393 100644 --- a/deploy/helm/thecombine/charts/backend/templates/backend-config-map.yaml +++ b/deploy/helm/thecombine/charts/backend/templates/backend-config-map.yaml @@ -7,10 +7,18 @@ metadata: data: COMBINE_PASSWORD_RESET_EXPIRE_TIME: {{ .Values.combinePasswordResetTime | quote }} - COMBINE_EMAIL_ENABLED: {{ .Values.combineEmailEnabled | quote }} + {{- if .Values.global.emailEnabled }} + COMBINE_EMAIL_ENABLED: "True" + {{- else }} + COMBINE_EMAIL_ENABLED: "False" + {{- end }} COMBINE_SMTP_ADDRESS: {{ .Values.combineSmtpAddress | quote }} COMBINE_SMTP_FROM: {{ .Values.combineSmtpFrom | quote }} COMBINE_SMTP_PORT: {{ .Values.combineSmtpPort | quote }} COMBINE_SMTP_SERVER: {{ .Values.combineSmtpServer | quote }} - TURNSTILE_ENABLED: {{ .Values.turnstileEnabled | quote }} - TURNSTILE_VERIFY_URL: {{ .Values.turnstileVerifyUrl | quote }} + {{- if .Values.global.captchRequired }} + COMBINE_CAPTCHA_REQUIRED: "True" + {{- else }} + COMBINE_CAPTCHA_REQUIRED: "False" + {{- end }} + COMBINE_CAPTCHA_VERIFY_URL: {{ .Values.captchaVerifyUrl | quote }} diff --git a/deploy/helm/thecombine/charts/backend/templates/backend-secrets.yaml b/deploy/helm/thecombine/charts/backend/templates/backend-secrets.yaml index 5a5686358b..367e8dab26 100644 --- a/deploy/helm/thecombine/charts/backend/templates/backend-secrets.yaml +++ b/deploy/helm/thecombine/charts/backend/templates/backend-secrets.yaml @@ -9,4 +9,4 @@ data: COMBINE_JWT_SECRET_KEY: {{ .Values.global.combineJwtSecretKey | b64enc }} COMBINE_SMTP_USERNAME: {{ .Values.global.combineSmtpUsername | b64enc }} COMBINE_SMTP_PASSWORD: {{ .Values.global.combineSmtpPassword | b64enc }} - TURNSTILE_SECRET_KEY: {{ .Values.global.turnstileSecretKey | b64enc }} + COMBINE_CAPTCHA_SECRET_KEY: {{ .Values.global.captchaSecretKey | b64enc }} diff --git a/deploy/helm/thecombine/charts/backend/templates/deployment-backend.yaml b/deploy/helm/thecombine/charts/backend/templates/deployment-backend.yaml index 193d999e84..44f1227931 100644 --- a/deploy/helm/thecombine/charts/backend/templates/deployment-backend.yaml +++ b/deploy/helm/thecombine/charts/backend/templates/deployment-backend.yaml @@ -74,20 +74,20 @@ spec: secretKeyRef: key: COMBINE_SMTP_USERNAME name: env-backend-secrets - - name: TURNSTILE_ENABLED + - name: COMBINE_CAPTCHA_REQUIRED valueFrom: configMapKeyRef: - key: TURNSTILE_ENABLED + key: COMBINE_CAPTCHA_REQUIRED name: env-backend - - name: TURNSTILE_SECRET_KEY + - name: COMBINE_CAPTCHA_SECRET_KEY valueFrom: secretKeyRef: - key: TURNSTILE_SECRET_KEY + key: COMBINE_CAPTCHA_SECRET_KEY name: env-backend-secrets - - name: TURNSTILE_VERIFY_URL + - name: COMBINE_CAPTCHA_VERIFY_URL valueFrom: configMapKeyRef: - key: TURNSTILE_VERIFY_URL + key: COMBINE_CAPTCHA_VERIFY_URL name: env-backend ports: - containerPort: 5000 diff --git a/deploy/helm/thecombine/charts/backend/values.yaml b/deploy/helm/thecombine/charts/backend/values.yaml index 3e7c890f82..fd300bbcb1 100644 --- a/deploy/helm/thecombine/charts/backend/values.yaml +++ b/deploy/helm/thecombine/charts/backend/values.yaml @@ -26,16 +26,17 @@ global: imageTag: "latest" # Define the image registry to use (may be blank for local images) imageRegistry: "" - # Cloudflare Turnstile verification - turnstileSecretKey: "Override" + # Email services + emailEnabled: true + # CAPTCHA verification + captchaSecretKey: "Override" persistentVolumeSize: 32Gi combinePasswordResetTime: 15 -combineEmailEnabled: "True" combineSmtpAddress: no-reply@thecombine.app combineSmtpFrom: "The Combine" combineSmtpPort: 587 combineSmtpServer: "email-smtp.us-east-1.amazonaws.com" imageName: combine_backend -turnstileEnabled: "True" -turnstileVerifyUrl: "https://challenges.cloudflare.com/turnstile/v0/siteverify" +captchaRequired: true +captchaVerifyUrl: "https://challenges.cloudflare.com/turnstile/v0/siteverify" diff --git a/deploy/helm/thecombine/charts/frontend/templates/env-frontend-configmap.yaml b/deploy/helm/thecombine/charts/frontend/templates/env-frontend-configmap.yaml index bf92b19546..f3a8cdbd27 100644 --- a/deploy/helm/thecombine/charts/frontend/templates/env-frontend-configmap.yaml +++ b/deploy/helm/thecombine/charts/frontend/templates/env-frontend-configmap.yaml @@ -8,10 +8,10 @@ data: SERVER_NAME: {{ .Values.global.serverName }} CERT_ADDL_DOMAINS: {{ .Values.combineAddlDomainList | quote }} CONFIG_USE_CONNECTION_URL: "true" - CONFIG_CAPTCHA_REQUIRED: {{ .Values.configCaptchaRequired | quote }} + CONFIG_CAPTCHA_REQUIRED: {{ .Values.global.captchaRequired | quote }} CONFIG_CAPTCHA_SITE_KEY: {{ .Values.configCaptchaSiteKey | quote }} CONFIG_OFFLINE: {{ .Values.configOffline | quote }} - CONFIG_EMAIL_ENABLED: {{ and .Values.configEmailEnabled (eq .Values.global.combineEmailEnabled "True") | quote }} + CONFIG_EMAIL_ENABLED: {{ .Values.global.emailEnabled quote }} CONFIG_SHOW_CERT_EXPIRATION: {{ .Values.configShowCertExpiration | quote }} {{- if .Values.configAnalyticsWriteKey }} CONFIG_ANALYTICS_WRITE_KEY: {{ .Values.configAnalyticsWriteKey | quote }} diff --git a/deploy/helm/thecombine/charts/frontend/values.yaml b/deploy/helm/thecombine/charts/frontend/values.yaml index 145e78a047..a3ed2cf0e8 100644 --- a/deploy/helm/thecombine/charts/frontend/values.yaml +++ b/deploy/helm/thecombine/charts/frontend/values.yaml @@ -13,14 +13,16 @@ global: imageTag: "latest" # Define the image registry to use (may be blank for local images) imageRegistry: "" + # Email services + emailEnabled: "true" + # CAPTCHA verification + captchaRequired: "true" imageName: combine_frontend # The additional domain list is a space-separated string list of domains combineAddlDomainList: "" -configCaptchaRequired: "false" configCaptchaSiteKey: "None - from frontend chart" configOffline: "false" -configEmailEnabled: "true" configShowCertExpiration: "false" configAnalyticsWriteKey: "" diff --git a/deploy/helm/thecombine/values.yaml b/deploy/helm/thecombine/values.yaml index 9c8ac81895..e1d82401de 100644 --- a/deploy/helm/thecombine/values.yaml +++ b/deploy/helm/thecombine/values.yaml @@ -38,8 +38,9 @@ global: pullSecretName: aws-login-credentials # Update strategy should be "Recreate" or "Rolling Update" updateStrategy: Recreate - # Cloudflare Turnstile verification - turnstileSecretKey: "Override" + # CAPTCHA verification + captchaRequired: false + captchaSecretKey: "Override" includeResourceLimits: false @@ -56,7 +57,6 @@ certManager: frontend: configShowCertExpiration: false configAnalyticsWriteKey: "" - configCaptchaRequired: false configCaptchaSiteKey: "None" # Maintenance configuration items diff --git a/deploy/scripts/install-combine.sh b/deploy/scripts/install-combine.sh index 0091f41961..a923ddfa35 100755 --- a/deploy/scripts/install-combine.sh +++ b/deploy/scripts/install-combine.sh @@ -18,7 +18,7 @@ set-combine-env () { export AWS_ACCESS_KEY_ID="${AWS_ACCESS_KEY_ID}" export AWS_SECRET_ACCESS_KEY="${AWS_SECRET_ACCESS_KEY}" export COMBINE_EMAIL_ENABLED="False" - export COMBINE_TURNSTILE_ENABLED="False" + export COMBINE_CAPTCHA_REQUIRED="False" .EOF chmod 600 ${CONFIG_DIR}/env fi diff --git a/deploy/scripts/setup_files/combine_config.yaml b/deploy/scripts/setup_files/combine_config.yaml index ceab14facc..cd30499f8e 100644 --- a/deploy/scripts/setup_files/combine_config.yaml +++ b/deploy/scripts/setup_files/combine_config.yaml @@ -79,9 +79,10 @@ targets: thecombine: global: serverName: qa-kube.thecombine.app + emailEnabled: true + captchaRequired: true frontend: - configCaptchaRequired: "true" - configCaptchaSiteKey: "0x4AAAAAAAe9zmM2ysXGSJk1" + configCaptchaSiteKey: "0x4AAAAAAAe9zmM2ysXGSJk1" # Turnstile site key prod: profile: prod env_vars_required: true @@ -90,9 +91,10 @@ targets: thecombine: global: serverName: thecombine.app + emailEnabled: true + captchaRequired: true frontend: - configCaptchaRequired: "true" - configCaptchaSiteKey: "0x4AAAAAAAe9zmM2ysXGSJk1" + configCaptchaSiteKey: "0x4AAAAAAAe9zmM2ysXGSJk1" # Turnstile site key # Set of profiles # Each key of 'profiles' defines one of the profiles used by the set of targets. @@ -156,8 +158,8 @@ charts: env_var: COMBINE_SMTP_USERNAME - config_item: combineSmtpPassword env_var: COMBINE_SMTP_PASSWORD - - config_item: turnstileSecretKey - env_var: TURNSTILE_SECRET_KEY + - config_item: captchaSecretKey + env_var: CAPTCHA_SECRET_KEY create-admin-user: namespace: thecombine install_langs: false diff --git a/deploy/scripts/setup_files/profiles/desktop.yaml b/deploy/scripts/setup_files/profiles/desktop.yaml index b19dc37313..67572bd545 100644 --- a/deploy/scripts/setup_files/profiles/desktop.yaml +++ b/deploy/scripts/setup_files/profiles/desktop.yaml @@ -13,12 +13,12 @@ charts: enabled: false global: awsS3Location: local.thecombine.app - combineEmailEnabled: "False" + emailEnabled: false + captchaRequired: false imagePullPolicy: IfNotPresent pullSecretName: None frontend: configOffline: true - configEmailEnabled: false maintenance: localLangList: - "ar" diff --git a/deploy/scripts/setup_files/profiles/dev.yaml b/deploy/scripts/setup_files/profiles/dev.yaml index 5462380da3..0fa22d737f 100644 --- a/deploy/scripts/setup_files/profiles/dev.yaml +++ b/deploy/scripts/setup_files/profiles/dev.yaml @@ -11,7 +11,6 @@ charts: enabled: false frontend: - configCaptchaRequired: "true" # https://developers.cloudflare.com/turnstile/troubleshooting/testing/ # has dummy secret keys for development and testing; options are # invisible pass and fail, visible pass and fail, and forced interaction @@ -22,6 +21,7 @@ charts: imagePullPolicy: Never includeResourceLimits: false awsS3Location: dev.thecombine.app + captchaRequired: true ingressClass: nginx imagePullPolicy: Never diff --git a/deploy/scripts/setup_files/profiles/nuc.yaml b/deploy/scripts/setup_files/profiles/nuc.yaml index 2d4bbba15b..6a9bec25ca 100644 --- a/deploy/scripts/setup_files/profiles/nuc.yaml +++ b/deploy/scripts/setup_files/profiles/nuc.yaml @@ -13,12 +13,12 @@ charts: enabled: false global: awsS3Location: prod.thecombine.app - combineEmailEnabled: "False" + captchaRequired: false + emailEnabled: false imagePullPolicy: IfNotPresent pullSecretName: None frontend: configOffline: true - configEmailEnabled: false maintenance: localLangList: - "ar" diff --git a/deploy/scripts/setup_files/profiles/nuc_qa.yaml b/deploy/scripts/setup_files/profiles/nuc_qa.yaml index c08d62bed6..05f2f5bee4 100644 --- a/deploy/scripts/setup_files/profiles/nuc_qa.yaml +++ b/deploy/scripts/setup_files/profiles/nuc_qa.yaml @@ -11,11 +11,11 @@ charts: enabled: true global: awsS3Location: prod.thecombine.app - combineEmailEnabled: "False" + captchaRequired: false + emailEnabled: false imagePullPolicy: Always frontend: configOffline: true - configEmailEnabled: false maintenance: localLangList: - "ar" diff --git a/src/api/api/user-api.ts b/src/api/api/user-api.ts index cf68b089c2..684e936992 100644 --- a/src/api/api/user-api.ts +++ b/src/api/api/user-api.ts @@ -612,13 +612,13 @@ export const UserApiAxiosParamCreator = function ( * @param {*} [options] Override http request option. * @throws {RequiredError} */ - validateTurnstile: async ( + verifyCaptchaToken: async ( token: string, options: any = {} ): Promise => { // verify required parameter 'token' is not null or undefined - assertParamExists("validateTurnstile", "token", token); - const localVarPath = `/v1/users/turnstile/{token}`.replace( + assertParamExists("verifyCaptchaToken", "token", token); + const localVarPath = `/v1/users/captcha/{token}`.replace( `{${"token"}}`, encodeURIComponent(String(token)) ); @@ -935,14 +935,14 @@ export const UserApiFp = function (configuration?: Configuration) { * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async validateTurnstile( + async verifyCaptchaToken( token: string, options?: any ): Promise< (axios?: AxiosInstance, basePath?: string) => AxiosPromise > { const localVarAxiosArgs = - await localVarAxiosParamCreator.validateTurnstile(token, options); + await localVarAxiosParamCreator.verifyCaptchaToken(token, options); return createRequestFunction( localVarAxiosArgs, globalAxios, @@ -1111,9 +1111,9 @@ export const UserApiFactory = function ( * @param {*} [options] Override http request option. * @throws {RequiredError} */ - validateTurnstile(token: string, options?: any): AxiosPromise { + verifyCaptchaToken(token: string, options?: any): AxiosPromise { return localVarFp - .validateTurnstile(token, options) + .verifyCaptchaToken(token, options) .then((request) => request(axios, basePath)); }, }; @@ -1267,15 +1267,15 @@ export interface UserApiValidateResetTokenRequest { } /** - * Request parameters for validateTurnstile operation in UserApi. + * Request parameters for verifyCaptchaToken operation in UserApi. * @export - * @interface UserApiValidateTurnstileRequest + * @interface UserApiVerifyCaptchaTokenRequest */ -export interface UserApiValidateTurnstileRequest { +export interface UserApiVerifyCaptchaTokenRequest { /** * * @type {string} - * @memberof UserApiValidateTurnstile + * @memberof UserApiVerifyCaptchaToken */ readonly token: string; } @@ -1470,17 +1470,17 @@ export class UserApi extends BaseAPI { /** * - * @param {UserApiValidateTurnstileRequest} requestParameters Request parameters. + * @param {UserApiVerifyCaptchaTokenRequest} requestParameters Request parameters. * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof UserApi */ - public validateTurnstile( - requestParameters: UserApiValidateTurnstileRequest, + public verifyCaptchaToken( + requestParameters: UserApiVerifyCaptchaTokenRequest, options?: any ) { return UserApiFp(this.configuration) - .validateTurnstile(requestParameters.token, options) + .verifyCaptchaToken(requestParameters.token, options) .then((request) => request(this.axios, this.basePath)); } } diff --git a/src/api/models/sense.ts b/src/api/models/sense.ts index 622cd09cf5..dc10c528dc 100644 --- a/src/api/models/sense.ts +++ b/src/api/models/sense.ts @@ -43,12 +43,6 @@ export interface Sense { * @memberof Sense */ grammaticalInfo: GrammaticalInfo; - /** - * - * @type {Array} - * @memberof Sense - */ - protectReasons?: Array | null; /** * * @type {Array} @@ -61,6 +55,12 @@ export interface Sense { * @memberof Sense */ glosses: Array; + /** + * + * @type {Array} + * @memberof Sense + */ + protectReasons?: Array | null; /** * * @type {Array} diff --git a/src/backend/index.ts b/src/backend/index.ts index b5d9dcb773..539a97d096 100644 --- a/src/backend/index.ts +++ b/src/backend/index.ts @@ -45,7 +45,7 @@ const config = new Api.Configuration(config_parameters); * and the blanket error pop ups should be suppressed.*/ const whiteListedErrorUrls = [ "users/authenticate", - "users/turnstile", + "users/captcha", "/speakers/create/", "/speakers/update/", ]; @@ -612,9 +612,9 @@ export async function getProgressEstimationLineChartRoot( /* UserController.cs */ -export async function validateCaptcha(token: string): Promise { +export async function verifyCaptchaToken(token: string): Promise { return await userApi - .validateTurnstile({ token }) + .verifyCaptchaToken({ token }) .then(() => true) .catch(() => false); } diff --git a/src/components/Login/Captcha.tsx b/src/components/Login/Captcha.tsx index 126e1c4e40..cf070cdfae 100644 --- a/src/components/Login/Captcha.tsx +++ b/src/components/Login/Captcha.tsx @@ -3,7 +3,7 @@ import { Fragment, type ReactElement, useEffect, useRef } from "react"; import { useTranslation } from "react-i18next"; import { toast } from "react-toastify"; -import { validateCaptcha } from "backend"; +import { verifyCaptchaToken } from "backend"; import i18n from "i18n"; import { RuntimeConfig } from "types/runtimeConfig"; @@ -37,15 +37,17 @@ export default function Captcha(props: CaptchaProps): ReactElement { const succeed = (): void => { setSuccess(true); }; - const validate = (token: string): void => { - validateCaptcha(token).then((isValid) => (isValid ? succeed() : fail())); + const verify = (token: string): void => { + verifyCaptchaToken(token).then((isVerified) => + isVerified ? succeed() : fail() + ); }; return isRequired.current ? ( From cedee67843eb68a4d99d4dc1693951c40a5e4ee4 Mon Sep 17 00:00:00 2001 From: Danny Rorabaugh Date: Fri, 19 Jul 2024 12:15:04 -0400 Subject: [PATCH 15/24] Clean up loose ends --- Backend/Services/CaptchaService.cs | 4 ++-- Backend/Services/EmailService.cs | 4 ++-- deploy/helm/thecombine/charts/backend/values.yaml | 5 ++--- deploy/helm/thecombine/charts/frontend/values.yaml | 2 +- deploy/helm/thecombine/values.yaml | 1 - deploy/scripts/setup_files/combine_config.yaml | 10 +++++----- deploy/scripts/setup_files/profiles/desktop.yaml | 2 +- docs/deploy/README.md | 2 +- src/api/models/sense.ts | 12 ++++++------ src/components/Login/Captcha.tsx | 2 +- 10 files changed, 21 insertions(+), 23 deletions(-) diff --git a/Backend/Services/CaptchaService.cs b/Backend/Services/CaptchaService.cs index 36dcc9900c..ef3c361eb0 100644 --- a/Backend/Services/CaptchaService.cs +++ b/Backend/Services/CaptchaService.cs @@ -21,7 +21,7 @@ public async Task VerifyToken(string token) { if (!_captchaContext.CaptchaEnabled) { - throw new TurnstileNotEnabledException(); + throw new CaptchaNotEnabledException(); } var secret = _captchaContext.CaptchaSecretKey; @@ -39,6 +39,6 @@ public async Task VerifyToken(string token) return contentString.Contains("\"success\":true"); } - private sealed class TurnstileNotEnabledException : Exception { } + private sealed class CaptchaNotEnabledException : Exception { } } } diff --git a/Backend/Services/EmailService.cs b/Backend/Services/EmailService.cs index 84c29cbb42..38604021d1 100644 --- a/Backend/Services/EmailService.cs +++ b/Backend/Services/EmailService.cs @@ -1,8 +1,8 @@ +using System; using System.Diagnostics.CodeAnalysis; -using BackendFramework.Interfaces; using System.Threading.Tasks; +using BackendFramework.Interfaces; using MimeKit; -using System; namespace BackendFramework.Services { diff --git a/deploy/helm/thecombine/charts/backend/values.yaml b/deploy/helm/thecombine/charts/backend/values.yaml index fd300bbcb1..227a328fb9 100644 --- a/deploy/helm/thecombine/charts/backend/values.yaml +++ b/deploy/helm/thecombine/charts/backend/values.yaml @@ -19,6 +19,7 @@ global: awsDefaultRegion: "Override" pullSecretName: "None" combineJwtSecretKey: "Override" + emailEnabled: true combineSmtpUsername: "Override" combineSmtpPassword: "Override" # Values for pulling container image from image registry @@ -26,9 +27,8 @@ global: imageTag: "latest" # Define the image registry to use (may be blank for local images) imageRegistry: "" - # Email services - emailEnabled: true # CAPTCHA verification + captchaRequired: true captchaSecretKey: "Override" persistentVolumeSize: 32Gi @@ -38,5 +38,4 @@ combineSmtpFrom: "The Combine" combineSmtpPort: 587 combineSmtpServer: "email-smtp.us-east-1.amazonaws.com" imageName: combine_backend -captchaRequired: true captchaVerifyUrl: "https://challenges.cloudflare.com/turnstile/v0/siteverify" diff --git a/deploy/helm/thecombine/charts/frontend/values.yaml b/deploy/helm/thecombine/charts/frontend/values.yaml index a3ed2cf0e8..1fdf087270 100644 --- a/deploy/helm/thecombine/charts/frontend/values.yaml +++ b/deploy/helm/thecombine/charts/frontend/values.yaml @@ -16,7 +16,7 @@ global: # Email services emailEnabled: "true" # CAPTCHA verification - captchaRequired: "true" + captchaRequired: "false" imageName: combine_frontend diff --git a/deploy/helm/thecombine/values.yaml b/deploy/helm/thecombine/values.yaml index e1d82401de..0b61b9550d 100644 --- a/deploy/helm/thecombine/values.yaml +++ b/deploy/helm/thecombine/values.yaml @@ -26,7 +26,6 @@ global: combineJwtSecretKey: "Override" combineSmtpUsername: "Override" combineSmtpPassword: "Override" - offline: false # Local Storage for fonts fontStorageAccessMode: "ReadWriteOnce" fontStorageSize: 1Gi diff --git a/deploy/scripts/setup_files/combine_config.yaml b/deploy/scripts/setup_files/combine_config.yaml index cd30499f8e..80c4b03925 100644 --- a/deploy/scripts/setup_files/combine_config.yaml +++ b/deploy/scripts/setup_files/combine_config.yaml @@ -78,9 +78,9 @@ targets: # override values for 'thecombine' chart thecombine: global: - serverName: qa-kube.thecombine.app - emailEnabled: true captchaRequired: true + emailEnabled: true + serverName: qa-kube.thecombine.app frontend: configCaptchaSiteKey: "0x4AAAAAAAe9zmM2ysXGSJk1" # Turnstile site key prod: @@ -90,9 +90,9 @@ targets: # override values for 'thecombine' chart thecombine: global: - serverName: thecombine.app - emailEnabled: true captchaRequired: true + emailEnabled: true + serverName: thecombine.app frontend: configCaptchaSiteKey: "0x4AAAAAAAe9zmM2ysXGSJk1" # Turnstile site key @@ -159,7 +159,7 @@ charts: - config_item: combineSmtpPassword env_var: COMBINE_SMTP_PASSWORD - config_item: captchaSecretKey - env_var: CAPTCHA_SECRET_KEY + env_var: COMBINE_CAPTCHA_SECRET_KEY create-admin-user: namespace: thecombine install_langs: false diff --git a/deploy/scripts/setup_files/profiles/desktop.yaml b/deploy/scripts/setup_files/profiles/desktop.yaml index 67572bd545..b84396f6d3 100644 --- a/deploy/scripts/setup_files/profiles/desktop.yaml +++ b/deploy/scripts/setup_files/profiles/desktop.yaml @@ -13,8 +13,8 @@ charts: enabled: false global: awsS3Location: local.thecombine.app - emailEnabled: false captchaRequired: false + emailEnabled: false imagePullPolicy: IfNotPresent pullSecretName: None frontend: diff --git a/docs/deploy/README.md b/docs/deploy/README.md index 675548ad97..9252d0862a 100644 --- a/docs/deploy/README.md +++ b/docs/deploy/README.md @@ -291,7 +291,7 @@ The setup scripts require the following environment variables to be set: - COMBINE_ADMIN_USERNAME - COMBINE_ADMIN_PASSWORD - COMBINE_ADMIN_EMAIL -- TURNSTILE_SECRET_KEY +- COMBINE_CAPTCHA_SECRET_KEY You may also set the KUBECONFIG environment variable to the location of the `kubectl` configuration file. This is not necessary if the configuration file is at `${HOME}/.kube/config`. diff --git a/src/api/models/sense.ts b/src/api/models/sense.ts index dc10c528dc..622cd09cf5 100644 --- a/src/api/models/sense.ts +++ b/src/api/models/sense.ts @@ -45,22 +45,22 @@ export interface Sense { grammaticalInfo: GrammaticalInfo; /** * - * @type {Array} + * @type {Array} * @memberof Sense */ - definitions: Array; + protectReasons?: Array | null; /** * - * @type {Array} + * @type {Array} * @memberof Sense */ - glosses: Array; + definitions: Array; /** * - * @type {Array} + * @type {Array} * @memberof Sense */ - protectReasons?: Array | null; + glosses: Array; /** * * @type {Array} diff --git a/src/components/Login/Captcha.tsx b/src/components/Login/Captcha.tsx index cf070cdfae..46c42085f7 100644 --- a/src/components/Login/Captcha.tsx +++ b/src/components/Login/Captcha.tsx @@ -12,7 +12,7 @@ export interface CaptchaProps { setSuccess: (success: boolean) => void; } -/** Component wrapper for Cloudflare Turnstile (CAPTCHA replacement). */ +/** Component wrapper for CAPTCHA implementation. */ export default function Captcha(props: CaptchaProps): ReactElement { const setSuccess = props.setSuccess; const isRequired = useRef(RuntimeConfig.getInstance().captchaRequired()); From 7c30de3d894c97578af5b8d6d4fda04243ec8c35 Mon Sep 17 00:00:00 2001 From: Danny Rorabaugh Date: Fri, 19 Jul 2024 15:28:00 -0400 Subject: [PATCH 16/24] Clean up --- Backend/Properties/launchSettings.json | 8 ++-- Backend/Startup.cs | 47 +++++++++++-------- README.md | 2 +- .../backend/templates/backend-config-map.yaml | 22 ++++----- .../backend/templates/backend-secrets.yaml | 2 +- .../backend/templates/deployment-backend.yaml | 38 +++++++-------- .../thecombine/charts/backend/values.yaml | 6 --- .../templates/env-frontend-configmap.yaml | 2 +- .../thecombine/charts/frontend/values.yaml | 7 +-- deploy/helm/thecombine/values.yaml | 8 ++-- deploy/scripts/install-combine.sh | 4 +- .../scripts/setup_files/combine_config.yaml | 12 +---- .../scripts/setup_files/profiles/desktop.yaml | 2 +- deploy/scripts/setup_files/profiles/dev.yaml | 3 +- .../scripts/setup_files/profiles/nuc_qa.yaml | 2 +- deploy/scripts/setup_files/profiles/prod.yaml | 5 +- .../scripts/setup_files/profiles/staging.yaml | 7 ++- docs/deploy/README.md | 2 +- src/components/Login/Captcha.tsx | 2 +- 19 files changed, 88 insertions(+), 93 deletions(-) diff --git a/Backend/Properties/launchSettings.json b/Backend/Properties/launchSettings.json index f4388940e3..6a6b51799c 100644 --- a/Backend/Properties/launchSettings.json +++ b/Backend/Properties/launchSettings.json @@ -15,10 +15,10 @@ "launchUrl": "v1", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development", - "COMBINE_JWT_SECRET_KEY": "0123456789abcdefghijklmnopqrstuvwxyz", "COMBINE_CAPTCHA_REQUIRED": "True", "COMBINE_CAPTCHA_SECRET_KEY": "1x0000000000000000000000000000000AA", - "COMBINE_CAPTCHA_VERIFY_URL": "https://challenges.cloudflare.com/turnstile/v0/siteverify" + "COMBINE_CAPTCHA_VERIFY_URL": "https://challenges.cloudflare.com/turnstile/v0/siteverify", + "COMBINE_JWT_SECRET_KEY": "0123456789abcdefghijklmnopqrstuvwxyz" } }, "BackendFramework": { @@ -28,10 +28,10 @@ "environmentVariables": { "Key": "Value", "ASPNETCORE_ENVIRONMENT": "Development", - "COMBINE_JWT_SECRET_KEY": "0123456789abcdefghijklmnopqrstuvwxyz", "COMBINE_CAPTCHA_REQUIRED": "True", "COMBINE_CAPTCHA_SECRET_KEY": "1x0000000000000000000000000000000AA", - "COMBINE_CAPTCHA_VERIFY_URL": "https://challenges.cloudflare.com/turnstile/v0/siteverify" + "COMBINE_CAPTCHA_VERIFY_URL": "https://challenges.cloudflare.com/turnstile/v0/siteverify", + "COMBINE_JWT_SECRET_KEY": "0123456789abcdefghijklmnopqrstuvwxyz" }, "applicationUrl": "http://localhost:5000", "hotReloadProfile": "aspnetcore" diff --git a/Backend/Startup.cs b/Backend/Startup.cs index 1eef1c1d71..51df3902b1 100644 --- a/Backend/Startup.cs +++ b/Backend/Startup.cs @@ -65,7 +65,7 @@ public Settings() private sealed class EnvironmentNotConfiguredException : Exception { } - private string? CheckedEnvironmentVariable(string name, string? defaultValue, string error = "") + private string? CheckedEnvironmentVariable(string name, string? defaultValue, string error = "", bool info = false) { var contents = Environment.GetEnvironmentVariable(name); if (contents is not null) @@ -73,7 +73,14 @@ private sealed class EnvironmentNotConfiguredException : Exception { } return contents; } - _logger.LogError("Environment variable: {Name} is not defined. {Error}", name, error); + if (info) + { + _logger.LogInformation("Environment variable: {Name} is not defined. {Error}", name, error); + } + else + { + _logger.LogError("Environment variable: {Name} is not defined. {Error}", name, error); + } return defaultValue; } @@ -159,11 +166,28 @@ public void ConfigureServices(IServiceCollection services) options.CombineDatabase = Configuration["MongoDB:CombineDatabase"] ?? throw new EnvironmentNotConfiguredException(); + options.CaptchaEnabled = bool.Parse(CheckedEnvironmentVariable( + "COMBINE_CAPTCHA_REQUIRED", + bool.TrueString, // "True" + "CAPTCHA should be explicitly required or not required.")!); + if (options.CaptchaEnabled) + { + options.CaptchaSecretKey = CheckedEnvironmentVariable( + "COMBINE_CAPTCHA_SECRET_KEY", + null, + "CAPTCHA secret key required."); + options.CaptchaVerifyUrl = CheckedEnvironmentVariable( + "COMBINE_CAPTCHA_VERIFY_URL", + null, + "CAPTCHA verification URL required."); + } + const string emailServiceFailureMessage = "Email services will not work."; options.EmailEnabled = bool.Parse(CheckedEnvironmentVariable( "COMBINE_EMAIL_ENABLED", bool.FalseString, // "False" - emailServiceFailureMessage)!); + emailServiceFailureMessage, + true)!); if (options.EmailEnabled) { options.SmtpServer = CheckedEnvironmentVariable( @@ -196,23 +220,6 @@ public void ConfigureServices(IServiceCollection services) "COMBINE_PASSWORD_RESET_EXPIRE_TIME", Settings.DefaultPasswordResetExpireTime.ToString(), $"Using default value: {Settings.DefaultPasswordResetExpireTime}")!); - - - options.CaptchaEnabled = bool.Parse(CheckedEnvironmentVariable( - "COMBINE_CAPTCHA_REQUIRED", - bool.TrueString, // "True" - "CAPTCHA should be explicitly enabled or disabled.")!); - if (options.CaptchaEnabled) - { - options.CaptchaSecretKey = CheckedEnvironmentVariable( - "COMBINE_CAPTCHA_SECRET_KEY", - null, - "CAPTCHA secret key required."); - options.CaptchaVerifyUrl = CheckedEnvironmentVariable( - "COMBINE_CAPTCHA_VERIFY_URL", - null, - "CAPTCHA verification URL required."); - } }); // Register concrete types for dependency injection diff --git a/README.md b/README.md index 2d89946688..b79b52a86b 100644 --- a/README.md +++ b/README.md @@ -687,7 +687,7 @@ Notes: ### Setup Environment Variables Before installing _The Combine_ in Kubernetes, you need to set the following environment variables: -`COMBINE_JWT_SECRET_KEY`, `COMBINE_CAPTCHA_REQUIRED`, `COMBINE_CAPTCHA_SECRET_KEY`. For development environments, you +`COMBINE_CAPTCHA_REQUIRED`, `COMBINE_CAPTCHA_SECRET_KEY`, `COMBINE_JWT_SECRET_KEY`. For development environments, you can use the values defined in `Backend/Properties/launchSettings.json`. Set them in your `.profile` (Linux or Mac 10.14-), your `.zprofile` (Mac 10.15+), or the _System_ app (Windows). diff --git a/deploy/helm/thecombine/charts/backend/templates/backend-config-map.yaml b/deploy/helm/thecombine/charts/backend/templates/backend-config-map.yaml index c59ebb0393..e5c2723628 100644 --- a/deploy/helm/thecombine/charts/backend/templates/backend-config-map.yaml +++ b/deploy/helm/thecombine/charts/backend/templates/backend-config-map.yaml @@ -5,20 +5,20 @@ metadata: name: env-backend namespace: {{ .Release.Namespace }} data: - COMBINE_PASSWORD_RESET_EXPIRE_TIME: - {{ .Values.combinePasswordResetTime | quote }} - {{- if .Values.global.emailEnabled }} +{{- if .Values.global.captchaRequired }} + COMBINE_CAPTCHA_REQUIRED: "True" +{{- else }} + COMBINE_CAPTCHA_REQUIRED: "False" +{{- end }} + COMBINE_CAPTCHA_VERIFY_URL: {{ .Values.captchaVerifyUrl | quote }} +{{- if .Values.global.emailEnabled }} COMBINE_EMAIL_ENABLED: "True" - {{- else }} +{{- else }} COMBINE_EMAIL_ENABLED: "False" - {{- end }} +{{- end }} + COMBINE_PASSWORD_RESET_EXPIRE_TIME: + {{ .Values.combinePasswordResetTime | quote }} COMBINE_SMTP_ADDRESS: {{ .Values.combineSmtpAddress | quote }} COMBINE_SMTP_FROM: {{ .Values.combineSmtpFrom | quote }} COMBINE_SMTP_PORT: {{ .Values.combineSmtpPort | quote }} COMBINE_SMTP_SERVER: {{ .Values.combineSmtpServer | quote }} - {{- if .Values.global.captchRequired }} - COMBINE_CAPTCHA_REQUIRED: "True" - {{- else }} - COMBINE_CAPTCHA_REQUIRED: "False" - {{- end }} - COMBINE_CAPTCHA_VERIFY_URL: {{ .Values.captchaVerifyUrl | quote }} diff --git a/deploy/helm/thecombine/charts/backend/templates/backend-secrets.yaml b/deploy/helm/thecombine/charts/backend/templates/backend-secrets.yaml index 367e8dab26..44ad6bc87b 100644 --- a/deploy/helm/thecombine/charts/backend/templates/backend-secrets.yaml +++ b/deploy/helm/thecombine/charts/backend/templates/backend-secrets.yaml @@ -6,7 +6,7 @@ metadata: namespace: {{ .Release.Namespace }} type: Opaque data: + COMBINE_CAPTCHA_SECRET_KEY: {{ .Values.global.captchaSecretKey | b64enc }} COMBINE_JWT_SECRET_KEY: {{ .Values.global.combineJwtSecretKey | b64enc }} COMBINE_SMTP_USERNAME: {{ .Values.global.combineSmtpUsername | b64enc }} COMBINE_SMTP_PASSWORD: {{ .Values.global.combineSmtpPassword | b64enc }} - COMBINE_CAPTCHA_SECRET_KEY: {{ .Values.global.captchaSecretKey | b64enc }} diff --git a/deploy/helm/thecombine/charts/backend/templates/deployment-backend.yaml b/deploy/helm/thecombine/charts/backend/templates/deployment-backend.yaml index 44f1227931..fbdf68418b 100644 --- a/deploy/helm/thecombine/charts/backend/templates/deployment-backend.yaml +++ b/deploy/helm/thecombine/charts/backend/templates/deployment-backend.yaml @@ -29,21 +29,36 @@ spec: image: {{ include "backend.containerImage" . }} imagePullPolicy: {{ .Values.global.imagePullPolicy }} env: - - name: COMBINE_JWT_SECRET_KEY + - name: COMBINE_CAPTCHA_REQUIRED + valueFrom: + configMapKeyRef: + key: COMBINE_CAPTCHA_REQUIRED + name: env-backend + - name: COMBINE_CAPTCHA_SECRET_KEY valueFrom: secretKeyRef: - key: COMBINE_JWT_SECRET_KEY + key: COMBINE_CAPTCHA_SECRET_KEY name: env-backend-secrets - - name: COMBINE_PASSWORD_RESET_EXPIRE_TIME + - name: COMBINE_CAPTCHA_VERIFY_URL valueFrom: configMapKeyRef: - key: COMBINE_PASSWORD_RESET_EXPIRE_TIME + key: COMBINE_CAPTCHA_VERIFY_URL name: env-backend - name: COMBINE_EMAIL_ENABLED valueFrom: configMapKeyRef: key: COMBINE_EMAIL_ENABLED name: env-backend + - name: COMBINE_JWT_SECRET_KEY + valueFrom: + secretKeyRef: + key: COMBINE_JWT_SECRET_KEY + name: env-backend-secrets + - name: COMBINE_PASSWORD_RESET_EXPIRE_TIME + valueFrom: + configMapKeyRef: + key: COMBINE_PASSWORD_RESET_EXPIRE_TIME + name: env-backend - name: COMBINE_SMTP_ADDRESS valueFrom: configMapKeyRef: @@ -74,21 +89,6 @@ spec: secretKeyRef: key: COMBINE_SMTP_USERNAME name: env-backend-secrets - - name: COMBINE_CAPTCHA_REQUIRED - valueFrom: - configMapKeyRef: - key: COMBINE_CAPTCHA_REQUIRED - name: env-backend - - name: COMBINE_CAPTCHA_SECRET_KEY - valueFrom: - secretKeyRef: - key: COMBINE_CAPTCHA_SECRET_KEY - name: env-backend-secrets - - name: COMBINE_CAPTCHA_VERIFY_URL - valueFrom: - configMapKeyRef: - key: COMBINE_CAPTCHA_VERIFY_URL - name: env-backend ports: - containerPort: 5000 resources: diff --git a/deploy/helm/thecombine/charts/backend/values.yaml b/deploy/helm/thecombine/charts/backend/values.yaml index 227a328fb9..a36bd1a1be 100644 --- a/deploy/helm/thecombine/charts/backend/values.yaml +++ b/deploy/helm/thecombine/charts/backend/values.yaml @@ -19,17 +19,11 @@ global: awsDefaultRegion: "Override" pullSecretName: "None" combineJwtSecretKey: "Override" - emailEnabled: true - combineSmtpUsername: "Override" - combineSmtpPassword: "Override" # Values for pulling container image from image registry imagePullPolicy: "Override" imageTag: "latest" # Define the image registry to use (may be blank for local images) imageRegistry: "" - # CAPTCHA verification - captchaRequired: true - captchaSecretKey: "Override" persistentVolumeSize: 32Gi combinePasswordResetTime: 15 diff --git a/deploy/helm/thecombine/charts/frontend/templates/env-frontend-configmap.yaml b/deploy/helm/thecombine/charts/frontend/templates/env-frontend-configmap.yaml index f3a8cdbd27..ac8bb9abec 100644 --- a/deploy/helm/thecombine/charts/frontend/templates/env-frontend-configmap.yaml +++ b/deploy/helm/thecombine/charts/frontend/templates/env-frontend-configmap.yaml @@ -11,7 +11,7 @@ data: CONFIG_CAPTCHA_REQUIRED: {{ .Values.global.captchaRequired | quote }} CONFIG_CAPTCHA_SITE_KEY: {{ .Values.configCaptchaSiteKey | quote }} CONFIG_OFFLINE: {{ .Values.configOffline | quote }} - CONFIG_EMAIL_ENABLED: {{ .Values.global.emailEnabled quote }} + CONFIG_EMAIL_ENABLED: {{ .Values.global.emailEnabled | quote }} CONFIG_SHOW_CERT_EXPIRATION: {{ .Values.configShowCertExpiration | quote }} {{- if .Values.configAnalyticsWriteKey }} CONFIG_ANALYTICS_WRITE_KEY: {{ .Values.configAnalyticsWriteKey | quote }} diff --git a/deploy/helm/thecombine/charts/frontend/values.yaml b/deploy/helm/thecombine/charts/frontend/values.yaml index 1fdf087270..47a1e5d183 100644 --- a/deploy/helm/thecombine/charts/frontend/values.yaml +++ b/deploy/helm/thecombine/charts/frontend/values.yaml @@ -3,7 +3,6 @@ # Declare variables to be passed into your templates. global: - combineSmtpUsername: "Override" serverName: localhost pullSecretName: aws-login-credentials # Update strategy should be "Recreate" or "Rolling Update" @@ -13,16 +12,12 @@ global: imageTag: "latest" # Define the image registry to use (may be blank for local images) imageRegistry: "" - # Email services - emailEnabled: "true" - # CAPTCHA verification - captchaRequired: "false" imageName: combine_frontend # The additional domain list is a space-separated string list of domains combineAddlDomainList: "" -configCaptchaSiteKey: "None - from frontend chart" +configCaptchaSiteKey: "None - defined in profiles" configOffline: "false" configShowCertExpiration: "false" configAnalyticsWriteKey: "" diff --git a/deploy/helm/thecombine/values.yaml b/deploy/helm/thecombine/values.yaml index 0b61b9550d..eecc48ed2e 100644 --- a/deploy/helm/thecombine/values.yaml +++ b/deploy/helm/thecombine/values.yaml @@ -23,7 +23,10 @@ global: adminUsername: "Override" adminPassword: "Override" adminEmail: "Override" + captchaRequired: false + captchaSecretKey: "Override" combineJwtSecretKey: "Override" + emailEnabled: false combineSmtpUsername: "Override" combineSmtpPassword: "Override" # Local Storage for fonts @@ -37,9 +40,6 @@ global: pullSecretName: aws-login-credentials # Update strategy should be "Recreate" or "Rolling Update" updateStrategy: Recreate - # CAPTCHA verification - captchaRequired: false - captchaSecretKey: "Override" includeResourceLimits: false @@ -56,7 +56,7 @@ certManager: frontend: configShowCertExpiration: false configAnalyticsWriteKey: "" - configCaptchaSiteKey: "None" + configCaptchaSiteKey: "" # Maintenance configuration items maintenance: diff --git a/deploy/scripts/install-combine.sh b/deploy/scripts/install-combine.sh index a923ddfa35..fc22f98942 100755 --- a/deploy/scripts/install-combine.sh +++ b/deploy/scripts/install-combine.sh @@ -13,12 +13,12 @@ set-combine-env () { read -p "Enter AWS_SECRET_ACCESS_KEY: " AWS_SECRET_ACCESS_KEY # write collected values and static values to config file cat <<.EOF > ${CONFIG_DIR}/env - export COMBINE_JWT_SECRET_KEY="${COMBINE_JWT_SECRET_KEY}" export AWS_DEFAULT_REGION="us-east-1" export AWS_ACCESS_KEY_ID="${AWS_ACCESS_KEY_ID}" export AWS_SECRET_ACCESS_KEY="${AWS_SECRET_ACCESS_KEY}" - export COMBINE_EMAIL_ENABLED="False" export COMBINE_CAPTCHA_REQUIRED="False" + export COMBINE_EMAIL_ENABLED="False" + export COMBINE_JWT_SECRET_KEY="${COMBINE_JWT_SECRET_KEY}" .EOF chmod 600 ${CONFIG_DIR}/env fi diff --git a/deploy/scripts/setup_files/combine_config.yaml b/deploy/scripts/setup_files/combine_config.yaml index 80c4b03925..ca9030bbbe 100644 --- a/deploy/scripts/setup_files/combine_config.yaml +++ b/deploy/scripts/setup_files/combine_config.yaml @@ -78,11 +78,7 @@ targets: # override values for 'thecombine' chart thecombine: global: - captchaRequired: true - emailEnabled: true serverName: qa-kube.thecombine.app - frontend: - configCaptchaSiteKey: "0x4AAAAAAAe9zmM2ysXGSJk1" # Turnstile site key prod: profile: prod env_vars_required: true @@ -90,11 +86,7 @@ targets: # override values for 'thecombine' chart thecombine: global: - captchaRequired: true - emailEnabled: true serverName: thecombine.app - frontend: - configCaptchaSiteKey: "0x4AAAAAAAe9zmM2ysXGSJk1" # Turnstile site key # Set of profiles # Each key of 'profiles' defines one of the profiles used by the set of targets. @@ -152,14 +144,14 @@ charts: env_var: AWS_ACCESS_KEY_ID - config_item: awsSecretAccessKey env_var: AWS_SECRET_ACCESS_KEY + - config_item: captchaSecretKey + env_var: COMBINE_CAPTCHA_SECRET_KEY - config_item: combineJwtSecretKey env_var: COMBINE_JWT_SECRET_KEY - config_item: combineSmtpUsername env_var: COMBINE_SMTP_USERNAME - config_item: combineSmtpPassword env_var: COMBINE_SMTP_PASSWORD - - config_item: captchaSecretKey - env_var: COMBINE_CAPTCHA_SECRET_KEY create-admin-user: namespace: thecombine install_langs: false diff --git a/deploy/scripts/setup_files/profiles/desktop.yaml b/deploy/scripts/setup_files/profiles/desktop.yaml index b84396f6d3..4d827ee28e 100644 --- a/deploy/scripts/setup_files/profiles/desktop.yaml +++ b/deploy/scripts/setup_files/profiles/desktop.yaml @@ -2,7 +2,7 @@ ################################################ # Profile specific configuration items # -# Profile: nuc +# Profile: desktop ################################################ charts: diff --git a/deploy/scripts/setup_files/profiles/dev.yaml b/deploy/scripts/setup_files/profiles/dev.yaml index 0fa22d737f..ae4b78a03f 100644 --- a/deploy/scripts/setup_files/profiles/dev.yaml +++ b/deploy/scripts/setup_files/profiles/dev.yaml @@ -13,7 +13,7 @@ charts: frontend: # https://developers.cloudflare.com/turnstile/troubleshooting/testing/ # has dummy secret keys for development and testing; options are - # invisible pass and fail, visible pass and fail, and forced interaction + # invisible pass, invisible fail, visible pass, visible fail, forced interaction configCaptchaSiteKey: "1x00000000000000000000AA" # visible pass global: @@ -22,6 +22,7 @@ charts: includeResourceLimits: false awsS3Location: dev.thecombine.app captchaRequired: true + emailEnabled: true ingressClass: nginx imagePullPolicy: Never diff --git a/deploy/scripts/setup_files/profiles/nuc_qa.yaml b/deploy/scripts/setup_files/profiles/nuc_qa.yaml index 05f2f5bee4..6f2efa29c5 100644 --- a/deploy/scripts/setup_files/profiles/nuc_qa.yaml +++ b/deploy/scripts/setup_files/profiles/nuc_qa.yaml @@ -2,7 +2,7 @@ ################################################ # Profile specific configuration items # -# Profile: nuc +# Profile: nuc_qa ################################################ charts: diff --git a/deploy/scripts/setup_files/profiles/prod.yaml b/deploy/scripts/setup_files/profiles/prod.yaml index 2e45809325..f293e076d6 100644 --- a/deploy/scripts/setup_files/profiles/prod.yaml +++ b/deploy/scripts/setup_files/profiles/prod.yaml @@ -13,8 +13,9 @@ charts: enabled: false # Frontend configuration items: frontend: - configShowCertExpiration: false configAnalyticsWriteKey: "j9EeK4oURluRSIKbaXCBKBxGCnT2WahB" + configCaptchaSiteKey: "0x4AAAAAAAe9zmM2ysXGSJk1" # Turnstile site key + configShowCertExpiration: false # Maintenance configuration items maintenance: ####################################### @@ -29,6 +30,8 @@ charts: updateFontsSchedule: "15 02 * * 0" global: awsS3Location: prod.thecombine.app + captchaRequired: true + emailEnabled: true fontStorageAccessMode: ReadWriteMany imagePullPolicy: Always pullSecretName: None diff --git a/deploy/scripts/setup_files/profiles/staging.yaml b/deploy/scripts/setup_files/profiles/staging.yaml index 6c86d40934..0243891114 100644 --- a/deploy/scripts/setup_files/profiles/staging.yaml +++ b/deploy/scripts/setup_files/profiles/staging.yaml @@ -2,17 +2,20 @@ ################################################ # Profile specific configuration items # -# Profile: prod +# Profile: staging ################################################ charts: thecombine: # Frontend configuration items: frontend: - configShowCertExpiration: false configAnalyticsWriteKey: "AoebaDJNjSlOMRUH87EaNjvwkQpfLoyy" + configCaptchaSiteKey: "0x4AAAAAAAe9zmM2ysXGSJk1" # Turnstile site key + configShowCertExpiration: false global: awsS3Location: prod.thecombine.app + captchaRequired: true + emailEnabled: true fontStorageAccessMode: ReadWriteMany imagePullPolicy: Always includeResourceLimits: true diff --git a/docs/deploy/README.md b/docs/deploy/README.md index 9252d0862a..bff173ffeb 100644 --- a/docs/deploy/README.md +++ b/docs/deploy/README.md @@ -285,13 +285,13 @@ The setup scripts require the following environment variables to be set: - AWS_DEFAULT_REGION - AWS_ACCESS_KEY_ID - AWS_SECRET_ACCESS_KEY +- COMBINE_CAPTCHA_SECRET_KEY - COMBINE_JWT_SECRET_KEY - COMBINE_SMTP_USERNAME - COMBINE_SMTP_PASSWORD - COMBINE_ADMIN_USERNAME - COMBINE_ADMIN_PASSWORD - COMBINE_ADMIN_EMAIL -- COMBINE_CAPTCHA_SECRET_KEY You may also set the KUBECONFIG environment variable to the location of the `kubectl` configuration file. This is not necessary if the configuration file is at `${HOME}/.kube/config`. diff --git a/src/components/Login/Captcha.tsx b/src/components/Login/Captcha.tsx index 46c42085f7..8f3de5f5ed 100644 --- a/src/components/Login/Captcha.tsx +++ b/src/components/Login/Captcha.tsx @@ -27,7 +27,7 @@ export default function Captcha(props: CaptchaProps): ReactElement { ? RuntimeConfig.getInstance().captchaSiteKey() : // https://developers.cloudflare.com/turnstile/troubleshooting/testing/ // has dummy site keys for development and testing; options are - // invisible pass and fail, visible pass and fail, and forced interaction + // invisible pass, invisible fail, visible pass, visible fail, forced interaction "1x00000000000000000000AA"; // visible pass const fail = (): void => { From bf7ac17281fb7129621bd4810106a15975aff152 Mon Sep 17 00:00:00 2001 From: Danny Rorabaugh Date: Fri, 19 Jul 2024 15:37:03 -0400 Subject: [PATCH 17/24] Move up --- Backend/Startup.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Backend/Startup.cs b/Backend/Startup.cs index 51df3902b1..0839d62cba 100644 --- a/Backend/Startup.cs +++ b/Backend/Startup.cs @@ -39,6 +39,9 @@ public class Settings { public const int DefaultPasswordResetExpireTime = 15; + public bool CaptchaEnabled { get; set; } + public string? CaptchaSecretKey { get; set; } + public string? CaptchaVerifyUrl { get; set; } public string ConnectionString { get; set; } public string CombineDatabase { get; set; } public bool EmailEnabled { get; set; } @@ -49,17 +52,14 @@ public class Settings public string? SmtpAddress { get; set; } public string? SmtpFrom { get; set; } public int PassResetExpireTime { get; set; } - public bool CaptchaEnabled { get; set; } - public string? CaptchaSecretKey { get; set; } - public string? CaptchaVerifyUrl { get; set; } public Settings() { + CaptchaEnabled = true; ConnectionString = ""; CombineDatabase = ""; EmailEnabled = false; PassResetExpireTime = DefaultPasswordResetExpireTime; - CaptchaEnabled = true; } } From fac46f245d01be4efebc415e7d0dd76dc41e04e7 Mon Sep 17 00:00:00 2001 From: Danny Rorabaugh Date: Mon, 22 Jul 2024 09:24:46 -0400 Subject: [PATCH 18/24] Update from review --- Backend/Properties/launchSettings.json | 4 ++-- Backend/Startup.cs | 4 ++-- .../charts/backend/templates/backend-config-map.yaml | 12 ++---------- deploy/scripts/install-combine.sh | 4 +--- 4 files changed, 7 insertions(+), 17 deletions(-) diff --git a/Backend/Properties/launchSettings.json b/Backend/Properties/launchSettings.json index 6a6b51799c..3fad483db3 100644 --- a/Backend/Properties/launchSettings.json +++ b/Backend/Properties/launchSettings.json @@ -15,7 +15,7 @@ "launchUrl": "v1", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development", - "COMBINE_CAPTCHA_REQUIRED": "True", + "COMBINE_CAPTCHA_REQUIRED": "true", "COMBINE_CAPTCHA_SECRET_KEY": "1x0000000000000000000000000000000AA", "COMBINE_CAPTCHA_VERIFY_URL": "https://challenges.cloudflare.com/turnstile/v0/siteverify", "COMBINE_JWT_SECRET_KEY": "0123456789abcdefghijklmnopqrstuvwxyz" @@ -28,7 +28,7 @@ "environmentVariables": { "Key": "Value", "ASPNETCORE_ENVIRONMENT": "Development", - "COMBINE_CAPTCHA_REQUIRED": "True", + "COMBINE_CAPTCHA_REQUIRED": "true", "COMBINE_CAPTCHA_SECRET_KEY": "1x0000000000000000000000000000000AA", "COMBINE_CAPTCHA_VERIFY_URL": "https://challenges.cloudflare.com/turnstile/v0/siteverify", "COMBINE_JWT_SECRET_KEY": "0123456789abcdefghijklmnopqrstuvwxyz" diff --git a/Backend/Startup.cs b/Backend/Startup.cs index 0839d62cba..b4bfb43735 100644 --- a/Backend/Startup.cs +++ b/Backend/Startup.cs @@ -168,7 +168,7 @@ public void ConfigureServices(IServiceCollection services) options.CaptchaEnabled = bool.Parse(CheckedEnvironmentVariable( "COMBINE_CAPTCHA_REQUIRED", - bool.TrueString, // "True" + bool.TrueString, "CAPTCHA should be explicitly required or not required.")!); if (options.CaptchaEnabled) { @@ -185,7 +185,7 @@ public void ConfigureServices(IServiceCollection services) const string emailServiceFailureMessage = "Email services will not work."; options.EmailEnabled = bool.Parse(CheckedEnvironmentVariable( "COMBINE_EMAIL_ENABLED", - bool.FalseString, // "False" + bool.FalseString, emailServiceFailureMessage, true)!); if (options.EmailEnabled) diff --git a/deploy/helm/thecombine/charts/backend/templates/backend-config-map.yaml b/deploy/helm/thecombine/charts/backend/templates/backend-config-map.yaml index e5c2723628..41575bb66b 100644 --- a/deploy/helm/thecombine/charts/backend/templates/backend-config-map.yaml +++ b/deploy/helm/thecombine/charts/backend/templates/backend-config-map.yaml @@ -5,17 +5,9 @@ metadata: name: env-backend namespace: {{ .Release.Namespace }} data: -{{- if .Values.global.captchaRequired }} - COMBINE_CAPTCHA_REQUIRED: "True" -{{- else }} - COMBINE_CAPTCHA_REQUIRED: "False" -{{- end }} + COMBINE_CAPTCHA_REQUIRED: {{ .Values.global.captchaRequired | quote }} COMBINE_CAPTCHA_VERIFY_URL: {{ .Values.captchaVerifyUrl | quote }} -{{- if .Values.global.emailEnabled }} - COMBINE_EMAIL_ENABLED: "True" -{{- else }} - COMBINE_EMAIL_ENABLED: "False" -{{- end }} + COMBINE_EMAIL_ENABLED: {{ .Values.global.emailEnabled | quote }} COMBINE_PASSWORD_RESET_EXPIRE_TIME: {{ .Values.combinePasswordResetTime | quote }} COMBINE_SMTP_ADDRESS: {{ .Values.combineSmtpAddress | quote }} diff --git a/deploy/scripts/install-combine.sh b/deploy/scripts/install-combine.sh index fc22f98942..55aed50a26 100755 --- a/deploy/scripts/install-combine.sh +++ b/deploy/scripts/install-combine.sh @@ -13,12 +13,10 @@ set-combine-env () { read -p "Enter AWS_SECRET_ACCESS_KEY: " AWS_SECRET_ACCESS_KEY # write collected values and static values to config file cat <<.EOF > ${CONFIG_DIR}/env + export COMBINE_JWT_SECRET_KEY="${COMBINE_JWT_SECRET_KEY}" export AWS_DEFAULT_REGION="us-east-1" export AWS_ACCESS_KEY_ID="${AWS_ACCESS_KEY_ID}" export AWS_SECRET_ACCESS_KEY="${AWS_SECRET_ACCESS_KEY}" - export COMBINE_CAPTCHA_REQUIRED="False" - export COMBINE_EMAIL_ENABLED="False" - export COMBINE_JWT_SECRET_KEY="${COMBINE_JWT_SECRET_KEY}" .EOF chmod 600 ${CONFIG_DIR}/env fi From 16775309e8414a6298dc57a31b013b3179ea0661 Mon Sep 17 00:00:00 2001 From: Danny Rorabaugh Date: Mon, 22 Jul 2024 14:18:34 -0400 Subject: [PATCH 19/24] Reenforce lowercase bool config strings --- Backend/Startup.cs | 4 ++-- README.md | 2 +- .../charts/backend/templates/backend-config-map.yaml | 4 ++-- .../charts/frontend/templates/env-frontend-configmap.yaml | 8 ++++---- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Backend/Startup.cs b/Backend/Startup.cs index b4bfb43735..8bdad6da05 100644 --- a/Backend/Startup.cs +++ b/Backend/Startup.cs @@ -168,7 +168,7 @@ public void ConfigureServices(IServiceCollection services) options.CaptchaEnabled = bool.Parse(CheckedEnvironmentVariable( "COMBINE_CAPTCHA_REQUIRED", - bool.TrueString, + "true", "CAPTCHA should be explicitly required or not required.")!); if (options.CaptchaEnabled) { @@ -185,7 +185,7 @@ public void ConfigureServices(IServiceCollection services) const string emailServiceFailureMessage = "Email services will not work."; options.EmailEnabled = bool.Parse(CheckedEnvironmentVariable( "COMBINE_EMAIL_ENABLED", - bool.FalseString, + "false", emailServiceFailureMessage, true)!); if (options.EmailEnabled) diff --git a/README.md b/README.md index b79b52a86b..affb0f3c77 100644 --- a/README.md +++ b/README.md @@ -146,7 +146,7 @@ A rapid word collection tool. See the [User Guide](https://sillsdev.github.io/Th SMTP values must be kept secret, so ask your email administrator to supply them. Set them in your `.profile` (Linux or Mac 10.14-), your `.zprofile` (Mac 10.15+), or the _System_ app (Windows). - - `COMBINE_EMAIL_ENABLED=True` + - `COMBINE_EMAIL_ENABLED=true` - `COMBINE_SMTP_SERVER` - `COMBINE_SMTP_PORT` - `COMBINE_SMTP_USERNAME` diff --git a/deploy/helm/thecombine/charts/backend/templates/backend-config-map.yaml b/deploy/helm/thecombine/charts/backend/templates/backend-config-map.yaml index 41575bb66b..451f3b35cb 100644 --- a/deploy/helm/thecombine/charts/backend/templates/backend-config-map.yaml +++ b/deploy/helm/thecombine/charts/backend/templates/backend-config-map.yaml @@ -5,9 +5,9 @@ metadata: name: env-backend namespace: {{ .Release.Namespace }} data: - COMBINE_CAPTCHA_REQUIRED: {{ .Values.global.captchaRequired | quote }} + COMBINE_CAPTCHA_REQUIRED: {{ .Values.global.captchaRequired | quote | toLower }} COMBINE_CAPTCHA_VERIFY_URL: {{ .Values.captchaVerifyUrl | quote }} - COMBINE_EMAIL_ENABLED: {{ .Values.global.emailEnabled | quote }} + COMBINE_EMAIL_ENABLED: {{ .Values.global.emailEnabled | quote | toLower }} COMBINE_PASSWORD_RESET_EXPIRE_TIME: {{ .Values.combinePasswordResetTime | quote }} COMBINE_SMTP_ADDRESS: {{ .Values.combineSmtpAddress | quote }} diff --git a/deploy/helm/thecombine/charts/frontend/templates/env-frontend-configmap.yaml b/deploy/helm/thecombine/charts/frontend/templates/env-frontend-configmap.yaml index ac8bb9abec..14fb51b974 100644 --- a/deploy/helm/thecombine/charts/frontend/templates/env-frontend-configmap.yaml +++ b/deploy/helm/thecombine/charts/frontend/templates/env-frontend-configmap.yaml @@ -8,11 +8,11 @@ data: SERVER_NAME: {{ .Values.global.serverName }} CERT_ADDL_DOMAINS: {{ .Values.combineAddlDomainList | quote }} CONFIG_USE_CONNECTION_URL: "true" - CONFIG_CAPTCHA_REQUIRED: {{ .Values.global.captchaRequired | quote }} + CONFIG_CAPTCHA_REQUIRED: {{ .Values.global.captchaRequired | quote | toLower }} CONFIG_CAPTCHA_SITE_KEY: {{ .Values.configCaptchaSiteKey | quote }} - CONFIG_OFFLINE: {{ .Values.configOffline | quote }} - CONFIG_EMAIL_ENABLED: {{ .Values.global.emailEnabled | quote }} - CONFIG_SHOW_CERT_EXPIRATION: {{ .Values.configShowCertExpiration | quote }} + CONFIG_OFFLINE: {{ .Values.configOffline | quote | toLower }} + CONFIG_EMAIL_ENABLED: {{ .Values.global.emailEnabled | quote | toLower }} + CONFIG_SHOW_CERT_EXPIRATION: {{ .Values.configShowCertExpiration | quote | toLower }} {{- if .Values.configAnalyticsWriteKey }} CONFIG_ANALYTICS_WRITE_KEY: {{ .Values.configAnalyticsWriteKey | quote }} {{- end }} From 303c74568abe840b3b70f23d999accb79a17798b Mon Sep 17 00:00:00 2001 From: Danny Rorabaugh Date: Tue, 23 Jul 2024 09:25:14 -0400 Subject: [PATCH 20/24] Clean up bool configs --- README.md | 10 +++++----- .../charts/backend/templates/backend-config-map.yaml | 4 ++-- .../frontend/templates/env-frontend-configmap.yaml | 8 ++++---- deploy/helm/thecombine/charts/frontend/values.yaml | 4 ++-- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index affb0f3c77..707af1ee32 100644 --- a/README.md +++ b/README.md @@ -143,8 +143,8 @@ A rapid word collection tool. See the [User Guide](https://sillsdev.github.io/Th ### Prepare the Environment 1. _(Optional)_ If you want the email services to work you will need to set the following environment variables. These - SMTP values must be kept secret, so ask your email administrator to supply them. Set them in your `.profile` (Linux - or Mac 10.14-), your `.zprofile` (Mac 10.15+), or the _System_ app (Windows). + `COMBINE_SMTP_` values must be kept secret, so ask your email administrator to supply them. Set them in your + `.profile` (Linux or Mac 10.14-), your `.zprofile` (Mac 10.15+), or the _System_ app (Windows). - `COMBINE_EMAIL_ENABLED=true` - `COMBINE_SMTP_SERVER` @@ -687,9 +687,9 @@ Notes: ### Setup Environment Variables Before installing _The Combine_ in Kubernetes, you need to set the following environment variables: -`COMBINE_CAPTCHA_REQUIRED`, `COMBINE_CAPTCHA_SECRET_KEY`, `COMBINE_JWT_SECRET_KEY`. For development environments, you -can use the values defined in `Backend/Properties/launchSettings.json`. Set them in your `.profile` (Linux or Mac -10.14-), your `.zprofile` (Mac 10.15+), or the _System_ app (Windows). +`COMBINE_CAPTCHA_SECRET_KEY`, `COMBINE_JWT_SECRET_KEY`. For development environments, you can use the values defined in +`Backend/Properties/launchSettings.json`. Set them in your `.profile` (Linux or Mac 10.14-), your `.zprofile` (Mac +10.15+), or the _System_ app (Windows). _Note: The following is optional for Development Environments._ diff --git a/deploy/helm/thecombine/charts/backend/templates/backend-config-map.yaml b/deploy/helm/thecombine/charts/backend/templates/backend-config-map.yaml index 451f3b35cb..0a7f5a803c 100644 --- a/deploy/helm/thecombine/charts/backend/templates/backend-config-map.yaml +++ b/deploy/helm/thecombine/charts/backend/templates/backend-config-map.yaml @@ -5,9 +5,9 @@ metadata: name: env-backend namespace: {{ .Release.Namespace }} data: - COMBINE_CAPTCHA_REQUIRED: {{ .Values.global.captchaRequired | quote | toLower }} + COMBINE_CAPTCHA_REQUIRED: {{ .Values.global.captchaRequired | quote | lower }} COMBINE_CAPTCHA_VERIFY_URL: {{ .Values.captchaVerifyUrl | quote }} - COMBINE_EMAIL_ENABLED: {{ .Values.global.emailEnabled | quote | toLower }} + COMBINE_EMAIL_ENABLED: {{ .Values.global.emailEnabled | quote | lower }} COMBINE_PASSWORD_RESET_EXPIRE_TIME: {{ .Values.combinePasswordResetTime | quote }} COMBINE_SMTP_ADDRESS: {{ .Values.combineSmtpAddress | quote }} diff --git a/deploy/helm/thecombine/charts/frontend/templates/env-frontend-configmap.yaml b/deploy/helm/thecombine/charts/frontend/templates/env-frontend-configmap.yaml index 14fb51b974..7069bc3c6e 100644 --- a/deploy/helm/thecombine/charts/frontend/templates/env-frontend-configmap.yaml +++ b/deploy/helm/thecombine/charts/frontend/templates/env-frontend-configmap.yaml @@ -8,11 +8,11 @@ data: SERVER_NAME: {{ .Values.global.serverName }} CERT_ADDL_DOMAINS: {{ .Values.combineAddlDomainList | quote }} CONFIG_USE_CONNECTION_URL: "true" - CONFIG_CAPTCHA_REQUIRED: {{ .Values.global.captchaRequired | quote | toLower }} + CONFIG_CAPTCHA_REQUIRED: {{ .Values.global.captchaRequired | quote | lower }} CONFIG_CAPTCHA_SITE_KEY: {{ .Values.configCaptchaSiteKey | quote }} - CONFIG_OFFLINE: {{ .Values.configOffline | quote | toLower }} - CONFIG_EMAIL_ENABLED: {{ .Values.global.emailEnabled | quote | toLower }} - CONFIG_SHOW_CERT_EXPIRATION: {{ .Values.configShowCertExpiration | quote | toLower }} + CONFIG_OFFLINE: {{ .Values.configOffline | quote | lower }} + CONFIG_EMAIL_ENABLED: {{ .Values.global.emailEnabled | quote | lower }} + CONFIG_SHOW_CERT_EXPIRATION: {{ .Values.configShowCertExpiration | quote | lower }} {{- if .Values.configAnalyticsWriteKey }} CONFIG_ANALYTICS_WRITE_KEY: {{ .Values.configAnalyticsWriteKey | quote }} {{- end }} diff --git a/deploy/helm/thecombine/charts/frontend/values.yaml b/deploy/helm/thecombine/charts/frontend/values.yaml index 47a1e5d183..2f25b3b684 100644 --- a/deploy/helm/thecombine/charts/frontend/values.yaml +++ b/deploy/helm/thecombine/charts/frontend/values.yaml @@ -18,6 +18,6 @@ imageName: combine_frontend # The additional domain list is a space-separated string list of domains combineAddlDomainList: "" configCaptchaSiteKey: "None - defined in profiles" -configOffline: "false" -configShowCertExpiration: "false" +configOffline: false +configShowCertExpiration: false configAnalyticsWriteKey: "" From c5106631bf60f7d414e2b6a793f0c6973f0cad7b Mon Sep 17 00:00:00 2001 From: Danny Rorabaugh Date: Tue, 23 Jul 2024 11:29:17 -0400 Subject: [PATCH 21/24] Deploy to QA in this branch for testing --- .github/workflows/deploy_qa.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/deploy_qa.yml b/.github/workflows/deploy_qa.yml index 4cc7058ed2..94b5da1b55 100644 --- a/.github/workflows/deploy_qa.yml +++ b/.github/workflows/deploy_qa.yml @@ -2,9 +2,7 @@ name: "Deploy Update to QA Server" on: push: - branches: - - master - #- turnstile + branches: [master, turnstile] permissions: contents: read From 34e4dcb3118e6704f29daeb4af25c93b9340d03b Mon Sep 17 00:00:00 2001 From: Danny Rorabaugh Date: Wed, 24 Jul 2024 09:19:34 -0400 Subject: [PATCH 22/24] Restore deploy_qa safeguards --- .github/workflows/deploy_qa.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy_qa.yml b/.github/workflows/deploy_qa.yml index 94b5da1b55..30cc2b46df 100644 --- a/.github/workflows/deploy_qa.yml +++ b/.github/workflows/deploy_qa.yml @@ -2,7 +2,7 @@ name: "Deploy Update to QA Server" on: push: - branches: [master, turnstile] + branches: [master] permissions: contents: read @@ -93,7 +93,7 @@ jobs: deploy_update: needs: build # Only push to the QA server when built on the master branch - #if: ${{ github.ref_name == 'master' }} + if: ${{ github.ref_name == 'master' }} runs-on: [self-hosted, thecombine] steps: - name: Harden Runner From 68b4a310e05248804247fe030ede5a6fa0c3c383 Mon Sep 17 00:00:00 2001 From: Danny Rorabaugh Date: Wed, 24 Jul 2024 09:59:41 -0400 Subject: [PATCH 23/24] Make subcharts stand-alone --- .../helm/thecombine/charts/backend/values.yaml | 17 +++++++++++++---- .../helm/thecombine/charts/frontend/values.yaml | 11 ++++++++--- deploy/helm/thecombine/values.yaml | 7 +------ 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/deploy/helm/thecombine/charts/backend/values.yaml b/deploy/helm/thecombine/charts/backend/values.yaml index a36bd1a1be..6bd9d71a1a 100644 --- a/deploy/helm/thecombine/charts/backend/values.yaml +++ b/deploy/helm/thecombine/charts/backend/values.yaml @@ -12,18 +12,27 @@ global: # Update strategy should be "Recreate" or "Rolling Update" updateStrategy: Recreate + # AWS Credentials + awsAccount: "Override" + awsDefaultRegion: "Override" + # Combine variables adminUsername: "Override" adminPassword: "Override" adminEmail: "Override" - awsAccount: "Override" - awsDefaultRegion: "Override" - pullSecretName: "None" + captchaRequired: false + captchaSecretKey: "Override" combineJwtSecretKey: "Override" + combineSmtpUsername: "Override" + combineSmtpPassword: "Override" + emailEnabled: false # Values for pulling container image from image registry imagePullPolicy: "Override" - imageTag: "latest" # Define the image registry to use (may be blank for local images) imageRegistry: "" + imageTag: "latest" + pullSecretName: "None" + + includeResourceLimits: false persistentVolumeSize: 32Gi combinePasswordResetTime: 15 diff --git a/deploy/helm/thecombine/charts/frontend/values.yaml b/deploy/helm/thecombine/charts/frontend/values.yaml index 2f25b3b684..9eb8fc30ff 100644 --- a/deploy/helm/thecombine/charts/frontend/values.yaml +++ b/deploy/helm/thecombine/charts/frontend/values.yaml @@ -4,20 +4,25 @@ global: serverName: localhost - pullSecretName: aws-login-credentials + # Combine variables + captchaRequired: false + emailEnabled: false # Update strategy should be "Recreate" or "Rolling Update" updateStrategy: Recreate # Values for pulling container image from image registry imagePullPolicy: "Override" - imageTag: "latest" # Define the image registry to use (may be blank for local images) imageRegistry: "" + imageTag: "latest" + pullSecretName: aws-login-credentials + + includeResourceLimits: false imageName: combine_frontend # The additional domain list is a space-separated string list of domains combineAddlDomainList: "" +configAnalyticsWriteKey: "" configCaptchaSiteKey: "None - defined in profiles" configOffline: false configShowCertExpiration: false -configAnalyticsWriteKey: "" diff --git a/deploy/helm/thecombine/values.yaml b/deploy/helm/thecombine/values.yaml index eecc48ed2e..99638a66d0 100644 --- a/deploy/helm/thecombine/values.yaml +++ b/deploy/helm/thecombine/values.yaml @@ -26,9 +26,9 @@ global: captchaRequired: false captchaSecretKey: "Override" combineJwtSecretKey: "Override" - emailEnabled: false combineSmtpUsername: "Override" combineSmtpPassword: "Override" + emailEnabled: false # Local Storage for fonts fontStorageAccessMode: "ReadWriteOnce" fontStorageSize: 1Gi @@ -53,11 +53,6 @@ certManager: enabled: false certIssuer: letsencrypt-prod -frontend: - configShowCertExpiration: false - configAnalyticsWriteKey: "" - configCaptchaSiteKey: "" - # Maintenance configuration items maintenance: backupSchedule: "" From 003edadc7ce3818ef56d632bcd5aecd22158bbb4 Mon Sep 17 00:00:00 2001 From: Danny Rorabaugh Date: Wed, 24 Jul 2024 15:02:30 -0400 Subject: [PATCH 24/24] None-ify the pullSecretName default --- deploy/helm/thecombine/charts/frontend/values.yaml | 2 +- deploy/helm/thecombine/values.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/deploy/helm/thecombine/charts/frontend/values.yaml b/deploy/helm/thecombine/charts/frontend/values.yaml index 9eb8fc30ff..42240c00fb 100644 --- a/deploy/helm/thecombine/charts/frontend/values.yaml +++ b/deploy/helm/thecombine/charts/frontend/values.yaml @@ -14,7 +14,7 @@ global: # Define the image registry to use (may be blank for local images) imageRegistry: "" imageTag: "latest" - pullSecretName: aws-login-credentials + pullSecretName: "None" includeResourceLimits: false diff --git a/deploy/helm/thecombine/values.yaml b/deploy/helm/thecombine/values.yaml index 99638a66d0..7f4ad5b282 100644 --- a/deploy/helm/thecombine/values.yaml +++ b/deploy/helm/thecombine/values.yaml @@ -37,7 +37,7 @@ global: # Define the image registry to use (may be blank for local images) imageRegistry: awsEcr imageTag: "latest" - pullSecretName: aws-login-credentials + pullSecretName: "None" # Update strategy should be "Recreate" or "Rolling Update" updateStrategy: Recreate