1
0
mirror of synced 2024-11-24 06:50:15 +01:00

Properly hash passwords

I can't believe this has gone unnoticed for so long.
This will rehash all passwords upon startup using bcrypt.
This commit is contained in:
Farewell_ 2024-10-24 12:40:02 +02:00
parent 00988bbc69
commit ef2711c7f6
6 changed files with 666 additions and 48 deletions

View File

@ -12,6 +12,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
<PackageReference Include="EntityFrameworkCore.Exceptions.Sqlite" Version="8.1.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.0-rc.2.23480.1" />

View File

@ -0,0 +1,572 @@
// <auto-generated />
using System;
using GameDatabase.Context;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace GameDatabase.Migrations
{
[DbContext(typeof(TaikoDbContext))]
[Migration("20241024092832_ReHashPasswords")]
partial class ReHashPasswords
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "8.0.4");
modelBuilder.Entity("GameDatabase.Entities.AiScoreDatum", b =>
{
b.Property<uint>("Baid")
.HasColumnType("INTEGER");
b.Property<uint>("SongId")
.HasColumnType("INTEGER");
b.Property<uint>("Difficulty")
.HasColumnType("INTEGER");
b.Property<bool>("IsWin")
.HasColumnType("INTEGER");
b.HasKey("Baid", "SongId", "Difficulty");
b.ToTable("AiScoreData");
});
modelBuilder.Entity("GameDatabase.Entities.AiSectionScoreDatum", b =>
{
b.Property<uint>("Baid")
.HasColumnType("INTEGER");
b.Property<uint>("SongId")
.HasColumnType("INTEGER");
b.Property<uint>("Difficulty")
.HasColumnType("INTEGER");
b.Property<int>("SectionIndex")
.HasColumnType("INTEGER");
b.Property<int>("Crown")
.HasColumnType("INTEGER");
b.Property<uint>("DrumrollCount")
.HasColumnType("INTEGER");
b.Property<uint>("GoodCount")
.HasColumnType("INTEGER");
b.Property<bool>("IsWin")
.HasColumnType("INTEGER");
b.Property<uint>("MissCount")
.HasColumnType("INTEGER");
b.Property<uint>("OkCount")
.HasColumnType("INTEGER");
b.Property<uint>("Score")
.HasColumnType("INTEGER");
b.HasKey("Baid", "SongId", "Difficulty", "SectionIndex");
b.ToTable("AiSectionScoreData");
});
modelBuilder.Entity("GameDatabase.Entities.Card", b =>
{
b.Property<string>("AccessCode")
.HasColumnType("TEXT");
b.Property<uint>("Baid")
.HasColumnType("INTEGER");
b.HasKey("AccessCode");
b.HasIndex("Baid");
b.ToTable("Card", (string)null);
});
modelBuilder.Entity("GameDatabase.Entities.Credential", b =>
{
b.Property<uint>("Baid")
.HasColumnType("INTEGER");
b.Property<string>("Password")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Salt")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Baid");
b.ToTable("Credential", (string)null);
});
modelBuilder.Entity("GameDatabase.Entities.DanScoreDatum", b =>
{
b.Property<uint>("Baid")
.HasColumnType("INTEGER");
b.Property<uint>("DanId")
.HasColumnType("INTEGER");
b.Property<int>("DanType")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(1);
b.Property<uint>("ArrivalSongCount")
.HasColumnType("INTEGER");
b.Property<uint>("ClearState")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(0u);
b.Property<uint>("ComboCountTotal")
.HasColumnType("INTEGER");
b.Property<uint>("SoulGaugeTotal")
.HasColumnType("INTEGER");
b.HasKey("Baid", "DanId", "DanType");
b.ToTable("DanScoreData");
});
modelBuilder.Entity("GameDatabase.Entities.DanStageScoreDatum", b =>
{
b.Property<uint>("Baid")
.HasColumnType("INTEGER");
b.Property<uint>("DanId")
.HasColumnType("INTEGER");
b.Property<int>("DanType")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(1);
b.Property<uint>("SongNumber")
.HasColumnType("INTEGER");
b.Property<uint>("BadCount")
.HasColumnType("INTEGER");
b.Property<uint>("ComboCount")
.HasColumnType("INTEGER");
b.Property<uint>("DrumrollCount")
.HasColumnType("INTEGER");
b.Property<uint>("GoodCount")
.HasColumnType("INTEGER");
b.Property<uint>("HighScore")
.HasColumnType("INTEGER");
b.Property<uint>("OkCount")
.HasColumnType("INTEGER");
b.Property<uint>("PlayScore")
.HasColumnType("INTEGER");
b.Property<uint>("TotalHitCount")
.HasColumnType("INTEGER");
b.HasKey("Baid", "DanId", "DanType", "SongNumber");
b.ToTable("DanStageScoreData");
});
modelBuilder.Entity("GameDatabase.Entities.SongBestDatum", b =>
{
b.Property<uint>("Baid")
.HasColumnType("INTEGER");
b.Property<uint>("SongId")
.HasColumnType("INTEGER");
b.Property<uint>("Difficulty")
.HasColumnType("INTEGER");
b.Property<uint>("BestCrown")
.HasColumnType("INTEGER");
b.Property<uint>("BestRate")
.HasColumnType("INTEGER");
b.Property<uint>("BestScore")
.HasColumnType("INTEGER");
b.Property<uint>("BestScoreRank")
.HasColumnType("INTEGER");
b.HasKey("Baid", "SongId", "Difficulty");
b.ToTable("SongBestData");
});
modelBuilder.Entity("GameDatabase.Entities.SongPlayDatum", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<uint>("Baid")
.HasColumnType("INTEGER");
b.Property<uint>("ComboCount")
.HasColumnType("INTEGER");
b.Property<uint>("Crown")
.HasColumnType("INTEGER");
b.Property<uint>("Difficulty")
.HasColumnType("INTEGER");
b.Property<uint>("DrumrollCount")
.HasColumnType("INTEGER");
b.Property<uint>("GoodCount")
.HasColumnType("INTEGER");
b.Property<uint>("HitCount")
.HasColumnType("INTEGER");
b.Property<uint>("MissCount")
.HasColumnType("INTEGER");
b.Property<uint>("OkCount")
.HasColumnType("INTEGER");
b.Property<DateTime>("PlayTime")
.HasColumnType("datetime");
b.Property<uint>("Score")
.HasColumnType("INTEGER");
b.Property<uint>("ScoreRank")
.HasColumnType("INTEGER");
b.Property<uint>("ScoreRate")
.HasColumnType("INTEGER");
b.Property<bool>("Skipped")
.HasColumnType("INTEGER");
b.Property<uint>("SongId")
.HasColumnType("INTEGER");
b.Property<uint>("SongNumber")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("Baid");
b.ToTable("SongPlayData");
});
modelBuilder.Entity("GameDatabase.Entities.Token", b =>
{
b.Property<uint>("Baid")
.HasColumnType("INTEGER");
b.Property<int>("Id")
.HasColumnType("INTEGER");
b.Property<int>("Count")
.HasColumnType("INTEGER");
b.HasKey("Baid", "Id");
b.ToTable("Tokens");
});
modelBuilder.Entity("GameDatabase.Entities.UserDatum", b =>
{
b.Property<uint>("Baid")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<uint>("AchievementDisplayDifficulty")
.HasColumnType("INTEGER");
b.Property<int>("AiWinCount")
.HasColumnType("INTEGER");
b.Property<uint>("ColorBody")
.HasColumnType("INTEGER");
b.Property<uint>("ColorFace")
.HasColumnType("INTEGER");
b.Property<uint>("ColorLimb")
.HasColumnType("INTEGER");
b.Property<string>("CostumeData")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("CostumeFlgArray")
.IsRequired()
.HasColumnType("TEXT");
b.Property<uint>("CurrentBody")
.HasColumnType("INTEGER");
b.Property<uint>("CurrentFace")
.HasColumnType("INTEGER");
b.Property<uint>("CurrentHead")
.HasColumnType("INTEGER");
b.Property<uint>("CurrentKigurumi")
.HasColumnType("INTEGER");
b.Property<uint>("CurrentPuchi")
.HasColumnType("INTEGER");
b.Property<string>("DifficultyPlayedArray")
.IsRequired()
.HasColumnType("TEXT");
b.Property<uint>("DifficultyPlayedCourse")
.HasColumnType("INTEGER");
b.Property<uint>("DifficultyPlayedSort")
.HasColumnType("INTEGER");
b.Property<uint>("DifficultyPlayedStar")
.HasColumnType("INTEGER");
b.Property<string>("DifficultySettingArray")
.IsRequired()
.HasColumnType("TEXT");
b.Property<uint>("DifficultySettingCourse")
.HasColumnType("INTEGER");
b.Property<uint>("DifficultySettingSort")
.HasColumnType("INTEGER");
b.Property<uint>("DifficultySettingStar")
.HasColumnType("INTEGER");
b.Property<bool>("DisplayAchievement")
.HasColumnType("INTEGER");
b.Property<bool>("DisplayDan")
.HasColumnType("INTEGER");
b.Property<string>("FavoriteSongsArray")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("GenericInfoFlgArray")
.IsRequired()
.HasColumnType("TEXT");
b.Property<bool>("IsAdmin")
.HasColumnType("INTEGER");
b.Property<bool>("IsSkipOn")
.HasColumnType("INTEGER");
b.Property<bool>("IsVoiceOn")
.HasColumnType("INTEGER");
b.Property<DateTime>("LastPlayDatetime")
.HasColumnType("datetime");
b.Property<uint>("LastPlayMode")
.HasColumnType("INTEGER");
b.Property<string>("MyDonName")
.IsRequired()
.HasColumnType("TEXT");
b.Property<uint>("MyDonNameLanguage")
.HasColumnType("INTEGER");
b.Property<int>("NotesPosition")
.HasColumnType("INTEGER");
b.Property<short>("OptionSetting")
.HasColumnType("INTEGER");
b.Property<uint>("SelectedToneId")
.HasColumnType("INTEGER");
b.Property<string>("Title")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("TitleFlgArray")
.IsRequired()
.HasColumnType("TEXT");
b.Property<uint>("TitlePlateId")
.HasColumnType("INTEGER");
b.Property<string>("ToneFlgArray")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("UnlockedBody")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("UnlockedFace")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("UnlockedHead")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("UnlockedKigurumi")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("UnlockedPuchi")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("UnlockedSongIdList")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Baid");
b.ToTable("UserData");
});
modelBuilder.Entity("GameDatabase.Entities.AiScoreDatum", b =>
{
b.HasOne("GameDatabase.Entities.UserDatum", "Ba")
.WithMany()
.HasForeignKey("Baid")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Ba");
});
modelBuilder.Entity("GameDatabase.Entities.AiSectionScoreDatum", b =>
{
b.HasOne("GameDatabase.Entities.AiScoreDatum", "Parent")
.WithMany("AiSectionScoreData")
.HasForeignKey("Baid", "SongId", "Difficulty")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Parent");
});
modelBuilder.Entity("GameDatabase.Entities.Card", b =>
{
b.HasOne("GameDatabase.Entities.UserDatum", "Ba")
.WithMany()
.HasForeignKey("Baid")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Ba");
});
modelBuilder.Entity("GameDatabase.Entities.Credential", b =>
{
b.HasOne("GameDatabase.Entities.UserDatum", "Ba")
.WithMany()
.HasForeignKey("Baid")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Ba");
});
modelBuilder.Entity("GameDatabase.Entities.DanScoreDatum", b =>
{
b.HasOne("GameDatabase.Entities.UserDatum", "Ba")
.WithMany()
.HasForeignKey("Baid")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Ba");
});
modelBuilder.Entity("GameDatabase.Entities.DanStageScoreDatum", b =>
{
b.HasOne("GameDatabase.Entities.DanScoreDatum", "Parent")
.WithMany("DanStageScoreData")
.HasForeignKey("Baid", "DanId", "DanType")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Parent");
});
modelBuilder.Entity("GameDatabase.Entities.SongBestDatum", b =>
{
b.HasOne("GameDatabase.Entities.UserDatum", "Ba")
.WithMany()
.HasForeignKey("Baid")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Ba");
});
modelBuilder.Entity("GameDatabase.Entities.SongPlayDatum", b =>
{
b.HasOne("GameDatabase.Entities.UserDatum", "Ba")
.WithMany()
.HasForeignKey("Baid")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Ba");
});
modelBuilder.Entity("GameDatabase.Entities.Token", b =>
{
b.HasOne("GameDatabase.Entities.UserDatum", "Datum")
.WithMany("Tokens")
.HasForeignKey("Baid")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Datum");
});
modelBuilder.Entity("GameDatabase.Entities.AiScoreDatum", b =>
{
b.Navigation("AiSectionScoreData");
});
modelBuilder.Entity("GameDatabase.Entities.DanScoreDatum", b =>
{
b.Navigation("DanStageScoreData");
});
modelBuilder.Entity("GameDatabase.Entities.UserDatum", b =>
{
b.Navigation("Tokens");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,53 @@
using BCrypt.Net;
using GameDatabase.Context;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Migrations;
using System.Text;
using System.Text.Json;
#nullable disable
namespace GameDatabase.Migrations
{
/// <inheritdoc />
public partial class ReHashPasswords : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
using var context = new TaikoDbContext();
var credentials = context.Credentials.ToList();
foreach (var credential in credentials)
{
// Check if the password is empty or null
if (!string.IsNullOrEmpty(credential.Password))
{
// Passwords are currenrly stored as a string containing Password + Salt encoded in base64 4 times.
// This is unacceptable so we'll rehash everything with bcrypt.
var decodedData = Encoding.UTF8.GetString(Convert.FromBase64String(credential.Password)); // First pass
decodedData = Encoding.UTF8.GetString(Convert.FromBase64String(decodedData)); // Second pass
decodedData = Encoding.UTF8.GetString(Convert.FromBase64String(decodedData)); // Third pass
decodedData = Encoding.UTF8.GetString(Convert.FromBase64String(decodedData)); // Last pass, leaving us with plain text password + salt
decodedData = decodedData.Substring(0, decodedData.Length - credential.Salt.Length); // Recovering plain text password
credential.Salt = BCrypt.Net.BCrypt.GenerateSalt(10);
credential.Password = BCrypt.Net.BCrypt.HashPassword(decodedData, credential.Salt); // Hashing the pass properly this time
Console.WriteLine("ReHashed password for baid " + credential.Baid + " (out of " + credentials.Count + " baids)");
}
}
context.SaveChanges();
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
// On Down we reset all passes and salts for everyone.
migrationBuilder.Sql(@"
UPDATE Credential
SET Password = '',
Salt = ''
");
}
}
}

View File

@ -15,7 +15,7 @@ namespace TaikoLocalServer.Migrations
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "8.0.3");
modelBuilder.HasAnnotation("ProductVersion", "8.0.4");
modelBuilder.Entity("GameDatabase.Entities.AiScoreDatum", b =>
{

View File

@ -43,15 +43,7 @@ public class AuthController(IAuthService authService, IUserDatumService userDatu
private static string ComputeHash(string inputPassword, string salt)
{
var encDataByte = Encoding.UTF8.GetBytes(inputPassword + salt);
var encodedData = Convert.ToBase64String(encDataByte);
encDataByte = Encoding.UTF8.GetBytes(encodedData);
encodedData = Convert.ToBase64String(encDataByte);
encDataByte = Encoding.UTF8.GetBytes(encodedData);
encodedData = Convert.ToBase64String(encDataByte);
encDataByte = Encoding.UTF8.GetBytes(encodedData);
encodedData = Convert.ToBase64String(encDataByte);
return encodedData;
return BCrypt.Net.BCrypt.HashPassword(inputPassword, salt);
}
private static Totp MakeTotp(uint baid)
@ -64,14 +56,7 @@ public class AuthController(IAuthService authService, IUserDatumService userDatu
private static string CreateSalt()
{
//Generate a cryptographic random number.
var randomNumber = new byte[32];
var rng = RandomNumberGenerator.Create();
rng.GetBytes(randomNumber);
var salt = Convert.ToBase64String(randomNumber);
// Return a Base64 string representation of the random number.
return salt;
return BCrypt.Net.BCrypt.GenerateSalt(10);
}
private static bool VerifyOtp(string otp, uint baid)
@ -150,6 +135,9 @@ public class AuthController(IAuthService authService, IUserDatumService userDatu
if (credential.Password != "")
return Unauthorized(new { message = "User Already Registered" });
if (password.Length <= 0)
return Unauthorized(new { message = "Password Cannot Be Empty !" });
if (registerWithLastPlayTime)
{
var invited = false;
@ -219,7 +207,10 @@ public class AuthController(IAuthService authService, IUserDatumService userDatu
var hashedOldPassword = ComputeHash(oldPassword, credential.Salt);
if (credential.Password != hashedOldPassword)
return Unauthorized(new { message = "Wrong Old Password" });
if (newPassword.Length <= 0)
return Unauthorized(new { message = "Password Cannot Be Empty !" });
// Hash the new password with the salt
var salt = CreateSalt();
var hashedNewPassword = ComputeHash(newPassword, salt);

View File

@ -28,34 +28,35 @@
</ItemGroup>
<ItemGroup>
<Compile Remove="Templates\TemplateController.cs"/>
<Compile Remove="Templates\TemplateController.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="DotNetZip" Version="1.16.0"/>
<PackageReference Include="MediatR" Version="12.2.0"/>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.4"/>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="8.0.4"/>
<PackageReference Include="Microsoft.AspNetCore.ResponseCompression" Version="2.2.0"/>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.4"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.4"/>
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
<PackageReference Include="DotNetZip" Version="1.16.0" />
<PackageReference Include="MediatR" Version="12.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.4" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="8.0.4" />
<PackageReference Include="Microsoft.AspNetCore.ResponseCompression" Version="2.2.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Otp.NET" Version="1.4.0"/>
<PackageReference Include="protobuf-net" Version="3.2.30"/>
<PackageReference Include="protobuf-net.AspNetCore" Version="3.2.12"/>
<PackageReference Include="Riok.Mapperly" Version="3.5.1"/>
<PackageReference Include="Serilog.AspNetCore" Version="8.0.2-dev-00334"/>
<PackageReference Include="Serilog.Expressions" Version="4.0.0"/>
<PackageReference Include="Serilog.Sinks.File.Header" Version="1.0.2"/>
<PackageReference Include="SharpZipLib" Version="1.4.2"/>
<PackageReference Include="Swan.Core" Version="7.0.0-beta.2"/>
<PackageReference Include="Swan.Logging" Version="6.0.2-beta.96"/>
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0"/>
<PackageReference Include="Throw" Version="1.4.0"/>
<PackageReference Include="Yoh.Text.Json.NamingPolicies" Version="1.1.2"/>
<PackageReference Include="Otp.NET" Version="1.4.0" />
<PackageReference Include="protobuf-net" Version="3.2.30" />
<PackageReference Include="protobuf-net.AspNetCore" Version="3.2.12" />
<PackageReference Include="Riok.Mapperly" Version="3.5.1" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.2-dev-00334" />
<PackageReference Include="Serilog.Expressions" Version="4.0.0" />
<PackageReference Include="Serilog.Sinks.File.Header" Version="1.0.2" />
<PackageReference Include="SharpZipLib" Version="1.4.2" />
<PackageReference Include="Swan.Core" Version="7.0.0-beta.2" />
<PackageReference Include="Swan.Logging" Version="6.0.2-beta.96" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
<PackageReference Include="Throw" Version="1.4.0" />
<PackageReference Include="Yoh.Text.Json.NamingPolicies" Version="1.1.2" />
</ItemGroup>
<ItemGroup>
@ -65,26 +66,26 @@
<None Update="Certificates\root.pfx">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<Content Remove="Configurations\ServerSettings.json"/>
<Content Remove="Configurations\ServerSettings.json" />
<None Include="Configurations\AuthSettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Configurations\ServerSettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<Content Remove="Configurations\Database.json"/>
<Content Remove="Configurations\Database.json" />
<None Include="Configurations\Database.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<Content Remove="Configurations\DataSettings.json"/>
<Content Remove="Configurations\DataSettings.json" />
<None Include="Configurations\DataSettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<Content Remove="Configurations\Kestrel.json"/>
<Content Remove="Configurations\Kestrel.json" />
<None Include="Configurations\Kestrel.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<Content Remove="Configurations\Logging.json"/>
<Content Remove="Configurations\Logging.json" />
<None Include="Configurations\Logging.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
@ -199,8 +200,8 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\GameDatabase\GameDatabase.csproj"/>
<ProjectReference Include="..\SharedProject\SharedProject.csproj"/>
<ProjectReference Include="..\TaikoWebUI\TaikoWebUI.csproj"/>
<ProjectReference Include="..\GameDatabase\GameDatabase.csproj" />
<ProjectReference Include="..\SharedProject\SharedProject.csproj" />
<ProjectReference Include="..\TaikoWebUI\TaikoWebUI.csproj" />
</ItemGroup>
</Project>