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"
+ }
+}