ProjectLighthouse/ProjectLighthouse.Servers.Website/Pages/TwoFactor/SetupTwoFactorPage.cshtml.cs
Josh 329ab66043
Refactor serialization system (#702)
* Initial work for serialization refactor

* Experiment with new naming conventions

* Mostly implement user and slot serialization.
Still needs to be fine tuned to match original implementation
Many things are left in a broken state like website features/api endpoints/lbp3 categories

* Fix release building

* Migrate scores, reviews, and more to new serialization system.
Many things are still broken but progress is steadily being made

* Fix Api responses and migrate serialization for most types

* Make serialization better and fix bugs
Fix recursive PrepareSerialization when recursive item is set during root item's PrepareSerialization, items, should be properly indexed in order but it's only tested to 1 level of recursion

* Fix review serialization

* Fix user serialization producing malformed SQL query

* Remove DefaultIfEmpty query

* MariaDB doesn't like double nested queries

* Fix LBP1 tag counter

* Implement lbp3 categories and add better deserialization handling

* Implement expression tree caching to speed up reflection and write new serializer tests

* Remove Game column from UserEntity and rename DatabaseContextModelSnapshot.cs back to DatabaseModelSnapshot.cs

* Make UserEntity username not required

* Fix recursive serialization of lists and add relevant unit tests

* Actually commit the migration

* Fix LocationTests to use new deserialization class

* Fix comments not serializing the right author username

* Replace all occurrences of StatusCode with their respective ASP.NET named result
instead of StatusCode(403) everything is now in the form of Forbid()

* Fix SlotBase.ConvertToEntity and LocationTests

* Fix compilation error

* Give Location a default value in GameUserSlot and GameUser

* Reimplement stubbed website functions

* Convert grief reports to new serialization system

* Update DatabaseModelSnapshot and bump dotnet tool version

* Remove unused directives

* Fix broken type reference

* Fix rated comments on website

* Don't include banned users in website comments

* Optimize score submission

* Fix slot id calculating in in-game comment posting

* Move serialization interfaces to types folder and add more documentation

* Allow uploading of versus scores
2023-03-28 00:39:54 +00:00

136 lines
No EOL
5.1 KiB
C#

#nullable enable
using System.Security.Cryptography;
using System.Text;
using System.Web;
using LBPUnion.ProjectLighthouse.Configuration;
using LBPUnion.ProjectLighthouse.Database;
using LBPUnion.ProjectLighthouse.Helpers;
using LBPUnion.ProjectLighthouse.Localization.StringLists;
using LBPUnion.ProjectLighthouse.Servers.Website.Pages.Layouts;
using LBPUnion.ProjectLighthouse.Types.Entities.Profile;
using LBPUnion.ProjectLighthouse.Types.Entities.Token;
using Microsoft.AspNetCore.Mvc;
using QRCoder;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
namespace LBPUnion.ProjectLighthouse.Servers.Website.Pages.TwoFactor;
public class SetupTwoFactorPage : BaseLayout
{
public SetupTwoFactorPage(DatabaseContext database) : base(database)
{ }
public string QrCode { get; set; } = "";
public string Error { get; set; } = "";
public async Task<IActionResult> OnGet()
{
if (!ServerConfiguration.Instance.TwoFactorConfiguration.TwoFactorEnabled) return this.Redirect("~/login");
UserEntity? user = this.Database.UserFromWebRequest(this.Request);
if (user == null) return this.Redirect("~/login");
if (user.IsTwoFactorSetup) return this.Redirect("~/");
// Don't regenerate the two factor secret if they accidentally refresh the page
if (string.IsNullOrWhiteSpace(user.TwoFactorSecret)) user.TwoFactorSecret = CryptoHelper.GenerateTotpSecret();
this.QrCode = getQrCode(user);
await this.Database.SaveChangesAsync();
return this.Page();
}
private static string GenerateQrCode(string text, int pixelsPerModule, Color darkColor, Color lightColor, bool drawQuietZones)
{
QRCodeGenerator qrGenerator = new();
QRCodeData qrCodeData = qrGenerator.CreateQrCode(text, QRCodeGenerator.ECCLevel.Q);
int size = (qrCodeData.ModuleMatrix.Count - (drawQuietZones ? 0 : 8)) * pixelsPerModule;
int offset = drawQuietZones ? 0 : 4 * pixelsPerModule;
Image image = new Image<Rgba32>(size, size);
Rgba32 dark = darkColor.ToPixel<Rgba32>();
Rgba32 light = lightColor.ToPixel<Rgba32>();
image.Mutate(c => c.ProcessPixelRowsAsVector4((span, value) =>
{
for (int x = 0; x < span.Length; x++)
{
int y = value.Y;
int offsetX = x + offset;
int offsetY = y + offset;
bool module =
qrCodeData.ModuleMatrix[(offsetY + pixelsPerModule) / pixelsPerModule - 1][
(offsetX + pixelsPerModule) / pixelsPerModule - 1];
if (module)
{
span[x].X = dark.R / 255f;
span[x].Y = dark.G / 255f;
span[x].Z = dark.B / 255f;
span[x].W = dark.A / 255f;
}
else
{
span[x].X = light.R / 255f;
span[x].Y = light.G / 255f;
span[x].Z = light.B / 255f;
span[x].W = light.A / 255f;
}
}
}));
return image.ToBase64String(PngFormat.Instance);
}
private static string getQrCode(UserEntity user)
{
string instanceName = ServerConfiguration.Instance.Customization.ServerName;
string totpLink = CryptoHelper.GenerateTotpLink(user.TwoFactorSecret, HttpUtility.HtmlEncode(instanceName), user.Username);
return GenerateQrCode(totpLink, 6, Color.FromRgb(18, 18, 18), Color.Transparent, false);
}
public async Task<IActionResult> OnPost([FromForm] string? code)
{
if (!ServerConfiguration.Instance.TwoFactorConfiguration.TwoFactorEnabled) return this.Redirect("~/login");
WebTokenEntity? token = this.Database.WebTokenFromRequest(this.Request);
if (token == null) return this.Redirect("~/login");
UserEntity? user = this.Database.UserFromWebRequest(this.Request);
if (user == null) return this.Redirect("~/login");
if (user.IsTwoFactorSetup) return this.Redirect("~/");
if (CryptoHelper.VerifyCode(code, user.TwoFactorSecret))
{
List<string> backups = new();
const string alphabet = "abcdefghijklmnopqrstuvwxyz0123456789";
// 6 backup codes, format = [0-9a-z]{5}-[0-9a-z]{5}
for (int i = 0; i < 6; i++)
{
StringBuilder backupCode = new();
for (int j = 0; j < 10; j++)
{
backupCode.Append(alphabet[RandomNumberGenerator.GetInt32(0, alphabet.Length)]);
if (j == 4) backupCode.Append('-');
}
backups.Add(backupCode.ToString());
}
user.TwoFactorBackup = string.Join(",", backups);
token.Verified = true;
await this.Database.SaveChangesAsync();
return this.Page();
}
this.QrCode = getQrCode(user);
this.Error = this.Translate(TwoFactorStrings.InvalidCode);
return this.Page();
}
}