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

Prevent duplicate user email #236

Merged
merged 6 commits into from
Sep 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions backend/LexBoxApi/GraphQL/UserMutations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using LexCore.Exceptions;
using LexData;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;

namespace LexBoxApi.GraphQL;
Expand All @@ -21,7 +22,7 @@ public record ChangeUserAccountByAdminInput(Guid UserId, string Email, string Na

[Error<NotFoundException>]
[Error<DbError>]
[Error<InvalidFormatException>]
[Error<UniqueValueException>]
[UseMutationConvention]
[RefreshJwt]
public Task<User> ChangeUserAccountData(
Expand All @@ -38,7 +39,7 @@ LexAuthService lexAuthService

[Error<NotFoundException>]
[Error<DbError>]
[Error<InvalidFormatException>]
[Error<UniqueValueException>]
[AdminRequired]
public Task<User> ChangeUserAccountByAdmin(
LoggedInContext loggedInContext,
Expand Down Expand Up @@ -78,21 +79,22 @@ LexAuthService lexAuthService

await dbContext.SaveChangesAsync();

if (!input.Email.IsNullOrEmpty() && !input.Email.Equals(user.Email))
if (!input.Email.IsNullOrEmpty() && !input.Email.Equals(user.Email, StringComparison.InvariantCultureIgnoreCase))
{
await SendVerifyNewAddressEmail(user, emailService, lexAuthService, input.Email);
await SendVerifyNewAddressEmail(user, emailService, lexAuthService, dbContext, input.Email);
}

return user;
}

private static async Task SendVerifyNewAddressEmail(
User user,
private static async Task SendVerifyNewAddressEmail(User user,
EmailService emailService,
LexAuthService lexAuthService,
string newEmail
)
LexBoxDbContext lexBoxDbContext,
string newEmail)
{
var emailInUse = await lexBoxDbContext.Users.AnyAsync(u => u.Email == newEmail);
if (emailInUse) throw new UniqueValueException("Email");
var (jwt, _) = lexAuthService.GenerateJwt(new LexAuthUser(user)
{
EmailVerificationRequired = null,
Expand Down
13 changes: 0 additions & 13 deletions backend/LexCore/Exceptions/FormatException.cs

This file was deleted.

8 changes: 8 additions & 0 deletions backend/LexCore/Exceptions/UniqueValueException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace LexCore.Exceptions;

public class UniqueValueException: Exception
{
public UniqueValueException(string field) : base($"The value for {field} is not unique")
{
}
}
3 changes: 3 additions & 0 deletions backend/LexData/Entities/UserEntityConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,16 @@ public override void Configure(EntityTypeBuilder<User> builder)
{
base.Configure(builder);
builder.Property(u => u.LocalizationCode).HasDefaultValue(User.DefaultLocalizationCode);
builder.Property(u => u.Email).UseCollation(LexBoxDbContext.CaseInsensitiveCollation);
builder.HasIndex(u => u.Email).IsUnique();
builder.HasMany(user => user.Projects)
.WithOne(projectUser => projectUser.User)
.HasForeignKey(projectUser => projectUser.UserId)
.OnDelete(DeleteBehavior.Cascade);
}
}


public static class UserEntityExtensions
{
public static IQueryable<User> FilterByEmail(this IQueryable<User> users, string email)
Expand Down
2 changes: 2 additions & 0 deletions backend/LexData/LexBoxDbContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ namespace LexData;

public class LexBoxDbContext : DbContext
{
public const string CaseInsensitiveCollation = "case_insensitive";
public LexBoxDbContext(DbContextOptions<LexBoxDbContext> options) : base(options)
{
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasCollation(CaseInsensitiveCollation, locale: "und-u-ks-level2", provider: "icu", deterministic: false);
modelBuilder.ApplyConfigurationsFromAssembly(typeof(EntityBaseConfiguration<>).Assembly);
}

Expand Down

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
@@ -0,0 +1,52 @@
using Microsoft.EntityFrameworkCore.Migrations;

#nullable disable

namespace LexData.Migrations
{
/// <inheritdoc />
public partial class AddEmailCaseInsenstiveCollation : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterDatabase()
.Annotation("Npgsql:CollationDefinition:case_insensitive", "und-u-ks-level2,und-u-ks-level2,icu,False");

migrationBuilder.AlterColumn<string>(
name: "Email",
table: "Users",
type: "text",
nullable: false,
collation: "case_insensitive",
oldClrType: typeof(string),
oldType: "text");

migrationBuilder.CreateIndex(
name: "IX_Users_Email",
table: "Users",
column: "Email",
unique: true);
}

/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_Users_Email",
table: "Users");

migrationBuilder.AlterDatabase()
.OldAnnotation("Npgsql:CollationDefinition:case_insensitive", "und-u-ks-level2,und-u-ks-level2,icu,False");

migrationBuilder.AlterColumn<string>(
name: "Email",
table: "Users",
type: "text",
nullable: false,
oldClrType: typeof(string),
oldType: "text",
oldCollation: "case_insensitive");
}
}
}
Loading