From d3d5f8ff08b083bb1bd6f316ae29e2d9efede95e Mon Sep 17 00:00:00 2001 From: Mateusz Skoczek Date: Wed, 24 Sep 2025 19:36:28 +0200 Subject: [PATCH] Initial --- .../.idea/.gitignore | 13 ++ .../.idea/encodings.xml | 4 + .../.idea/indexLayout.xml | 8 ++ .../.idea/vcs.xml | 6 + .../AuthenticatePasswordRequest.cs | 6 + .../AuthenticateResponse.cs | 6 + .../AuthenticateTokenRequest.cs | 6 + .../RegisterRequest.cs | 6 + ...ner.API.Services.Authentication.DTO.csproj | 9 ++ ...leDesigner.API.Services.Authentication.sln | 22 ++++ .../.dockerignore | 25 ++++ .../API/Endpoints.cs | 34 +++++ .../Configuration/AccountConfiguration.cs | 37 ++++++ .../RefreshTokenConfiguration.cs | 33 +++++ .../Database/DatabaseContext.cs | 23 ++++ .../20250924120541_Initial.Designer.cs | 119 ++++++++++++++++++ .../Migrations/20250924120541_Initial.cs | 81 ++++++++++++ .../DatabaseContextModelSnapshot.cs | 116 +++++++++++++++++ .../Database/Model/Account.cs | 13 ++ .../Database/Model/RefreshToken.cs | 12 ++ .../Dockerfile | 23 ++++ .../Program.cs | 59 +++++++++ .../Properties/launchSettings.json | 23 ++++ ...esigner.API.Services.Authentication.csproj | 27 ++++ .../appsettings.json | 12 ++ 25 files changed, 723 insertions(+) create mode 100644 .idea/.idea.TimetableDesigner.API.Services.Authentication/.idea/.gitignore create mode 100644 .idea/.idea.TimetableDesigner.API.Services.Authentication/.idea/encodings.xml create mode 100644 .idea/.idea.TimetableDesigner.API.Services.Authentication/.idea/indexLayout.xml create mode 100644 .idea/.idea.TimetableDesigner.API.Services.Authentication/.idea/vcs.xml create mode 100644 TimetableDesigner.API.Services.Authentication.DTO/AuthenticatePasswordRequest.cs create mode 100644 TimetableDesigner.API.Services.Authentication.DTO/AuthenticateResponse.cs create mode 100644 TimetableDesigner.API.Services.Authentication.DTO/AuthenticateTokenRequest.cs create mode 100644 TimetableDesigner.API.Services.Authentication.DTO/RegisterRequest.cs create mode 100644 TimetableDesigner.API.Services.Authentication.DTO/TimetableDesigner.API.Services.Authentication.DTO.csproj create mode 100644 TimetableDesigner.API.Services.Authentication.sln create mode 100644 TimetableDesigner.API.Services.Authentication/.dockerignore create mode 100644 TimetableDesigner.API.Services.Authentication/API/Endpoints.cs create mode 100644 TimetableDesigner.API.Services.Authentication/Database/Configuration/AccountConfiguration.cs create mode 100644 TimetableDesigner.API.Services.Authentication/Database/Configuration/RefreshTokenConfiguration.cs create mode 100644 TimetableDesigner.API.Services.Authentication/Database/DatabaseContext.cs create mode 100644 TimetableDesigner.API.Services.Authentication/Database/Migrations/20250924120541_Initial.Designer.cs create mode 100644 TimetableDesigner.API.Services.Authentication/Database/Migrations/20250924120541_Initial.cs create mode 100644 TimetableDesigner.API.Services.Authentication/Database/Migrations/DatabaseContextModelSnapshot.cs create mode 100644 TimetableDesigner.API.Services.Authentication/Database/Model/Account.cs create mode 100644 TimetableDesigner.API.Services.Authentication/Database/Model/RefreshToken.cs create mode 100644 TimetableDesigner.API.Services.Authentication/Dockerfile create mode 100644 TimetableDesigner.API.Services.Authentication/Program.cs create mode 100644 TimetableDesigner.API.Services.Authentication/Properties/launchSettings.json create mode 100644 TimetableDesigner.API.Services.Authentication/TimetableDesigner.API.Services.Authentication.csproj create mode 100644 TimetableDesigner.API.Services.Authentication/appsettings.json diff --git a/.idea/.idea.TimetableDesigner.API.Services.Authentication/.idea/.gitignore b/.idea/.idea.TimetableDesigner.API.Services.Authentication/.idea/.gitignore new file mode 100644 index 0000000..d21ea17 --- /dev/null +++ b/.idea/.idea.TimetableDesigner.API.Services.Authentication/.idea/.gitignore @@ -0,0 +1,13 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Rider ignored files +/.idea.TimetableDesigner.API.Services.Authentication.iml +/projectSettingsUpdater.xml +/modules.xml +/contentModel.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/.idea.TimetableDesigner.API.Services.Authentication/.idea/encodings.xml b/.idea/.idea.TimetableDesigner.API.Services.Authentication/.idea/encodings.xml new file mode 100644 index 0000000..df87cf9 --- /dev/null +++ b/.idea/.idea.TimetableDesigner.API.Services.Authentication/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/.idea.TimetableDesigner.API.Services.Authentication/.idea/indexLayout.xml b/.idea/.idea.TimetableDesigner.API.Services.Authentication/.idea/indexLayout.xml new file mode 100644 index 0000000..7b08163 --- /dev/null +++ b/.idea/.idea.TimetableDesigner.API.Services.Authentication/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/.idea.TimetableDesigner.API.Services.Authentication/.idea/vcs.xml b/.idea/.idea.TimetableDesigner.API.Services.Authentication/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/.idea.TimetableDesigner.API.Services.Authentication/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/TimetableDesigner.API.Services.Authentication.DTO/AuthenticatePasswordRequest.cs b/TimetableDesigner.API.Services.Authentication.DTO/AuthenticatePasswordRequest.cs new file mode 100644 index 0000000..b24daec --- /dev/null +++ b/TimetableDesigner.API.Services.Authentication.DTO/AuthenticatePasswordRequest.cs @@ -0,0 +1,6 @@ +namespace TimetableDesigner.API.Services.Authentication.DTO; + +public class AuthenticatePasswordRequest +{ + +} \ No newline at end of file diff --git a/TimetableDesigner.API.Services.Authentication.DTO/AuthenticateResponse.cs b/TimetableDesigner.API.Services.Authentication.DTO/AuthenticateResponse.cs new file mode 100644 index 0000000..ef7ed7f --- /dev/null +++ b/TimetableDesigner.API.Services.Authentication.DTO/AuthenticateResponse.cs @@ -0,0 +1,6 @@ +namespace TimetableDesigner.API.Services.Authentication.DTO; + +public class AuthenticateResponse +{ + +} \ No newline at end of file diff --git a/TimetableDesigner.API.Services.Authentication.DTO/AuthenticateTokenRequest.cs b/TimetableDesigner.API.Services.Authentication.DTO/AuthenticateTokenRequest.cs new file mode 100644 index 0000000..1146d1d --- /dev/null +++ b/TimetableDesigner.API.Services.Authentication.DTO/AuthenticateTokenRequest.cs @@ -0,0 +1,6 @@ +namespace TimetableDesigner.API.Services.Authentication.DTO; + +public class AuthenticateTokenRequest +{ + +} \ No newline at end of file diff --git a/TimetableDesigner.API.Services.Authentication.DTO/RegisterRequest.cs b/TimetableDesigner.API.Services.Authentication.DTO/RegisterRequest.cs new file mode 100644 index 0000000..2cdb45d --- /dev/null +++ b/TimetableDesigner.API.Services.Authentication.DTO/RegisterRequest.cs @@ -0,0 +1,6 @@ +namespace TimetableDesigner.API.Services.Authentication.DTO; + +public class RegisterRequest +{ + +} \ No newline at end of file diff --git a/TimetableDesigner.API.Services.Authentication.DTO/TimetableDesigner.API.Services.Authentication.DTO.csproj b/TimetableDesigner.API.Services.Authentication.DTO/TimetableDesigner.API.Services.Authentication.DTO.csproj new file mode 100644 index 0000000..17b910f --- /dev/null +++ b/TimetableDesigner.API.Services.Authentication.DTO/TimetableDesigner.API.Services.Authentication.DTO.csproj @@ -0,0 +1,9 @@ + + + + net9.0 + enable + enable + + + diff --git a/TimetableDesigner.API.Services.Authentication.sln b/TimetableDesigner.API.Services.Authentication.sln new file mode 100644 index 0000000..0c6751d --- /dev/null +++ b/TimetableDesigner.API.Services.Authentication.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TimetableDesigner.API.Services.Authentication", "TimetableDesigner.API.Services.Authentication/TimetableDesigner.API.Services.Authentication.csproj", "{F8C0AEF3-B53F-4904-90F7-EE4A8587F023}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TimetableDesigner.API.Services.Authentication.DTO", "TimetableDesigner.API.Services.Authentication.DTO\TimetableDesigner.API.Services.Authentication.DTO.csproj", "{384C8036-ACA7-40EB-924D-5E0271BEDB09}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {F8C0AEF3-B53F-4904-90F7-EE4A8587F023}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F8C0AEF3-B53F-4904-90F7-EE4A8587F023}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F8C0AEF3-B53F-4904-90F7-EE4A8587F023}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F8C0AEF3-B53F-4904-90F7-EE4A8587F023}.Release|Any CPU.Build.0 = Release|Any CPU + {384C8036-ACA7-40EB-924D-5E0271BEDB09}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {384C8036-ACA7-40EB-924D-5E0271BEDB09}.Debug|Any CPU.Build.0 = Debug|Any CPU + {384C8036-ACA7-40EB-924D-5E0271BEDB09}.Release|Any CPU.ActiveCfg = Release|Any CPU + {384C8036-ACA7-40EB-924D-5E0271BEDB09}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/TimetableDesigner.API.Services.Authentication/.dockerignore b/TimetableDesigner.API.Services.Authentication/.dockerignore new file mode 100644 index 0000000..cd967fc --- /dev/null +++ b/TimetableDesigner.API.Services.Authentication/.dockerignore @@ -0,0 +1,25 @@ +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/.idea +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/azds.yaml +**/bin +**/charts +**/docker-compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +LICENSE +README.md \ No newline at end of file diff --git a/TimetableDesigner.API.Services.Authentication/API/Endpoints.cs b/TimetableDesigner.API.Services.Authentication/API/Endpoints.cs new file mode 100644 index 0000000..9dfbd15 --- /dev/null +++ b/TimetableDesigner.API.Services.Authentication/API/Endpoints.cs @@ -0,0 +1,34 @@ +using Microsoft.AspNetCore.Http.HttpResults; +using TimetableDesigner.API.Services.Authentication.DTO; + +namespace TimetableDesigner.API.Services.Authentication.API; + +public static class Endpoints +{ + public static IEndpointRouteBuilder MapEndpoints(this IEndpointRouteBuilder app) + { + app.MapPost("/register", Register) + .WithName("Register"); + app.MapPost("/authenticate_password", AuthenticatePassword) + .WithName("AuthenticatePassword"); + app.MapPost("/authenticate_token", AuthenticateToken) + .WithName("AuthenticateToken"); + + return app; + } + + public static async Task, ProblemHttpResult>> Register(RegisterRequest request) + { + return null; + } + + public static async Task, ProblemHttpResult>> AuthenticatePassword(AuthenticatePasswordRequest request) + { + return null; + } + + public static async Task, ProblemHttpResult>> AuthenticateToken(AuthenticateTokenRequest request) + { + return null; + } +} \ No newline at end of file diff --git a/TimetableDesigner.API.Services.Authentication/Database/Configuration/AccountConfiguration.cs b/TimetableDesigner.API.Services.Authentication/Database/Configuration/AccountConfiguration.cs new file mode 100644 index 0000000..acd2493 --- /dev/null +++ b/TimetableDesigner.API.Services.Authentication/Database/Configuration/AccountConfiguration.cs @@ -0,0 +1,37 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using TimetableDesigner.API.Services.Authentication.Database.Model; + +namespace TimetableDesigner.API.Services.Authentication.Database.Configuration; + +public class AccountConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(x => x.Id); + builder.HasIndex(x => x.Id) + .IsUnique(); + builder.Property(x => x.Id) + .IsRequired() + .UseIdentityAlwaysColumn(); + + builder.Property(x => x.Email) + .HasMaxLength(320) + .IsRequired(); + + builder.Property(x => x.Password) + .HasMaxLength(1000) + .IsRequired(); + + builder.Property(x => x.PasswordSaltLeft) + .HasMaxLength(20) + .IsRequired(); + + builder.Property(x => x.PasswordSaltRight) + .HasMaxLength(20) + .IsRequired(); + + builder.Property(b => b.Version) + .IsRowVersion(); + } +} \ No newline at end of file diff --git a/TimetableDesigner.API.Services.Authentication/Database/Configuration/RefreshTokenConfiguration.cs b/TimetableDesigner.API.Services.Authentication/Database/Configuration/RefreshTokenConfiguration.cs new file mode 100644 index 0000000..d5c7293 --- /dev/null +++ b/TimetableDesigner.API.Services.Authentication/Database/Configuration/RefreshTokenConfiguration.cs @@ -0,0 +1,33 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using TimetableDesigner.API.Services.Authentication.Database.Model; + +namespace TimetableDesigner.API.Services.Authentication.Database.Configuration; + +public class RefreshTokenConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(x => x.Token); + builder.HasIndex(x => x.Token) + .IsUnique(); + builder.Property(x => x.Token) + .IsRequired(); + + builder.HasOne(x => x.Account) + .WithMany(x => x.RefreshTokens) + .HasForeignKey(x => x.AccountId) + .IsRequired(); + builder.Property(x => x.AccountId) + .IsRequired(); + + builder.Property(x => x.ExpirationDate) + .IsRequired(); + + builder.Property(x => x.IsExtendable) + .IsRequired(); + + builder.Property(b => b.Version) + .IsRowVersion(); + } +} \ No newline at end of file diff --git a/TimetableDesigner.API.Services.Authentication/Database/DatabaseContext.cs b/TimetableDesigner.API.Services.Authentication/Database/DatabaseContext.cs new file mode 100644 index 0000000..75173ce --- /dev/null +++ b/TimetableDesigner.API.Services.Authentication/Database/DatabaseContext.cs @@ -0,0 +1,23 @@ +using System.Reflection; +using Microsoft.EntityFrameworkCore; +using TimetableDesigner.API.Services.Authentication.Database.Model; + +namespace TimetableDesigner.API.Services.Authentication.Database; + +public class DatabaseContext : DbContext +{ + public virtual DbSet Accounts { get; set; } + public virtual DbSet RefreshTokens { get; set; } + + + public DatabaseContext() { } + + public DatabaseContext(DbContextOptions options) : base(options) { } + + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => + optionsBuilder.UseNpgsql("name=Database"); + + protected override void OnModelCreating(ModelBuilder modelBuilder) => + modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetAssembly(typeof(DatabaseContext))!); +} \ No newline at end of file diff --git a/TimetableDesigner.API.Services.Authentication/Database/Migrations/20250924120541_Initial.Designer.cs b/TimetableDesigner.API.Services.Authentication/Database/Migrations/20250924120541_Initial.Designer.cs new file mode 100644 index 0000000..0eec242 --- /dev/null +++ b/TimetableDesigner.API.Services.Authentication/Database/Migrations/20250924120541_Initial.Designer.cs @@ -0,0 +1,119 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using TimetableDesigner.API.Services.Authentication.Database; + +#nullable disable + +namespace TimetableDesigner.API.Services.Authentication.Database.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20250924120541_Initial")] + partial class Initial + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.9") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("TimetableDesigner.API.Services.Authentication.Database.Model.Account", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); + + b.Property("Email") + .IsRequired() + .HasMaxLength(320) + .HasColumnType("character varying(320)"); + + b.Property("Password") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("bytea"); + + b.Property("PasswordSaltLeft") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("PasswordSaltRight") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Version") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("Accounts"); + }); + + modelBuilder.Entity("TimetableDesigner.API.Services.Authentication.Database.Model.RefreshToken", b => + { + b.Property("Token") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountId") + .HasColumnType("bigint"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("IsExtendable") + .HasColumnType("boolean"); + + b.Property("Version") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.HasKey("Token"); + + b.HasIndex("AccountId"); + + b.HasIndex("Token") + .IsUnique(); + + b.ToTable("RefreshTokens"); + }); + + modelBuilder.Entity("TimetableDesigner.API.Services.Authentication.Database.Model.RefreshToken", b => + { + b.HasOne("TimetableDesigner.API.Services.Authentication.Database.Model.Account", "Account") + .WithMany("RefreshTokens") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("TimetableDesigner.API.Services.Authentication.Database.Model.Account", b => + { + b.Navigation("RefreshTokens"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/TimetableDesigner.API.Services.Authentication/Database/Migrations/20250924120541_Initial.cs b/TimetableDesigner.API.Services.Authentication/Database/Migrations/20250924120541_Initial.cs new file mode 100644 index 0000000..860d197 --- /dev/null +++ b/TimetableDesigner.API.Services.Authentication/Database/Migrations/20250924120541_Initial.cs @@ -0,0 +1,81 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace TimetableDesigner.API.Services.Authentication.Database.Migrations +{ + /// + public partial class Initial : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Accounts", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityAlwaysColumn), + Email = table.Column(type: "character varying(320)", maxLength: 320, nullable: false), + Password = table.Column(type: "bytea", maxLength: 1000, nullable: false), + PasswordSaltLeft = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + PasswordSaltRight = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + xmin = table.Column(type: "xid", rowVersion: true, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Accounts", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "RefreshTokens", + columns: table => new + { + Token = table.Column(type: "uuid", nullable: false), + AccountId = table.Column(type: "bigint", nullable: false), + ExpirationDate = table.Column(type: "timestamp with time zone", nullable: false), + IsExtendable = table.Column(type: "boolean", nullable: false), + xmin = table.Column(type: "xid", rowVersion: true, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_RefreshTokens", x => x.Token); + table.ForeignKey( + name: "FK_RefreshTokens_Accounts_AccountId", + column: x => x.AccountId, + principalTable: "Accounts", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_Accounts_Id", + table: "Accounts", + column: "Id", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_RefreshTokens_AccountId", + table: "RefreshTokens", + column: "AccountId"); + + migrationBuilder.CreateIndex( + name: "IX_RefreshTokens_Token", + table: "RefreshTokens", + column: "Token", + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "RefreshTokens"); + + migrationBuilder.DropTable( + name: "Accounts"); + } + } +} diff --git a/TimetableDesigner.API.Services.Authentication/Database/Migrations/DatabaseContextModelSnapshot.cs b/TimetableDesigner.API.Services.Authentication/Database/Migrations/DatabaseContextModelSnapshot.cs new file mode 100644 index 0000000..811d7b3 --- /dev/null +++ b/TimetableDesigner.API.Services.Authentication/Database/Migrations/DatabaseContextModelSnapshot.cs @@ -0,0 +1,116 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using TimetableDesigner.API.Services.Authentication.Database; + +#nullable disable + +namespace TimetableDesigner.API.Services.Authentication.Database.Migrations +{ + [DbContext(typeof(DatabaseContext))] + partial class DatabaseContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.9") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("TimetableDesigner.API.Services.Authentication.Database.Model.Account", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id")); + + b.Property("Email") + .IsRequired() + .HasMaxLength(320) + .HasColumnType("character varying(320)"); + + b.Property("Password") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("bytea"); + + b.Property("PasswordSaltLeft") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("PasswordSaltRight") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Version") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.HasKey("Id"); + + b.HasIndex("Id") + .IsUnique(); + + b.ToTable("Accounts"); + }); + + modelBuilder.Entity("TimetableDesigner.API.Services.Authentication.Database.Model.RefreshToken", b => + { + b.Property("Token") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccountId") + .HasColumnType("bigint"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("IsExtendable") + .HasColumnType("boolean"); + + b.Property("Version") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("xid") + .HasColumnName("xmin"); + + b.HasKey("Token"); + + b.HasIndex("AccountId"); + + b.HasIndex("Token") + .IsUnique(); + + b.ToTable("RefreshTokens"); + }); + + modelBuilder.Entity("TimetableDesigner.API.Services.Authentication.Database.Model.RefreshToken", b => + { + b.HasOne("TimetableDesigner.API.Services.Authentication.Database.Model.Account", "Account") + .WithMany("RefreshTokens") + .HasForeignKey("AccountId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Account"); + }); + + modelBuilder.Entity("TimetableDesigner.API.Services.Authentication.Database.Model.Account", b => + { + b.Navigation("RefreshTokens"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/TimetableDesigner.API.Services.Authentication/Database/Model/Account.cs b/TimetableDesigner.API.Services.Authentication/Database/Model/Account.cs new file mode 100644 index 0000000..674fb3c --- /dev/null +++ b/TimetableDesigner.API.Services.Authentication/Database/Model/Account.cs @@ -0,0 +1,13 @@ +namespace TimetableDesigner.API.Services.Authentication.Database.Model; + +public class Account +{ + public long Id { get; set; } + public string Email { get; set; } = null!; + public byte[] Password { get; set; } = null!; + public string PasswordSaltLeft { get; set; } = null!; + public string PasswordSaltRight { get; set; } = null!; + public uint Version { get; set; } + + public virtual IEnumerable RefreshTokens { get; set; } = new List(); +} \ No newline at end of file diff --git a/TimetableDesigner.API.Services.Authentication/Database/Model/RefreshToken.cs b/TimetableDesigner.API.Services.Authentication/Database/Model/RefreshToken.cs new file mode 100644 index 0000000..6697a98 --- /dev/null +++ b/TimetableDesigner.API.Services.Authentication/Database/Model/RefreshToken.cs @@ -0,0 +1,12 @@ +namespace TimetableDesigner.API.Services.Authentication.Database.Model; + +public class RefreshToken +{ + public Guid Token { get; set; } + public long AccountId { get; set; } + public DateTimeOffset ExpirationDate { get; set; } + public bool IsExtendable { get; set; } + public uint Version { get; set; } + + public virtual Account Account { get; set; } = null!; +} \ No newline at end of file diff --git a/TimetableDesigner.API.Services.Authentication/Dockerfile b/TimetableDesigner.API.Services.Authentication/Dockerfile new file mode 100644 index 0000000..e5fa0e6 --- /dev/null +++ b/TimetableDesigner.API.Services.Authentication/Dockerfile @@ -0,0 +1,23 @@ +FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base +USER $APP_UID +WORKDIR /app +EXPOSE 8080 +EXPOSE 8081 + +FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build +ARG BUILD_CONFIGURATION=Release +WORKDIR /src +COPY ["TimetableDesigner.API.Services.Authentication.csproj", "./"] +RUN dotnet restore "TimetableDesigner.API.Services.Authentication.csproj" +COPY . . +WORKDIR "/src/" +RUN dotnet build "./TimetableDesigner.API.Services.Authentication.csproj" -c $BUILD_CONFIGURATION -o /app/build + +FROM build AS publish +ARG BUILD_CONFIGURATION=Release +RUN dotnet publish "./TimetableDesigner.API.Services.Authentication.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "TimetableDesigner.API.Services.Authentication.dll"] diff --git a/TimetableDesigner.API.Services.Authentication/Program.cs b/TimetableDesigner.API.Services.Authentication/Program.cs new file mode 100644 index 0000000..b468675 --- /dev/null +++ b/TimetableDesigner.API.Services.Authentication/Program.cs @@ -0,0 +1,59 @@ +using Microsoft.EntityFrameworkCore; +using TimetableDesigner.API.Services.Authentication.API; +using TimetableDesigner.API.Services.Authentication.Database; + +namespace TimetableDesigner.API.Services.Authentication; + +public static class Program +{ + public static void Main(string[] args) + { + WebApplication app = WebApplication.CreateBuilder(args) + .SetupOpenApi() + .SetupSecurity() + .SetupDatabase() + .Build(); + + if (app.Environment.IsDevelopment()) + app.MapOpenApi(); + app.InitializeDatabase(); + app.UseHttpsRedirection(); + app.MapEndpoints(); + + app.Run(); + } + + private static WebApplicationBuilder SetupOpenApi(this WebApplicationBuilder builder) + { + builder.Services.AddOpenApi(); + return builder; + } + + private static WebApplicationBuilder SetupSecurity(this WebApplicationBuilder builder) + { + //builder.Services.AddAuthorization(); + return builder; + } + + private static WebApplicationBuilder SetupDatabase(this WebApplicationBuilder builder) + { + builder.Services.AddDbContext(x => x.UseNpgsql(builder.Configuration.GetConnectionString("Database")), ServiceLifetime.Transient); + return builder; + } + + private static WebApplication InitializeDatabase(this WebApplication app) + { + using (IServiceScope scope = app.Services.CreateScope()) + { + DatabaseContext database = scope.ServiceProvider.GetRequiredService(); + while (!database.Database.CanConnect()) + { + app.Logger.LogInformation("Waiting for database..."); + Thread.Sleep(1000); + } + + database.Database.Migrate(); + } + return app; + } +} \ No newline at end of file diff --git a/TimetableDesigner.API.Services.Authentication/Properties/launchSettings.json b/TimetableDesigner.API.Services.Authentication/Properties/launchSettings.json new file mode 100644 index 0000000..f3b051b --- /dev/null +++ b/TimetableDesigner.API.Services.Authentication/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:7001;http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/TimetableDesigner.API.Services.Authentication/TimetableDesigner.API.Services.Authentication.csproj b/TimetableDesigner.API.Services.Authentication/TimetableDesigner.API.Services.Authentication.csproj new file mode 100644 index 0000000..e26f049 --- /dev/null +++ b/TimetableDesigner.API.Services.Authentication/TimetableDesigner.API.Services.Authentication.csproj @@ -0,0 +1,27 @@ + + + + net9.0 + enable + enable + Linux + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + diff --git a/TimetableDesigner.API.Services.Authentication/appsettings.json b/TimetableDesigner.API.Services.Authentication/appsettings.json new file mode 100644 index 0000000..65ac104 --- /dev/null +++ b/TimetableDesigner.API.Services.Authentication/appsettings.json @@ -0,0 +1,12 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "ConnectionStrings": { + "Database": "Host=localhost;Port=5433;Database=ttd_authentication;Username=postgres;Password=l4JxOIuSoyod86N;Include Error Detail=True" + } +}