diff --git a/ProjectLighthouse/Database.cs b/ProjectLighthouse/Database.cs index 345d0bb3..22bf6baa 100644 --- a/ProjectLighthouse/Database.cs +++ b/ProjectLighthouse/Database.cs @@ -86,7 +86,7 @@ public class Database : DbContext string body = "An account for Project Lighthouse has been registered with this email address.\n\n" + $"You can login at {ServerSettings.Instance.ExternalUrl}."; - SMTPHelper.SendEmail(emailAddress, "Account Created: " + username, body); + SMTPHelper.SendEmail(emailAddress, "Project Lighthouse Account Created: " + username, body); } return user; diff --git a/ProjectLighthouse/Migrations/20220301195426_AddEmailAddressToUser.Designer.cs b/ProjectLighthouse/Migrations/20220301195426_AddEmailAddressToUser.Designer.cs deleted file mode 100644 index 1cef4223..00000000 --- a/ProjectLighthouse/Migrations/20220301195426_AddEmailAddressToUser.Designer.cs +++ /dev/null @@ -1,1012 +0,0 @@ -// -using LBPUnion.ProjectLighthouse; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; - -#nullable disable - -namespace ProjectLighthouse.Migrations -{ - [DbContext(typeof(Database))] - [Migration("20220301195426_AddEmailAddressToUser")] - partial class AddEmailAddressToUser - { - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "6.0.2") - .HasAnnotation("Relational:MaxIdentifierLength", 64); - - modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.AuthenticationAttempt", b => - { - b.Property("AuthenticationAttemptId") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - b.Property("GameTokenId") - .HasColumnType("int"); - - b.Property("IPAddress") - .HasColumnType("longtext"); - - b.Property("Platform") - .HasColumnType("int"); - - b.Property("Timestamp") - .HasColumnType("bigint"); - - b.HasKey("AuthenticationAttemptId"); - - b.HasIndex("GameTokenId"); - - b.ToTable("AuthenticationAttempts"); - }); - - modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Categories.DatabaseCategory", b => - { - b.Property("CategoryId") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - b.Property("Description") - .HasColumnType("longtext"); - - b.Property("Endpoint") - .HasColumnType("longtext"); - - b.Property("IconHash") - .HasColumnType("longtext"); - - b.Property("Name") - .HasColumnType("longtext"); - - b.Property("SlotIdsCollection") - .HasColumnType("longtext"); - - b.HasKey("CategoryId"); - - b.ToTable("CustomCategories"); - }); - - modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.GameToken", b => - { - b.Property("TokenId") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - b.Property("Approved") - .HasColumnType("tinyint(1)"); - - b.Property("GameVersion") - .HasColumnType("int"); - - b.Property("Platform") - .HasColumnType("int"); - - b.Property("Used") - .HasColumnType("tinyint(1)"); - - b.Property("UserId") - .HasColumnType("int"); - - b.Property("UserLocation") - .HasColumnType("longtext"); - - b.Property("UserToken") - .HasColumnType("longtext"); - - b.HasKey("TokenId"); - - b.HasIndex("UserId"); - - b.ToTable("GameTokens"); - }); - - modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.HeartedProfile", b => - { - b.Property("HeartedProfileId") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - b.Property("HeartedUserId") - .HasColumnType("int"); - - b.Property("UserId") - .HasColumnType("int"); - - b.HasKey("HeartedProfileId"); - - b.HasIndex("HeartedUserId"); - - b.HasIndex("UserId"); - - b.ToTable("HeartedProfiles"); - }); - - modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.HeartedLevel", b => - { - b.Property("HeartedLevelId") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - b.Property("SlotId") - .HasColumnType("int"); - - b.Property("UserId") - .HasColumnType("int"); - - b.HasKey("HeartedLevelId"); - - b.HasIndex("SlotId"); - - b.HasIndex("UserId"); - - b.ToTable("HeartedLevels"); - }); - - modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.QueuedLevel", b => - { - b.Property("QueuedLevelId") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - b.Property("SlotId") - .HasColumnType("int"); - - b.Property("UserId") - .HasColumnType("int"); - - b.HasKey("QueuedLevelId"); - - b.HasIndex("SlotId"); - - b.HasIndex("UserId"); - - b.ToTable("QueuedLevels"); - }); - - modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.RatedLevel", b => - { - b.Property("RatedLevelId") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - b.Property("Rating") - .HasColumnType("int"); - - b.Property("RatingLBP1") - .HasColumnType("double"); - - b.Property("SlotId") - .HasColumnType("int"); - - b.Property("UserId") - .HasColumnType("int"); - - b.HasKey("RatedLevelId"); - - b.HasIndex("SlotId"); - - b.HasIndex("UserId"); - - b.ToTable("RatedLevels"); - }); - - modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.Slot", b => - { - b.Property("SlotId") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - b.Property("AuthorLabels") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("BackgroundHash") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("CreatorId") - .HasColumnType("int"); - - b.Property("CrossControllerRequired") - .HasColumnType("tinyint(1)"); - - b.Property("Description") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("FirstUploaded") - .HasColumnType("bigint"); - - b.Property("GameVersion") - .HasColumnType("int"); - - b.Property("IconHash") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("InitiallyLocked") - .HasColumnType("tinyint(1)"); - - b.Property("LastUpdated") - .HasColumnType("bigint"); - - b.Property("Lbp1Only") - .HasColumnType("tinyint(1)"); - - b.Property("LevelType") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("LocationId") - .HasColumnType("int"); - - b.Property("MaximumPlayers") - .HasColumnType("int"); - - b.Property("MinimumPlayers") - .HasColumnType("int"); - - b.Property("MoveRequired") - .HasColumnType("tinyint(1)"); - - b.Property("Name") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("PlaysLBP1") - .HasColumnType("int"); - - b.Property("PlaysLBP1Complete") - .HasColumnType("int"); - - b.Property("PlaysLBP1Unique") - .HasColumnType("int"); - - b.Property("PlaysLBP2") - .HasColumnType("int"); - - b.Property("PlaysLBP2Complete") - .HasColumnType("int"); - - b.Property("PlaysLBP2Unique") - .HasColumnType("int"); - - b.Property("PlaysLBP3") - .HasColumnType("int"); - - b.Property("PlaysLBP3Complete") - .HasColumnType("int"); - - b.Property("PlaysLBP3Unique") - .HasColumnType("int"); - - b.Property("PlaysLBPVita") - .HasColumnType("int"); - - b.Property("PlaysLBPVitaComplete") - .HasColumnType("int"); - - b.Property("PlaysLBPVitaUnique") - .HasColumnType("int"); - - b.Property("ResourceCollection") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("RootLevel") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("Shareable") - .HasColumnType("int"); - - b.Property("SubLevel") - .HasColumnType("tinyint(1)"); - - b.Property("TeamPick") - .HasColumnType("tinyint(1)"); - - b.HasKey("SlotId"); - - b.HasIndex("CreatorId"); - - b.HasIndex("LocationId"); - - b.ToTable("Slots"); - }); - - modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.VisitedLevel", b => - { - b.Property("VisitedLevelId") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - b.Property("PlaysLBP1") - .HasColumnType("int"); - - b.Property("PlaysLBP2") - .HasColumnType("int"); - - b.Property("PlaysLBP3") - .HasColumnType("int"); - - b.Property("PlaysLBPVita") - .HasColumnType("int"); - - b.Property("SlotId") - .HasColumnType("int"); - - b.Property("UserId") - .HasColumnType("int"); - - b.HasKey("VisitedLevelId"); - - b.HasIndex("SlotId"); - - b.HasIndex("UserId"); - - b.ToTable("VisitedLevels"); - }); - - modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Photo", b => - { - b.Property("PhotoId") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - b.Property("CreatorId") - .HasColumnType("int"); - - b.Property("LargeHash") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("MediumHash") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("PhotoSubjectCollection") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("PlanHash") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("SmallHash") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("Timestamp") - .HasColumnType("bigint"); - - b.HasKey("PhotoId"); - - b.HasIndex("CreatorId"); - - b.ToTable("Photos"); - }); - - modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.PhotoSubject", b => - { - b.Property("PhotoSubjectId") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - b.Property("Bounds") - .HasColumnType("longtext"); - - b.Property("UserId") - .HasColumnType("int"); - - b.HasKey("PhotoSubjectId"); - - b.HasIndex("UserId"); - - b.ToTable("PhotoSubjects"); - }); - - modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.Comment", b => - { - b.Property("CommentId") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - b.Property("Deleted") - .HasColumnType("tinyint(1)"); - - b.Property("DeletedBy") - .HasColumnType("longtext"); - - b.Property("DeletedType") - .HasColumnType("longtext"); - - b.Property("Message") - .HasColumnType("longtext"); - - b.Property("PosterUserId") - .HasColumnType("int"); - - b.Property("TargetId") - .HasColumnType("int"); - - b.Property("ThumbsDown") - .HasColumnType("int"); - - b.Property("ThumbsUp") - .HasColumnType("int"); - - b.Property("Timestamp") - .HasColumnType("bigint"); - - b.Property("Type") - .HasColumnType("int"); - - b.HasKey("CommentId"); - - b.HasIndex("PosterUserId"); - - b.ToTable("Comments"); - }); - - modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.LastContact", b => - { - b.Property("UserId") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - b.Property("GameVersion") - .HasColumnType("int"); - - b.Property("Platform") - .HasColumnType("int"); - - b.Property("Timestamp") - .HasColumnType("bigint"); - - b.HasKey("UserId"); - - b.ToTable("LastContacts"); - }); - - modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.Location", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - b.Property("X") - .HasColumnType("int"); - - b.Property("Y") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.ToTable("Locations"); - }); - - modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Reaction", b => - { - b.Property("RatingId") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - b.Property("Rating") - .HasColumnType("int"); - - b.Property("TargetId") - .HasColumnType("int"); - - b.Property("UserId") - .HasColumnType("int"); - - b.HasKey("RatingId"); - - b.ToTable("Reactions"); - }); - - modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Reports.GriefReport", b => - { - b.Property("ReportId") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - b.Property("Bounds") - .HasColumnType("longtext"); - - b.Property("GriefStateHash") - .HasColumnType("longtext"); - - b.Property("InitialStateHash") - .HasColumnType("longtext"); - - b.Property("JpegHash") - .HasColumnType("longtext"); - - b.Property("LevelId") - .HasColumnType("int"); - - b.Property("LevelOwner") - .HasColumnType("longtext"); - - b.Property("LevelType") - .HasColumnType("longtext"); - - b.Property("Players") - .HasColumnType("longtext"); - - b.Property("ReportingPlayerId") - .HasColumnType("int"); - - b.Property("Timestamp") - .HasColumnType("bigint"); - - b.Property("Type") - .HasColumnType("int"); - - b.HasKey("ReportId"); - - b.HasIndex("ReportingPlayerId"); - - b.ToTable("Reports"); - }); - - modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Reviews.RatedReview", b => - { - b.Property("RatedReviewId") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - b.Property("ReviewId") - .HasColumnType("int"); - - b.Property("Thumb") - .HasColumnType("int"); - - b.Property("UserId") - .HasColumnType("int"); - - b.HasKey("RatedReviewId"); - - b.HasIndex("ReviewId"); - - b.HasIndex("UserId"); - - b.ToTable("RatedReviews"); - }); - - modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Reviews.Review", b => - { - b.Property("ReviewId") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - b.Property("Deleted") - .HasColumnType("tinyint(1)"); - - b.Property("DeletedBy") - .HasColumnType("int"); - - b.Property("LabelCollection") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("ReviewerId") - .HasColumnType("int"); - - b.Property("SlotId") - .HasColumnType("int"); - - b.Property("Text") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("Thumb") - .HasColumnType("int"); - - b.Property("ThumbsDown") - .HasColumnType("int"); - - b.Property("ThumbsUp") - .HasColumnType("int"); - - b.Property("Timestamp") - .HasColumnType("bigint"); - - b.HasKey("ReviewId"); - - b.HasIndex("ReviewerId"); - - b.HasIndex("SlotId"); - - b.ToTable("Reviews"); - }); - - modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Score", b => - { - b.Property("ScoreId") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - b.Property("PlayerIdCollection") - .HasColumnType("longtext"); - - b.Property("Points") - .HasColumnType("int"); - - b.Property("SlotId") - .HasColumnType("int"); - - b.Property("Type") - .HasColumnType("int"); - - b.HasKey("ScoreId"); - - b.HasIndex("SlotId"); - - b.ToTable("Scores"); - }); - - modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.User", b => - { - b.Property("UserId") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - b.Property("Banned") - .HasColumnType("tinyint(1)"); - - b.Property("BannedReason") - .HasColumnType("longtext"); - - b.Property("Biography") - .HasColumnType("longtext"); - - b.Property("BooHash") - .HasColumnType("longtext"); - - b.Property("EmailAddress") - .HasColumnType("longtext"); - - b.Property("Game") - .HasColumnType("int"); - - b.Property("IconHash") - .HasColumnType("longtext"); - - b.Property("IsAdmin") - .HasColumnType("tinyint(1)"); - - b.Property("LocationId") - .HasColumnType("int"); - - b.Property("MehHash") - .HasColumnType("longtext"); - - b.Property("Password") - .HasColumnType("longtext"); - - b.Property("PasswordResetRequired") - .HasColumnType("tinyint(1)"); - - b.Property("Pins") - .HasColumnType("longtext"); - - b.Property("PlanetHashLBP2") - .HasColumnType("longtext"); - - b.Property("PlanetHashLBP3") - .HasColumnType("longtext"); - - b.Property("PlanetHashLBPVita") - .HasColumnType("longtext"); - - b.Property("Username") - .HasColumnType("longtext"); - - b.Property("YayHash") - .HasColumnType("longtext"); - - b.HasKey("UserId"); - - b.HasIndex("LocationId"); - - b.ToTable("Users"); - }); - - modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.UserApprovedIpAddress", b => - { - b.Property("UserApprovedIpAddressId") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - b.Property("IpAddress") - .HasColumnType("longtext"); - - b.Property("UserId") - .HasColumnType("int"); - - b.HasKey("UserApprovedIpAddressId"); - - b.HasIndex("UserId"); - - b.ToTable("UserApprovedIpAddresses"); - }); - - modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.WebToken", b => - { - b.Property("TokenId") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - b.Property("UserId") - .HasColumnType("int"); - - b.Property("UserToken") - .HasColumnType("longtext"); - - b.HasKey("TokenId"); - - b.ToTable("WebTokens"); - }); - - modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.AuthenticationAttempt", b => - { - b.HasOne("LBPUnion.ProjectLighthouse.Types.GameToken", "GameToken") - .WithMany() - .HasForeignKey("GameTokenId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("GameToken"); - }); - - modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.GameToken", b => - { - b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("User"); - }); - - modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.HeartedProfile", b => - { - b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "HeartedUser") - .WithMany() - .HasForeignKey("HeartedUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("HeartedUser"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.HeartedLevel", b => - { - b.HasOne("LBPUnion.ProjectLighthouse.Types.Levels.Slot", "Slot") - .WithMany() - .HasForeignKey("SlotId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Slot"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.QueuedLevel", b => - { - b.HasOne("LBPUnion.ProjectLighthouse.Types.Levels.Slot", "Slot") - .WithMany() - .HasForeignKey("SlotId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Slot"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.RatedLevel", b => - { - b.HasOne("LBPUnion.ProjectLighthouse.Types.Levels.Slot", "Slot") - .WithMany() - .HasForeignKey("SlotId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Slot"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.Slot", b => - { - b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "Creator") - .WithMany() - .HasForeignKey("CreatorId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("LBPUnion.ProjectLighthouse.Types.Profiles.Location", "Location") - .WithMany() - .HasForeignKey("LocationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Creator"); - - b.Navigation("Location"); - }); - - modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Levels.VisitedLevel", b => - { - b.HasOne("LBPUnion.ProjectLighthouse.Types.Levels.Slot", "Slot") - .WithMany() - .HasForeignKey("SlotId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Slot"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Photo", b => - { - b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "Creator") - .WithMany() - .HasForeignKey("CreatorId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Creator"); - }); - - modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.PhotoSubject", b => - { - b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("User"); - }); - - modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.Comment", b => - { - b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "Poster") - .WithMany() - .HasForeignKey("PosterUserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Poster"); - }); - - modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Reports.GriefReport", b => - { - b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "ReportingPlayer") - .WithMany() - .HasForeignKey("ReportingPlayerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("ReportingPlayer"); - }); - - modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Reviews.RatedReview", b => - { - b.HasOne("LBPUnion.ProjectLighthouse.Types.Reviews.Review", "Review") - .WithMany() - .HasForeignKey("ReviewId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Review"); - - b.Navigation("User"); - }); - - modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Reviews.Review", b => - { - b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "Reviewer") - .WithMany() - .HasForeignKey("ReviewerId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("LBPUnion.ProjectLighthouse.Types.Levels.Slot", "Slot") - .WithMany() - .HasForeignKey("SlotId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Reviewer"); - - b.Navigation("Slot"); - }); - - modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Score", b => - { - b.HasOne("LBPUnion.ProjectLighthouse.Types.Levels.Slot", "Slot") - .WithMany() - .HasForeignKey("SlotId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Slot"); - }); - - modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.User", b => - { - b.HasOne("LBPUnion.ProjectLighthouse.Types.Profiles.Location", "Location") - .WithMany() - .HasForeignKey("LocationId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Location"); - }); - - modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.UserApprovedIpAddress", b => - { - b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User") - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("User"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/ProjectLighthouse/Migrations/20220301195426_AddEmailAddressToUser.cs b/ProjectLighthouse/Migrations/20220301195426_AddEmailAddressToUser.cs index d30d4f9d..d466c49d 100644 --- a/ProjectLighthouse/Migrations/20220301195426_AddEmailAddressToUser.cs +++ b/ProjectLighthouse/Migrations/20220301195426_AddEmailAddressToUser.cs @@ -1,9 +1,13 @@ -using Microsoft.EntityFrameworkCore.Migrations; +using LBPUnion.ProjectLighthouse; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; #nullable disable namespace ProjectLighthouse.Migrations { + [DbContext(typeof(Database))] + [Migration("20220301195426_AddEmailAddressToUser")] public partial class AddEmailAddressToUser : Migration { protected override void Up(MigrationBuilder migrationBuilder) diff --git a/ProjectLighthouse/Migrations/20220302003658_AddEmailVerifiedToUser.cs b/ProjectLighthouse/Migrations/20220302003658_AddEmailVerifiedToUser.cs new file mode 100644 index 00000000..0a7e595c --- /dev/null +++ b/ProjectLighthouse/Migrations/20220302003658_AddEmailVerifiedToUser.cs @@ -0,0 +1,30 @@ +using LBPUnion.ProjectLighthouse; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace ProjectLighthouse.Migrations +{ + [DbContext(typeof(Database))] + [Migration("20220302003658_AddEmailVerifiedToUser")] + public partial class AddEmailVerifiedToUser : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "EmailAddressVerified", + table: "Users", + type: "tinyint(1)", + nullable: false, + defaultValue: false); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "EmailAddressVerified", + table: "Users"); + } + } +} diff --git a/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs b/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs index f51b106a..4de223d8 100644 --- a/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs +++ b/ProjectLighthouse/Migrations/DatabaseModelSnapshot.cs @@ -710,6 +710,9 @@ namespace ProjectLighthouse.Migrations b.Property("EmailAddress") .HasColumnType("longtext"); + b.Property("EmailAddressVerified") + .HasColumnType("tinyint(1)"); + b.Property("Game") .HasColumnType("int"); diff --git a/ProjectLighthouse/Pages/CompleteEmailVerificationPage.cshtml b/ProjectLighthouse/Pages/CompleteEmailVerificationPage.cshtml new file mode 100644 index 00000000..b00dd9aa --- /dev/null +++ b/ProjectLighthouse/Pages/CompleteEmailVerificationPage.cshtml @@ -0,0 +1,27 @@ +@page "/verifyEmail" +@model LBPUnion.ProjectLighthouse.Pages.CompleteEmailVerificationPage + +@{ + Layout = "Layouts/BaseLayout"; + + if (Model.Error == null) + { + Model.Title = "Email Address Verified"; + } + else + { + Model.Title = "Couldn't verify email address"; + } +} + +@if (Model.Error != null) +{ +

@Model.Error

+ +
Resend email
+
+} +else +{ +

Your email has been successfully verified. You may now close this tab.

+} \ No newline at end of file diff --git a/ProjectLighthouse/Pages/CompleteEmailVerificationPage.cshtml.cs b/ProjectLighthouse/Pages/CompleteEmailVerificationPage.cshtml.cs new file mode 100644 index 00000000..8552b7ba --- /dev/null +++ b/ProjectLighthouse/Pages/CompleteEmailVerificationPage.cshtml.cs @@ -0,0 +1,39 @@ +#nullable enable +using System.Threading.Tasks; +using JetBrains.Annotations; +using LBPUnion.ProjectLighthouse.Pages.Layouts; +using LBPUnion.ProjectLighthouse.Types; +using LBPUnion.ProjectLighthouse.Types.Profiles.Email; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace LBPUnion.ProjectLighthouse.Pages; + +public class CompleteEmailVerificationPage : BaseLayout +{ + public CompleteEmailVerificationPage([NotNull] Database database) : base(database) + {} + + public string? Error = null; + + public async Task OnGet(string token) + { + User? user = this.Database.UserFromWebRequest(this.Request); + if (user == null) return this.Redirect("~/login"); + + EmailVerificationToken? emailVerifyToken = await this.Database.EmailVerificationTokens.FirstOrDefaultAsync(e => e.EmailToken == token); + if (emailVerifyToken == null) + { + this.Error = "Invalid verification token"; + return this.Page(); + } + + this.Database.EmailVerificationTokens.Remove(emailVerifyToken); + + user.EmailAddressVerified = true; + + await this.Database.SaveChangesAsync(); + + return this.Page(); + } +} \ No newline at end of file diff --git a/ProjectLighthouse/Pages/LoginForm.cshtml.cs b/ProjectLighthouse/Pages/LoginForm.cshtml.cs index babb65d6..98dfdf67 100644 --- a/ProjectLighthouse/Pages/LoginForm.cshtml.cs +++ b/ProjectLighthouse/Pages/LoginForm.cshtml.cs @@ -105,6 +105,7 @@ public class LoginForm : BaseLayout Logger.Log($"User {user.Username} (id: {user.UserId}) successfully logged in on web", LoggerLevelLogin.Instance); if (user.PasswordResetRequired) return this.Redirect("~/passwordResetRequired"); + if (!user.EmailAddressVerified) return this.Redirect("~/login/sendVerificationEmail"); return this.RedirectToPage(nameof(LandingPage)); } diff --git a/ProjectLighthouse/Pages/PasswordResetPage.cshtml.cs b/ProjectLighthouse/Pages/PasswordResetPage.cshtml.cs index 51649f72..62b5129e 100644 --- a/ProjectLighthouse/Pages/PasswordResetPage.cshtml.cs +++ b/ProjectLighthouse/Pages/PasswordResetPage.cshtml.cs @@ -38,6 +38,8 @@ public class PasswordResetPage : BaseLayout await this.Database.SaveChangesAsync(); + if (!user.EmailAddressVerified) return this.Redirect("~/login/sendVerificationEmail"); + return this.Redirect("~/"); } diff --git a/ProjectLighthouse/Pages/SendVerificationEmailPage.cshtml b/ProjectLighthouse/Pages/SendVerificationEmailPage.cshtml new file mode 100644 index 00000000..09e0f2dc --- /dev/null +++ b/ProjectLighthouse/Pages/SendVerificationEmailPage.cshtml @@ -0,0 +1,14 @@ +@page "/login/sendVerificationEmail" +@model LBPUnion.ProjectLighthouse.Pages.SendVerificationEmailPage + +@{ + Layout = "Layouts/BaseLayout"; + Model.Title = "Verify Email Address"; +} + +

An email address on your account has been set, but hasn't been verified yet.

+

To verify it, check the email sent to @Model.User.EmailAddress and click the link in the email.

+ + +
Resend email
+
\ No newline at end of file diff --git a/ProjectLighthouse/Pages/SendVerificationEmailPage.cshtml.cs b/ProjectLighthouse/Pages/SendVerificationEmailPage.cshtml.cs new file mode 100644 index 00000000..f02a2244 --- /dev/null +++ b/ProjectLighthouse/Pages/SendVerificationEmailPage.cshtml.cs @@ -0,0 +1,47 @@ +#nullable enable +using System; +using System.Threading.Tasks; +using LBPUnion.ProjectLighthouse.Helpers; +using LBPUnion.ProjectLighthouse.Pages.Layouts; +using LBPUnion.ProjectLighthouse.Types; +using LBPUnion.ProjectLighthouse.Types.Profiles.Email; +using LBPUnion.ProjectLighthouse.Types.Settings; +using Microsoft.AspNetCore.Mvc; + +namespace LBPUnion.ProjectLighthouse.Pages; + +public class SendVerificationEmailPage : BaseLayout +{ + public SendVerificationEmailPage(Database database) : base(database) + {} + + public async Task OnGet() + { + User? user = this.Database.UserFromWebRequest(this.Request); + if (user == null) return this.Redirect("/login"); + + EmailVerificationToken verifyToken = new() + { + UserId = user.UserId, + User = user, + EmailToken = HashHelper.GenerateAuthToken(), + }; + + this.Database.EmailVerificationTokens.Add(verifyToken); + + await this.Database.SaveChangesAsync(); + + string body = "Hello,\n\n" + + $"A request to verify this email for your Project Lighthouse account ({user.Username}).\n\n" + + $"To verify your account, click this link: {ServerSettings.Instance.ExternalUrl}/verifyEmail?token={verifyToken.EmailToken}"; + + if (SMTPHelper.SendEmail(user.EmailAddress, "Project Lighthouse Email Verification", body)) + { + return this.Page(); + } + else + { + throw new Exception("failed to send email"); + } + } +} \ No newline at end of file diff --git a/ProjectLighthouse/Pages/SetEmailForm.cshtml.cs b/ProjectLighthouse/Pages/SetEmailForm.cshtml.cs index bd46778e..dfcb3c3e 100644 --- a/ProjectLighthouse/Pages/SetEmailForm.cshtml.cs +++ b/ProjectLighthouse/Pages/SetEmailForm.cshtml.cs @@ -1,8 +1,13 @@ #nullable enable +using System; using System.Threading.Tasks; +using Kettu; using LBPUnion.ProjectLighthouse.Helpers; +using LBPUnion.ProjectLighthouse.Logging; using LBPUnion.ProjectLighthouse.Pages.Layouts; +using LBPUnion.ProjectLighthouse.Types; using LBPUnion.ProjectLighthouse.Types.Profiles.Email; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; @@ -35,17 +40,39 @@ public class SetEmailForm : BaseLayout emailToken.User.EmailAddress = emailAddress; this.Database.EmailSetTokens.Remove(emailToken); + User user = emailToken.User; + EmailVerificationToken emailVerifyToken = new() { - UserId = emailToken.UserId, - User = emailToken.User, + UserId = user.UserId, + User = user, EmailToken = HashHelper.GenerateAuthToken(), }; this.Database.EmailVerificationTokens.Add(emailVerifyToken); + // The user just set their email address. Now, let's grant them a token to proceed with verifying the email. + WebToken webToken = new() + { + UserId = user.UserId, + UserToken = HashHelper.GenerateAuthToken(), + }; + + this.Response.Cookies.Append + ( + "LighthouseToken", + webToken.UserToken, + new CookieOptions + { + Expires = DateTimeOffset.Now.AddDays(7), + } + ); + + Logger.Log($"User {user.Username} (id: {user.UserId}) successfully logged in on web after setting an email address", LoggerLevelLogin.Instance); + + this.Database.WebTokens.Add(webToken); await this.Database.SaveChangesAsync(); - return this.Redirect("/login/verify?token=" + emailVerifyToken.EmailToken); + return this.Redirect("/login/sendVerificationEmail"); } } \ No newline at end of file diff --git a/ProjectLighthouse/Types/User.cs b/ProjectLighthouse/Types/User.cs index 35e7a7cc..6356d75c 100644 --- a/ProjectLighthouse/Types/User.cs +++ b/ProjectLighthouse/Types/User.cs @@ -18,6 +18,8 @@ public class User public string? EmailAddress { get; set; } = null; #nullable disable + public bool EmailAddressVerified { get; set; } = false; + [JsonIgnore] public string Password { get; set; }