Skip to content

Commit

Permalink
feat(templates): Add Google reCaptcha parameter to Boilerplate projec…
Browse files Browse the repository at this point in the history
…t template #7386 (#7387)
  • Loading branch information
ysmoradi authored Apr 21, 2024
1 parent 67ea3e5 commit eb525ec
Show file tree
Hide file tree
Showing 31 changed files with 123 additions and 105 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/bit.full.ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
- name: Release build todo sample + sqlite database
run: |
dotnet workload install maui-tizen maui-android wasm-tools wasm-experimental
dotnet new bit-bp --name TodoBPSqlite --database sqlite --sample todo --pipeline other
dotnet new bit-bp --name TodoBPSqlite --database sqlite --sample todo --pipeline none
${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager --sdk_root=$ANDROID_SDK_ROOT "platform-tools"
cd TodoBPSqlite/src/TodoBPSqlite.Server/
dotnet tool restore
Expand All @@ -42,9 +42,9 @@ jobs:
dotnet build TodoBPSqlite/TodoBPSqlite.sln -c Release -p:RunAOTCompilation=false
dotnet build TodoBPSqlite/src/Client/TodoBPSqlite.Client.Web/TodoBPSqlite.Client.Web.csproj -c Release -p:BlazorWebAssemblyStandalone=true
- name: Release build empty sample + offline db + Win exe
- name: Release build empty sample + offline db + Win exe + No reCaptcha
run: |
dotnet new bit-bp --name EmptyBP --database other --sample none --pipeline azure --offlineDb --windows
dotnet new bit-bp --name EmptyBP --database other --sample none --pipeline azure --offlineDb --windows --captcha none
dotnet build EmptyBP/EmptyBP.sln -c Release -p:RunAOTCompilation=false
- name: Release build empty sample without api
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@
"format": "yyyy-MM-dd"
}
},
"api": {
"displayName": "Add API to Server project?",
"type": "parameter",
"datatype": "bool",
"defaultValue": "true"
},
"database": {
"displayName": "Backend database",
"type": "parameter",
Expand All @@ -71,6 +77,22 @@
}
]
},
"captcha": {
"displayName": "Captcha",
"type": "parameter",
"datatype": "choice",
"defaultValue": "reCaptcha",
"choices": [
{
"choice": "reCaptcha",
"description": "Google reCaptcha"
},
{
"choice": "None",
"description": "None"
}
]
},
"pipeline": {
"displayName": "CI-CD pipeline",
"type": "parameter",
Expand All @@ -86,8 +108,8 @@
"description": "Azure DevOps pipelines"
},
{
"choice": "Other",
"description": "Other"
"choice": "None",
"description": "None"
}
]
},
Expand Down Expand Up @@ -123,12 +145,6 @@
"datatype": "bool",
"defaultValue": "false"
},
"api": {
"displayName": "Add API to Server project?",
"type": "parameter",
"datatype": "bool",
"defaultValue": "true"
},
"appInsights": {
"displayName": "Add Azure application insights to project?",
"type": "parameter",
Expand Down Expand Up @@ -277,9 +293,23 @@
"src/Boilerplate.Server/Services/AppSecureJwtDataFormat.cs",
"src/Boilerplate.Server/wwwroot/swagger/**",
"src/Boilerplate.Server/AppSettings.cs",
"src/Boilerplate.Server/Services/ServerJsonContext.cs",
"src/Boilerplate.Server/IdentityCertificate.pfx"
]
},
{
"condition": "(api != true || captcha != reCaptcha)",
"exclude": [
"src/Boilerplate.Server/Services/GoogleRecaptchaHttpClient.cs",
"src/Boilerplate.Server/Services/GoogleRecaptchaVerificationResponse.cs"
]
},
{
"condition": "(captcha != reCaptcha)",
"exclude": [
"src/Boilerplate.Client.Core/Components/Pages/Identity/GoogleRecaptcha.razor"
]
},
{
"condition": "(appInsights != true)",
"exclude": [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
namespace Boilerplate.Server;
//+:cnd:noEmit
namespace Boilerplate.Server;

public class AppSettings
{
Expand All @@ -10,7 +11,9 @@ public class AppSettings

public string UserProfileImagesDir { get; set; } = default!;

//#if (captcha == "reCaptcha")
public string GoogleRecaptchaSecretKey { get; set; } = default!;
//#endif
}

public class HealthCheckSettings
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,17 @@

<head>
<base href="/" />

<meta charset="utf-8" />
<meta name="theme-color">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="The Boilerplate is built with ASP.NET Core, Identity, Web API, EF Core and Blazor." />

@*#if (captcha == "reCaptcha")*@
<link rel="preconnect" href="https://www.google.com">
<link rel="preconnect" href="https://www.gstatic.com" crossorigin>
@*#endif*@

<Link rel="icon" href="favicon.ico" type="image/x-icon" />
<HeadOutlet @rendermode=renderMode />
<Link rel="apple-touch-icon" sizes="512x512" href="images/icons/bit-icon-512.png" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//-:cnd:noEmit
//+:cnd:noEmit
using System.Text;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
Expand Down Expand Up @@ -28,11 +28,11 @@ public partial class IdentityController : AppControllerBase, IIdentityController

[AutoInject] private HtmlRenderer htmlRenderer = default!;

[AutoInject] private IStringLocalizer<IdentityStrings> identityLocalizer = default!;

[AutoInject] private IOptionsMonitor<BearerTokenOptions> bearerTokenOptions = default!;

//#if (captcha == "reCaptcha")
[AutoInject] private GoogleRecaptchaHttpClient googleRecaptchaHttpClient = default!;
//#endif

/// <summary>
/// By leveraging summary tags in your controller's actions and DTO properties you can make your codes much easier to maintain.
Expand All @@ -41,8 +41,10 @@ public partial class IdentityController : AppControllerBase, IIdentityController
[HttpPost]
public async Task SignUp(SignUpRequestDto signUpRequest, CancellationToken cancellationToken)
{
if (await googleRecaptchaHttpClient.Verify(signUpRequest.GoogleRecaptchaResponse) is false)
//#if (captcha == "reCaptcha")
if (await googleRecaptchaHttpClient.Verify(signUpRequest.GoogleRecaptchaResponse, cancellationToken) is false)
throw new BadRequestException(Localizer[nameof(AppStrings.InvalidGoogleRecaptchaResponse)]);
//#endif

var existingUser = await userManager.FindByNameAsync(signUpRequest.Email!);

Expand Down Expand Up @@ -145,11 +147,8 @@ public async Task ConfirmEmail(ConfirmEmailRequestDto body)
}

[HttpPost, ProducesResponseType<TokenResponseDto>(statusCode: 200)]
public async Task SignIn(SignInRequestDto signInRequest)
public async Task SignIn(SignInRequestDto signInRequest, CancellationToken cancellationToken)
{
if (await googleRecaptchaHttpClient.Verify(signInRequest.GoogleRecaptchaResponse) is false)
throw new BadRequestException(Localizer[nameof(AppStrings.InvalidGoogleRecaptchaResponse)]);

signInManager.AuthenticationScheme = IdentityConstants.BearerScheme;

var result = await signInManager.PasswordSignInAsync(signInRequest.UserName!, signInRequest.Password!, isPersistent: false, lockoutOnFailure: true);
Expand Down Expand Up @@ -196,9 +195,6 @@ await signInManager.ValidateSecurityStampAsync(refreshTicket.Principal) is not U
[HttpPost]
public async Task SendResetPasswordEmail(SendResetPasswordEmailRequestDto sendResetPasswordEmailRequest, CancellationToken cancellationToken)
{
if (await googleRecaptchaHttpClient.Verify(sendResetPasswordEmailRequest.GoogleRecaptchaResponse) is false)
throw new BadRequestException(Localizer.GetString(nameof(AppStrings.InvalidGoogleRecaptchaResponse)));

var user = await userManager.FindByEmailAsync(sendResetPasswordEmailRequest.Email!)
?? throw new BadRequestException(Localizer.GetString(nameof(AppStrings.UserNameNotFound), sendResetPasswordEmailRequest.Email!));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,10 +144,12 @@ private static void ConfigureServices(this WebApplicationBuilder builder)

AddBlazor(builder);

//#if (api == true && captcha == "reCaptcha")
services.AddHttpClient<GoogleRecaptchaHttpClient>(c =>
{
c.BaseAddress = new Uri("https://www.google.com/recaptcha/");
});
//#endif
}

private static void AddBlazor(WebApplicationBuilder builder)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,17 @@ public partial class GoogleRecaptchaHttpClient

[AutoInject] protected HttpClient httpClient = default!;

public async ValueTask<bool> Verify(string? googleRecaptchaResponse)
public async ValueTask<bool> Verify(string? googleRecaptchaResponse, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(googleRecaptchaResponse)) return false;

var url = $"api/siteverify?secret={AppSettings.GoogleRecaptchaSecretKey}&response={googleRecaptchaResponse}";
var response = await httpClient.PostAsync(url, null);
var response = await httpClient.PostAsync(url, null, cancellationToken);

response.EnsureSuccessStatusCode();

var result = await response.Content.ReadFromJsonAsync(ServerJsonContext.Default.GoogleRecaptchaVerificationResponse);
var result = await response.Content.ReadFromJsonAsync(ServerJsonContext.Default.GoogleRecaptchaVerificationResponse, cancellationToken: cancellationToken);

return result?.Success ?? false;
return result?.Success is true;
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
namespace Boilerplate.Server.Services;
//+:cnd:noEmit
namespace Boilerplate.Server.Services;

/// <summary>
/// https://devblogs.microsoft.com/dotnet/try-the-new-system-text-json-source-generator/
/// </summary>
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
[JsonSerializable(typeof(Dictionary<string, object>))]
//#if (captcha == "reCaptcha")
[JsonSerializable(typeof(GoogleRecaptchaVerificationResponse))]
//#endif
public partial class ServerJsonContext : JsonSerializerContext
{
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@
"EnableHealthChecks": false
},
"UserProfileImagesDir": "Attachments/Profiles/",
//#if (captcha == "reCaptcha")
"GoogleRecaptchaSecretKey": "6LdMKr4pAAAAANvngWNam_nlHzEDJ2t6SfV6L_DS"
//#endif
},
//#endif
"AllowedHosts": "*"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@

//+:cnd:noEmit
namespace Boilerplate.Shared.Dtos.Identity;

[DtoResourceType(typeof(AppStrings))]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@

//+:cnd:noEmit
namespace Boilerplate.Shared.Dtos.Identity;

[DtoResourceType(typeof(AppStrings))]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@

//+:cnd:noEmit
namespace Boilerplate.Shared.Dtos.Identity;

[DtoResourceType(typeof(AppStrings))]
Expand All @@ -8,6 +8,4 @@ public class SendResetPasswordEmailRequestDto
[EmailAddress(ErrorMessage = nameof(AppStrings.EmailAddressAttribute_ValidationError))]
[Display(Name = nameof(AppStrings.Email))]
public string? Email { get; set; }

public string? GoogleRecaptchaResponse { get; set; }
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@

//+:cnd:noEmit
namespace Boilerplate.Shared.Dtos.Identity;

[DtoResourceType(typeof(AppStrings))]
Expand All @@ -18,6 +18,4 @@ public class SignInRequestDto
[JsonIgnore]
[Display(Name = nameof(AppStrings.RememberMe))]
public bool RememberMe { get; set; } = true;

public string? GoogleRecaptchaResponse { get; set; }
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
namespace Boilerplate.Shared.Dtos.Identity;
//+:cnd:noEmit
namespace Boilerplate.Shared.Dtos.Identity;

[DtoResourceType(typeof(AppStrings))]
public class SignUpRequestDto
Expand All @@ -22,5 +23,7 @@ public class SignUpRequestDto
[Display(Name = nameof(AppStrings.TermsAccepted))]
public bool TermsAccepted { get; set; }

//#if (captcha == "reCaptcha")
public string? GoogleRecaptchaResponse { get; set; }
//#endif
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -583,7 +583,4 @@
<data name="InvalidGoogleRecaptchaChallenge" xml:space="preserve">
<value>شما باید چالش گوگل ریکپچا را به سرانجام برسانید.</value>
</data>
<data name="ResetRecaptcha" xml:space="preserve">
<value>بازنشانی ریکپجا</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -582,7 +582,4 @@
<data name="InvalidGoogleRecaptchaChallenge" xml:space="preserve">
<value>Vous devez réussir le défi Google reCAPTCHA.</value>
</data>
<data name="ResetRecaptcha" xml:space="preserve">
<value>Réinitialiser reCAPTCHA</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -585,14 +585,13 @@ Please confirm your email by clicking on the link.</value>
<data name="Dashboard" xml:space="preserve">
<value>Dashboard</value>
</data>
<!--#if (captcha == "reCaptcha") -->
<data name="InvalidGoogleRecaptchaResponse" xml:space="preserve">
<value>Invalid Google reCAPTCHA response.</value>
</data>
<data name="InvalidGoogleRecaptchaChallenge" xml:space="preserve">
<value>You need to pass the Google reCAPTCHA challenge.</value>
</data>
<data name="ResetRecaptcha" xml:space="preserve">
<value>Reset reCAPTCHA</value>
</data>
<!--#endif -->
<!--#endif -->
</root>
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
@page "/forgot-password"
@*+:cnd:noEmit*@
@page "/forgot-password"
@inherits AppComponentBase

<PageTitle>@Localizer[nameof(AppStrings.ForgetPasswordTitle)]</PageTitle>
Expand Down Expand Up @@ -30,10 +31,6 @@
<ValidationMessage For="@(() => forgotPasswordModel.Email)" />
</div>

<div class="form-input-container">
<GoogleRecaptcha />
</div>

<BitButton IsLoading="isLoading"
Class="form-submit-button"
ButtonStyle="BitButtonStyle.Primary"
Expand Down
Loading

0 comments on commit eb525ec

Please sign in to comment.