Skip to content
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
35 changes: 34 additions & 1 deletion Refresh.Database/GameDatabaseContext.Registration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ public bool IsUsernameTaken(string username, GameUser? userToName = null)
{
if (this.GameUsers.Any(u => u.Username == username)) return true;
if (this.QueuedRegistrations.Any(r => r.Username == username)) return true;
if (this.IsUserDisallowed(username)) return true;

PreviousUsername? previous = this.PreviousUsernames.FirstOrDefault(p => p.Username == username);
// no one has ever had this name before
Expand All @@ -113,7 +114,8 @@ public bool IsUsernameTaken(string username, GameUser? userToName = null)
public bool IsEmailTaken(string emailAddress)
{
return this.GameUsers.Any(u => u.EmailAddress == emailAddress) ||
this.QueuedRegistrations.Any(r => r.EmailAddress == emailAddress);
this.QueuedRegistrations.Any(r => r.EmailAddress == emailAddress) ||
this.IsEmailDisallowed(emailAddress);
}

public void AddRegistrationToQueue(string username, string emailAddress, string passwordBcrypt)
Expand Down Expand Up @@ -250,4 +252,35 @@ public bool IsUserDisallowed(string username)
{
return this.DisallowedUsers.FirstOrDefault(u => u.Username == username) != null;
}

public bool DisallowEmail(string email)
{
if (this.IsEmailDisallowed(email))
return false;

this.DisallowedEmails.Add(new()
{
Email = email,
});
this.SaveChanges();

return true;
}

public bool ReallowEmail(string email)
{
DisallowedEmail? disallowedEmail = this.DisallowedEmails.FirstOrDefault(u => u.Email == email);
if (disallowedEmail == null)
return false;

this.DisallowedEmails.Remove(disallowedEmail);
this.SaveChanges();

return true;
}

public bool IsEmailDisallowed(string email)
{
return this.DisallowedEmails.Any(u => u.Email == email);
}
}
50 changes: 34 additions & 16 deletions Refresh.Database/GameDatabaseContext.Users.cs
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,8 @@ public void DeleteUser(GameUser user)
this.BanUser(user, deletedReason, DateTimeOffset.MaxValue);
this.RevokeAllTokensForUser(user);
this.DeleteNotificationsByUser(user);
if (user.EmailAddress != null)
this.DisallowEmail(user.EmailAddress);

this.Write(() =>
{
Expand All @@ -402,7 +404,14 @@ public void DeleteUser(GameUser user)
user.Lbp2PlanetsHash = "0";
user.Lbp3PlanetsHash = "0";
user.VitaPlanetsHash = "0";
user.BetaPlanetsHash = "0";
user.IconHash = "0";
user.VitaIconHash = "0";
user.BetaIconHash = "0";
user.PspIconHash = "0";
user.YayFaceHash = "0";
user.BooFaceHash = "0";
user.MehFaceHash = "0";
user.AllowIpAuthentication = false;
user.EmailAddressVerified = false;
user.PsnAuthenticationAllowed = false;
Expand All @@ -413,27 +422,36 @@ public void DeleteUser(GameUser user)
subject.UserId = null;
}

this.FavouriteLevelRelations.RemoveRange(r => r.User == user);
this.FavouriteUserRelations.RemoveRange(r => r.UserToFavourite == user);
this.FavouriteUserRelations.RemoveRange(r => r.UserFavouriting == user);
this.QueueLevelRelations.RemoveRange(r => r.User == user);
this.GamePhotos.RemoveRange(p => p.Publisher == user);
this.GameUserVerifiedIpRelations.RemoveRange(p => p.User == user);
this.FavouriteLevelRelations.RemoveRange(r => r.UserId == user.UserId);
this.FavouriteUserRelations.RemoveRange(r => r.UserToFavouriteId == user.UserId);
this.FavouriteUserRelations.RemoveRange(r => r.UserFavouritingId == user.UserId);
this.QueueLevelRelations.RemoveRange(r => r.UserId == user.UserId);
this.TagLevelRelations.RemoveRange(r => r.UserId == user.UserId);
this.GameUserVerifiedIpRelations.RemoveRange(p => p.UserId == user.UserId);

this.GameNotifications.RemoveRange(s => s.UserId == user.UserId);
this.GamePhotos.RemoveRange(p => p.PublisherId == user.UserId);
this.Events.RemoveRange(e => e.UserId == user.UserId);
this.GameScores.RemoveRange(s => s.PublisherId == user.UserId);
this.GameChallengeScores.RemoveRange(s => s.PublisherUserId == user.UserId);

this.GameLevelComments.RemoveRange(s => s.AuthorUserId == user.UserId);
this.LevelCommentRelations.RemoveRange(s => s.UserId == user.UserId);
this.GameProfileComments.RemoveRange(s => s.AuthorUserId == user.UserId);
this.ProfileCommentRelations.RemoveRange(s => s.UserId == user.UserId);
this.GameReviews.RemoveRange(s => s.PublisherUserId == user.UserId);
this.RateReviewRelations.RemoveRange(s => s.UserId == user.UserId);

foreach (GameScore score in this.GameScores.ToList())
{
if (!score.PlayerIds.Contains(user.UserId)) continue;
this.GameScores.Remove(score);
}

foreach (GameLevel level in this.GameLevels.Where(l => l.Publisher == user))
this.PinProgressRelations.RemoveRange(s => s.PublisherId == user.UserId);
this.ProfilePinRelations.RemoveRange(s => s.PublisherId == user.UserId);
this.GamePlaylists.RemoveRange(s => s.PublisherId == user.UserId);

foreach (GameLevel level in this.GameLevels.Where(l => l.PublisherUserId == user.UserId))
{
level.Publisher = null;
}

this.GameChallengeScores.RemoveRange(s => s.Publisher == user);

foreach (GameChallenge challenge in this.GameChallenges.Where(c => c.Publisher == user))
foreach (GameChallenge challenge in this.GameChallenges.Where(c => c.PublisherUserId == user.UserId))
{
challenge.Publisher = null;
}
Expand Down
1 change: 1 addition & 0 deletions Refresh.Database/GameDatabaseContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ public partial class GameDatabaseContext : DbContext, IDatabaseContext
internal DbSet<AssetDependencyRelation> AssetDependencyRelations { get; set; }
internal DbSet<GameReview> GameReviews { get; set; }
internal DbSet<DisallowedUser> DisallowedUsers { get; set; }
internal DbSet<DisallowedEmail> DisallowedEmails { get; set; }
internal DbSet<RateReviewRelation> RateReviewRelations { get; set; }
internal DbSet<TagLevelRelation> TagLevelRelations { get; set; }
internal DbSet<GamePlaylist> GamePlaylists { get; set; }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;

#nullable disable

namespace Refresh.Database.Migrations
{
/// <inheritdoc />
[DbContext(typeof(GameDatabaseContext))]
[Migration("20260312203825_AddAbilityToDisallowEmails")]
public partial class AddAbilityToDisallowEmails : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "DisallowedEmails",
columns: table => new
{
Email = table.Column<string>(type: "text", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_DisallowedEmails", x => x.Email);
});
}

/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "DisallowedEmails");
}
}
}
10 changes: 10 additions & 0 deletions Refresh.Database/Migrations/GameDatabaseContextModelSnapshot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1510,6 +1510,16 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.ToTable("RequestStatistics");
});

modelBuilder.Entity("Refresh.Database.Models.Users.DisallowedEmail", b =>
{
b.Property<string>("Email")
.HasColumnType("text");

b.HasKey("Email");

b.ToTable("DisallowedEmails");
});

modelBuilder.Entity("Refresh.Database.Models.Users.DisallowedUser", b =>
{
b.Property<string>("Username")
Expand Down
6 changes: 5 additions & 1 deletion Refresh.Database/Models/Relations/LevelCommentRelation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ namespace Refresh.Database.Models.Relations;
public partial class LevelCommentRelation : ICommentRelation<GameLevelComment>
{
[Key] public ObjectId CommentRelationId { get; set; } = ObjectId.GenerateNewId();
[Required] public GameUser User { get; set; }

[ForeignKey(nameof(UserId)), Required]
public GameUser User { get; set; }
[Required] public ObjectId UserId { get; set; }

[Required] public GameLevelComment Comment { get; set; }
public RatingType RatingType { get; set; }
public DateTimeOffset Timestamp { get; set; }
Expand Down
6 changes: 5 additions & 1 deletion Refresh.Database/Models/Relations/ProfileCommentRelation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ namespace Refresh.Database.Models.Relations;
public partial class ProfileCommentRelation : ICommentRelation<GameProfileComment>
{
[Key] public ObjectId CommentRelationId { get; set; } = ObjectId.GenerateNewId();
[Required] public GameUser User { get; set; }

[ForeignKey(nameof(UserId)), Required]
public GameUser User { get; set; }
[Required] public ObjectId UserId { get; set; }

[Required] public GameProfileComment Comment { get; set; }
public RatingType RatingType { get; set; }
public DateTimeOffset Timestamp { get; set; }
Expand Down
9 changes: 9 additions & 0 deletions Refresh.Database/Models/Users/DisallowedEmail.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Refresh.Database.Models.Users;

#nullable disable

public partial class DisallowedEmail
{
[Key]
public string Email { get; set; }
}
26 changes: 25 additions & 1 deletion Refresh.GameServer/CommandLineManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ private class Options

[Option("reallow-user", HelpText = "Re-allow a user to register. Username option is required if this is set.")]
public bool ReallowUser { get; set; }

[Option("disallow-email", HelpText = "Disallow the email from being used by anyone in the future. Email option is required if this is set.")]
public bool DisallowEmail { get; set; }

[Option("reallow-email", HelpText = "Re-allow the email to be used by anyone. Email option is required if this is set")]
public bool ReallowEmail { get; set; }

[Option("rename-user", HelpText = "Changes a user's username. (old) username or Email option is required if this is set.")]
public string? RenameUser { get; set; }
Expand Down Expand Up @@ -188,10 +194,28 @@ private void StartWithOptions(Options options)
}
else Fail("No username was provided");
}
else if (options.DisallowEmail)
{
if (options.EmailAddress != null)
{
if (!this._server.DisallowEmail(options.EmailAddress))
Fail("Email address is already disallowed");
}
else Fail("No email address was provided");
}
else if (options.ReallowEmail)
{
if (options.EmailAddress != null)
{
if (!this._server.ReallowEmail(options.EmailAddress))
Fail("Email address is already allowed");
}
else Fail("No email address was provided");
}
else if (options.RenameUser != null)
{
if(string.IsNullOrWhiteSpace(options.RenameUser))
Fail("Username must contain content");
Fail("Email address must contain content");

GameUser user = this.GetUserOrFail(options);
this._server.RenameUser(user, options.RenameUser, options.Force);
Expand Down
14 changes: 14 additions & 0 deletions Refresh.GameServer/RefreshGameServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,20 @@ public bool ReallowUser(string username)
return context.ReallowUser(username);
}

public bool DisallowEmail(string email)
{
using GameDatabaseContext context = this.GetContext();

return context.DisallowEmail(email);
}

public bool ReallowEmail(string email)
{
using GameDatabaseContext context = this.GetContext();

return context.ReallowEmail(email);
}

public void RenameUser(GameUser user, string newUsername, bool force = false)
{
using GameDatabaseContext context = this.GetContext();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ public ApiResponse<IApiAuthenticationResponse> Register(RequestContext context,
if (!smtpService.CheckEmailDomainValidity(body.EmailAddress))
return ApiValidationError.EmailDoesNotActuallyExistError;

if (database.IsUserDisallowed(body.Username))
if (database.IsUserDisallowed(body.Username) || database.IsEmailDisallowed(body.EmailAddress))
return new ApiAuthenticationError("You aren't allowed to play on this instance.");

if (!database.IsUsernameValid(body.Username))
Expand Down
22 changes: 22 additions & 0 deletions RefreshTests.GameServer/Tests/ApiV3/UserApiTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,28 @@ public void RegisterAccount()
context.Database.Refresh();
Assert.That(context.Database.GetUserByUsername(username), Is.Not.EqualTo(null));
}

[Test]
public void CannotRegisterAccountWithDisallowedEmail()
{
using TestContext context = this.GetServer();

const string email = "guy@lil.com";
context.Database.DisallowEmail(email);

ApiResponse<ApiAuthenticationResponse>? response = context.Http.PostData<ApiAuthenticationResponse>("/api/v3/register", new ApiRegisterRequest
{
Username = "a_lil_guy",
EmailAddress = email,
PasswordSha512 = "ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff",
}, false, true);
Assert.That(response, Is.Not.Null);
Assert.That(response!.Error, Is.Not.Null);
Assert.That(response.Error!.Name, Is.EqualTo("ApiAuthenticationError"));

context.Database.Refresh();
Assert.That(context.Database.GetUserByEmailAddress(email), Is.Null);
}

[Test]
public void CannotRegisterAccountWithDisallowedUsername()
Expand Down
16 changes: 16 additions & 0 deletions RefreshTests.GameServer/Tests/Users/UserActionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,20 @@ public void CanResetOwnIcon(string newIcon)
Assert.That(userUpdated, Is.Not.Null);
Assert.That(userUpdated!.IconHash, Is.EqualTo("0"));
}

[Test]
public void DeletingUserDisallowsEmail()
{
using TestContext context = this.GetServer();
GameUser publisher = context.CreateUser();
Assert.That(publisher.EmailAddress, Is.Not.Null);
Assert.That(context.Database.IsEmailDisallowed(publisher.EmailAddress!), Is.False);
string email = publisher.EmailAddress!;

// Delete publisher
context.Database.DeleteUser(publisher);
context.Database.Refresh();

Assert.That(context.Database.IsEmailDisallowed(email), Is.True);
}
}
Loading