Add prompt for users without an email address set so they can set & verify one

This commit is contained in:
jvyden 2022-03-01 16:28:13 -05:00
commit cc2159d539
No known key found for this signature in database
GPG key ID: 18BCF2BE0262B278
12 changed files with 370 additions and 8 deletions

View file

@ -8,6 +8,7 @@ using LBPUnion.ProjectLighthouse.Types;
using LBPUnion.ProjectLighthouse.Types.Categories;
using LBPUnion.ProjectLighthouse.Types.Levels;
using LBPUnion.ProjectLighthouse.Types.Profiles;
using LBPUnion.ProjectLighthouse.Types.Profiles.Email;
using LBPUnion.ProjectLighthouse.Types.Reports;
using LBPUnion.ProjectLighthouse.Types.Reviews;
using LBPUnion.ProjectLighthouse.Types.Settings;
@ -41,6 +42,8 @@ public class Database : DbContext
public DbSet<DatabaseCategory> CustomCategories { get; set; }
public DbSet<Reaction> Reactions { get; set; }
public DbSet<GriefReport> Reports { get; set; }
public DbSet<EmailVerificationToken> EmailVerificationTokens { get; set; }
public DbSet<EmailSetToken> EmailSetTokens { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder options)
=> options.UseMySql(ServerSettings.Instance.DbConnectionString, MySqlServerVersion.LatestSupportedServerVersion);

View file

@ -0,0 +1,50 @@
using LBPUnion.ProjectLighthouse;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ProjectLighthouse.Migrations
{
[DbContext(typeof(Database))]
[Migration("20220301204930_AddEmailVerificationTokens")]
public partial class AddEmailVerificationTokens : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "EmailVerificationToken",
columns: table => new
{
EmailVerificationTokenId = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
UserId = table.Column<int>(type: "int", nullable: false),
EmailToken = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
},
constraints: table =>
{
table.PrimaryKey("PK_EmailVerificationToken", x => x.EmailVerificationTokenId);
table.ForeignKey(
name: "FK_EmailVerificationToken_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "UserId",
onDelete: ReferentialAction.Cascade);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateIndex(
name: "IX_EmailVerificationToken_UserId",
table: "EmailVerificationToken",
column: "UserId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "EmailVerificationToken");
}
}
}

View file

@ -0,0 +1,110 @@
using LBPUnion.ProjectLighthouse;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ProjectLighthouse.Migrations
{
[DbContext(typeof(Database))]
[Migration("20220301212120_SplitSetAndVerificationTokenTypes")]
public partial class SplitSetAndVerificationTokenTypes : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "EmailVerificationToken");
migrationBuilder.CreateTable(
name: "EmailSetTokens",
columns: table => new
{
EmailSetTokenId = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
UserId = table.Column<int>(type: "int", nullable: false),
EmailToken = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
},
constraints: table =>
{
table.PrimaryKey("PK_EmailSetTokens", x => x.EmailSetTokenId);
table.ForeignKey(
name: "FK_EmailSetTokens_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "UserId",
onDelete: ReferentialAction.Cascade);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "EmailVerificationTokens",
columns: table => new
{
EmailVerificationTokenId = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
UserId = table.Column<int>(type: "int", nullable: false),
EmailToken = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
},
constraints: table =>
{
table.PrimaryKey("PK_EmailVerificationTokens", x => x.EmailVerificationTokenId);
table.ForeignKey(
name: "FK_EmailVerificationTokens_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "UserId",
onDelete: ReferentialAction.Cascade);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateIndex(
name: "IX_EmailSetTokens_UserId",
table: "EmailSetTokens",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_EmailVerificationTokens_UserId",
table: "EmailVerificationTokens",
column: "UserId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "EmailSetTokens");
migrationBuilder.DropTable(
name: "EmailVerificationTokens");
migrationBuilder.CreateTable(
name: "EmailVerificationToken",
columns: table => new
{
EmailVerificationTokenId = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
UserId = table.Column<int>(type: "int", nullable: false),
EmailToken = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
},
constraints: table =>
{
table.PrimaryKey("PK_EmailVerificationToken", x => x.EmailVerificationTokenId);
table.ForeignKey(
name: "FK_EmailVerificationToken_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "UserId",
onDelete: ReferentialAction.Cascade);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateIndex(
name: "IX_EmailVerificationToken_UserId",
table: "EmailVerificationToken",
column: "UserId");
}
}
}

View file

@ -452,6 +452,44 @@ namespace ProjectLighthouse.Migrations
b.ToTable("Comments");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.Email.EmailSetToken", b =>
{
b.Property<int>("EmailSetTokenId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("EmailToken")
.HasColumnType("longtext");
b.Property<int>("UserId")
.HasColumnType("int");
b.HasKey("EmailSetTokenId");
b.HasIndex("UserId");
b.ToTable("EmailSetTokens");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.Email.EmailVerificationToken", b =>
{
b.Property<int>("EmailVerificationTokenId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("EmailToken")
.HasColumnType("longtext");
b.Property<int>("UserId")
.HasColumnType("int");
b.HasKey("EmailVerificationTokenId");
b.HasIndex("UserId");
b.ToTable("EmailVerificationTokens");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.LastContact", b =>
{
b.Property<int>("UserId")
@ -923,6 +961,28 @@ namespace ProjectLighthouse.Migrations
b.Navigation("Poster");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.Email.EmailSetToken", b =>
{
b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("User");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Profiles.Email.EmailVerificationToken", b =>
{
b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("User");
});
modelBuilder.Entity("LBPUnion.ProjectLighthouse.Types.Reports.GriefReport", b =>
{
b.HasOne("LBPUnion.ProjectLighthouse.Types.User", "ReportingPlayer")

View file

@ -8,6 +8,8 @@ using LBPUnion.ProjectLighthouse.Helpers.Extensions;
using LBPUnion.ProjectLighthouse.Logging;
using LBPUnion.ProjectLighthouse.Pages.Layouts;
using LBPUnion.ProjectLighthouse.Types;
using LBPUnion.ProjectLighthouse.Types.Profiles.Email;
using LBPUnion.ProjectLighthouse.Types.Settings;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
@ -64,6 +66,23 @@ public class LoginForm : BaseLayout
return this.Page();
}
if (user.EmailAddress == null && ServerSettings.Instance.SMTPEnabled)
{
Logger.Log($"User {user.Username} (id: {user.UserId}) failed to login; email not set", LoggerLevelLogin.Instance);
EmailSetToken emailSetToken = new()
{
UserId = user.UserId,
User = user,
EmailToken = HashHelper.GenerateAuthToken(),
};
this.Database.EmailSetTokens.Add(emailSetToken);
await this.Database.SaveChangesAsync();
return this.Redirect("/login/setEmail?token=" + emailSetToken.EmailToken);
}
WebToken webToken = new()
{
UserId = user.UserId,

View file

@ -43,13 +43,16 @@
</div>
</div>
<div class="field">
<label>Email address</label>
<div class="ui left icon input">
<input type="email" name="emailAddress" id="emailAddress" placeholder="Email Address">
<i class="mail icon"></i>
@if (ServerSettings.Instance.SMTPEnabled)
{
<div class="field">
<label>Email address</label>
<div class="ui left icon input">
<input type="email" name="emailAddress" id="emailAddress" placeholder="Email Address">
<i class="mail icon"></i>
</div>
</div>
</div>
}
<div class="field">
<label>Password</label>

View file

@ -17,7 +17,6 @@ public class RegisterForm : BaseLayout
{}
public string Error { get; private set; }
public bool WasRegisterRequest { get; private set; }
[UsedImplicitly]
[SuppressMessage("ReSharper", "SpecifyStringComparison")]
@ -37,7 +36,7 @@ public class RegisterForm : BaseLayout
return this.Page();
}
if (string.IsNullOrWhiteSpace(emailAddress))
if (string.IsNullOrWhiteSpace(emailAddress) && ServerSettings.Instance.SMTPEnabled)
{
this.Error = "Email address field is required.";
return this.Page();

View file

@ -0,0 +1,29 @@
@page "/login/setEmail"
@using LBPUnion.ProjectLighthouse.Types.Settings
@model LBPUnion.ProjectLighthouse.Pages.SetEmailForm
@{
Layout = "Layouts/BaseLayout";
Model.Title = "Set an Email Address";
}
<p>This instance requires email verification. As your account was created before this was a requirement, you must now set an email for your account before continuing.</p>
<form class="ui form" onsubmit="return onSubmit(this)" method="post">
@Html.AntiForgeryToken()
@if (ServerSettings.Instance.SMTPEnabled)
{
<div class="field">
<label>Please type a valid email address and verify it:</label>
<div class="ui left icon input">
<input type="email" name="emailAddress" id="emailAddress" placeholder="Email Address">
<i class="mail icon"></i>
</div>
<input type="hidden" name="token" id="token" value="@Model.EmailToken.EmailToken">
</div>
}
<input type="submit" value="Verify Email Address" id="submit" class="ui blue button">
</form>

View file

@ -0,0 +1,51 @@
#nullable enable
using System.Threading.Tasks;
using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Pages.Layouts;
using LBPUnion.ProjectLighthouse.Types.Profiles.Email;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace LBPUnion.ProjectLighthouse.Pages;
public class SetEmailForm : BaseLayout
{
public SetEmailForm(Database database) : base(database)
{}
public EmailSetToken EmailToken;
public async Task<IActionResult> OnGet(string? token = null)
{
if (token == null) return this.Redirect("/login");
EmailSetToken? emailToken = await this.Database.EmailSetTokens.FirstOrDefaultAsync(t => t.EmailToken == token);
if (emailToken == null) return this.Redirect("/login");
this.EmailToken = emailToken;
return this.Page();
}
public async Task<IActionResult> OnPost(string emailAddress, string token)
{
EmailSetToken? emailToken = await this.Database.EmailSetTokens.Include(t => t.User).FirstOrDefaultAsync(t => t.EmailToken == token);
if (emailToken == null) return this.Redirect("/login");
emailToken.User.EmailAddress = emailAddress;
this.Database.EmailSetTokens.Remove(emailToken);
EmailVerificationToken emailVerifyToken = new()
{
UserId = emailToken.UserId,
User = emailToken.User,
EmailToken = HashHelper.GenerateAuthToken(),
};
this.Database.EmailVerificationTokens.Add(emailVerifyToken);
await this.Database.SaveChangesAsync();
return this.Redirect("/login/verify?token=" + emailVerifyToken.EmailToken);
}
}

View file

@ -51,6 +51,10 @@
<None Remove="r.tar.gz"/>
</ItemGroup>
<ItemGroup>
<Compile Remove="Migrations\20220301204930_AddEmailVerificationTokens.Designer.cs"/>
</ItemGroup>
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
<Exec Command="git describe --long --always --dirty --exclude=\* --abbrev=8 &gt; &quot;$(ProjectDir)/gitVersion.txt&quot;"/>
<Exec Command="git branch --show-current &gt; &quot;$(ProjectDir)/gitBranch.txt&quot;"/>

View file

@ -0,0 +1,17 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace LBPUnion.ProjectLighthouse.Types.Profiles.Email;
public class EmailSetToken
{
[Key]
public int EmailSetTokenId { get; set; }
public int UserId { get; set; }
[ForeignKey(nameof(UserId))]
public User User { get; set; }
public string EmailToken { get; set; }
}

View file

@ -0,0 +1,17 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace LBPUnion.ProjectLighthouse.Types.Profiles.Email;
public class EmailVerificationToken
{
[Key]
public int EmailVerificationTokenId { get; set; }
public int UserId { get; set; }
[ForeignKey(nameof(UserId))]
public User User { get; set; }
public string EmailToken { get; set; }
}