Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CAPTCHA] Replace ReCAPTCHA with Turnstile #3249

Merged
merged 27 commits into from
Jul 25, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
83e1825
Replace CAPTCHA with Turnstile (with test keys)
imnasnainaec Jul 16, 2024
969ccc9
Complete CAPTCHA replacement
imnasnainaec Jul 16, 2024
80ba196
Setup secret key
imnasnainaec Jul 16, 2024
3dc89a2
Fix App tests
imnasnainaec Jul 16, 2024
eec3ba0
Merge branch 'master' into turnstile
imnasnainaec Jul 16, 2024
b9f029a
Fix failing tests
imnasnainaec Jul 16, 2024
2674e73
Fix App tests
imnasnainaec Jul 16, 2024
d805fb4
Tidy Turnstile component
imnasnainaec Jul 17, 2024
1db2d1a
Add for Turnstile: environment variables, Context, Service
imnasnainaec Jul 17, 2024
b2eab3f
Replace frontend CAPTCHA configs with Turnstile config
imnasnainaec Jul 17, 2024
0bb8630
Clean up residue
imnasnainaec Jul 18, 2024
18cf48c
Simplify bool conditional
imnasnainaec Jul 18, 2024
cef2a5e
Add turnstileSiteKey config
imnasnainaec Jul 18, 2024
4f0035b
Restore frontend Captcha configs; Add backend Enabled configs
imnasnainaec Jul 19, 2024
caeccde
Unify frontend and backend captcha/email configs
imnasnainaec Jul 19, 2024
cedee67
Clean up loose ends
imnasnainaec Jul 19, 2024
7c30de3
Clean up
imnasnainaec Jul 19, 2024
bf7ac17
Move up
imnasnainaec Jul 19, 2024
fac46f2
Update from review
imnasnainaec Jul 22, 2024
1677530
Reenforce lowercase bool config strings
imnasnainaec Jul 22, 2024
303c745
Clean up bool configs
imnasnainaec Jul 23, 2024
c510663
Deploy to QA in this branch for testing
imnasnainaec Jul 23, 2024
34e4dcb
Restore deploy_qa safeguards
imnasnainaec Jul 24, 2024
68b4a31
Make subcharts stand-alone
imnasnainaec Jul 24, 2024
003edad
None-ify the pullSecretName default
imnasnainaec Jul 24, 2024
934944b
Merge branch 'master' into turnstile
imnasnainaec Jul 24, 2024
01b67b4
Merge branch 'master' into turnstile
imnasnainaec Jul 25, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/deploy_qa.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ on:
push:
branches:
- master
#- turnstile

permissions:
contents: read
Expand Down Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@
"piptools",
"Prenoun",
"Preverb",
"recaptcha",
"reportgenerator",
"sched",
"signup",
Expand Down
2 changes: 1 addition & 1 deletion Backend.Tests/Controllers/UserControllerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
13 changes: 13 additions & 0 deletions Backend.Tests/Mocks/TurnstileServiceMock.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System.Threading.Tasks;
using BackendFramework.Interfaces;

namespace Backend.Tests.Mocks
{
sealed internal class TurnstileServiceMock : ITurnstileService
{
public Task<bool> VerifyToken(string token)
{
return Task.FromResult(true);
}
}
}
19 changes: 19 additions & 0 deletions Backend/Contexts/TurnstileContext.cs
Original file line number Diff line number Diff line change
@@ -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<Startup.Settings> options)
{
TurnstileSecretKey = options.Value.TurnstileSecretKey;
TurnstileVerifyUrl = options.Value.TurnstileVerifyUrl;
}
}
}
13 changes: 12 additions & 1 deletion Backend/Controllers/UserController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,25 @@ public class UserController : Controller
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)
IEmailService emailService, IPasswordResetService passwordResetService, ITurnstileService turnstileService)
{
_userRepo = userRepo;
_emailService = emailService;
_passwordResetService = passwordResetService;
_permissionService = permissionService;
_turnstileService = turnstileService;
}

/// <summary> Validates a Cloudflare Turnstile token </summary>
[AllowAnonymous]
[HttpGet("turnstile/{token}", Name = "ValidateTurnstile")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<IActionResult> ValidateTurnstile(string token)
{
return await _turnstileService.VerifyToken(token) ? Ok() : BadRequest();
}

/// <summary> Sends a password reset request </summary>
Expand Down
8 changes: 8 additions & 0 deletions Backend/Interfaces/ITurnstileContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace BackendFramework.Interfaces
{
public interface ITurnstileContext
{
string? TurnstileSecretKey { get; }
string? TurnstileVerifyUrl { get; }
}
}
9 changes: 9 additions & 0 deletions Backend/Interfaces/ITurnstileService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.Threading.Tasks;

namespace BackendFramework.Interfaces
{
public interface ITurnstileService
{
Task<bool> VerifyToken(string token);
}
}
10 changes: 8 additions & 2 deletions Backend/Properties/launchSettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand All @@ -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"
Expand Down
38 changes: 38 additions & 0 deletions Backend/Services/TurnstileService.cs
Original file line number Diff line number Diff line change
@@ -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<bool> 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<string, string>{
{"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");
}
}
}
17 changes: 17 additions & 0 deletions Backend/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down Expand Up @@ -178,10 +180,21 @@ public void ConfigureServices(IServiceCollection services)
"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);
});

// Register concrete types for dependency injection
Expand Down Expand Up @@ -228,6 +241,10 @@ public void ConfigureServices(IServiceCollection services)
// Statistics types
services.AddSingleton<IStatisticsService, StatisticsService>();

// Turnstile types
services.AddTransient<ITurnstileContext, TurnstileContext>();
services.AddTransient<ITurnstileService, TurnstileService>();

// User types
services.AddTransient<IUserContext, UserContext>();
services.AddTransient<IUserRepository, UserRepository>();
Expand Down
14 changes: 6 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand All @@ -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/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
TURNSTILE_SECRET_KEY: {{ .Values.global.turnstileSecretKey | b64enc }}
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,16 @@ spec:
secretKeyRef:
key: COMBINE_SMTP_USERNAME
name: env-backend-secrets
- name: TURNSTILE_SECRET_KEY
valueFrom:
secretKeyRef:
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:
Expand Down
4 changes: 4 additions & 0 deletions deploy/helm/thecombine/charts/backend/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,14 @@ global:
combineJwtSecretKey: "Override"
combineSmtpUsername: "Override"
combineSmtpPassword: "Override"
combineTurnstileSecretKey: "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: ""
# Cloudflare Turnstile verification
turnstileSecretKey: "Override"

persistentVolumeSize: 32Gi
combinePasswordResetTime: 15
Expand All @@ -34,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"
2 changes: 2 additions & 0 deletions deploy/helm/thecombine/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,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

Expand Down
2 changes: 2 additions & 0 deletions deploy/scripts/setup_files/combine_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,8 @@ charts:
env_var: COMBINE_SMTP_USERNAME
- config_item: combineSmtpPassword
env_var: COMBINE_SMTP_PASSWORD
- config_item: turnstileSecretKey
env_var: TURNSTILE_SECRET_KEY
create-admin-user:
namespace: thecombine
install_langs: false
Expand Down
1 change: 1 addition & 0 deletions docs/deploy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +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

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`.
Expand Down
47 changes: 1 addition & 46 deletions docs/user_guide/assets/licenses/frontend_licenses.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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 <[email protected]>

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
Expand Down Expand Up @@ -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 <[email protected]>

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.
Expand Down
Loading
Loading