Compare commits
7 Commits
feature/ci
...
feature/lo
| Author | SHA1 | Date | |
|---|---|---|---|
| a5727f0f51 | |||
| efd69f63b0 | |||
| 2e93f3d04e | |||
| 0ef2c73c71 | |||
| 883252c049 | |||
| 9e7ec186cd | |||
| dffd31cd0f |
10
USEntryCoach.Server/Data/ApplicationDbContext.cs
Normal file
10
USEntryCoach.Server/Data/ApplicationDbContext.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace USEntryCoach.Server.Data;
|
||||
|
||||
public class ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : IdentityDbContext<IdentityUser>(options)
|
||||
{
|
||||
|
||||
}
|
||||
277
USEntryCoach.Server/Migrations/20250527123305_Initial.Designer.cs
generated
Normal file
277
USEntryCoach.Server/Migrations/20250527123305_Initial.Designer.cs
generated
Normal file
@ -0,0 +1,277 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
using USEntryCoach.Server.Data;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace USEntryCoach.Server.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20250527123305_Initial")]
|
||||
partial class Initial
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "9.0.5")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("NormalizedName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedName")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("RoleNameIndex");
|
||||
|
||||
b.ToTable("AspNetRoles", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetRoleClaims", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<int>("AccessFailedCount")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<bool>("EmailConfirmed")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("LockoutEnabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<DateTimeOffset?>("LockoutEnd")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("NormalizedEmail")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("NormalizedUserName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("PhoneNumber")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool>("PhoneNumberConfirmed")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("SecurityStamp")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool>("TwoFactorEnabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("UserName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedEmail")
|
||||
.HasDatabaseName("EmailIndex");
|
||||
|
||||
b.HasIndex("NormalizedUserName")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UserNameIndex");
|
||||
|
||||
b.ToTable("AspNetUsers", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserClaims", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ProviderKey")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ProviderDisplayName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("LoginProvider", "ProviderKey");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserLogins", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("UserId", "RoleId");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetUserRoles", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("UserId", "LoginProvider", "Name");
|
||||
|
||||
b.ToTable("AspNetUserTokens", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
223
USEntryCoach.Server/Migrations/20250527123305_Initial.cs
Normal file
223
USEntryCoach.Server/Migrations/20250527123305_Initial.cs
Normal file
@ -0,0 +1,223 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace USEntryCoach.Server.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Initial : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "AspNetRoles",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
Name = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
|
||||
NormalizedName = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
|
||||
ConcurrencyStamp = table.Column<string>(type: "text", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_AspNetRoles", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "AspNetUsers",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
UserName = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
|
||||
NormalizedUserName = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
|
||||
Email = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
|
||||
NormalizedEmail = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
|
||||
EmailConfirmed = table.Column<bool>(type: "boolean", nullable: false),
|
||||
PasswordHash = table.Column<string>(type: "text", nullable: true),
|
||||
SecurityStamp = table.Column<string>(type: "text", nullable: true),
|
||||
ConcurrencyStamp = table.Column<string>(type: "text", nullable: true),
|
||||
PhoneNumber = table.Column<string>(type: "text", nullable: true),
|
||||
PhoneNumberConfirmed = table.Column<bool>(type: "boolean", nullable: false),
|
||||
TwoFactorEnabled = table.Column<bool>(type: "boolean", nullable: false),
|
||||
LockoutEnd = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
|
||||
LockoutEnabled = table.Column<bool>(type: "boolean", nullable: false),
|
||||
AccessFailedCount = table.Column<int>(type: "integer", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_AspNetUsers", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "AspNetRoleClaims",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
RoleId = table.Column<string>(type: "text", nullable: false),
|
||||
ClaimType = table.Column<string>(type: "text", nullable: true),
|
||||
ClaimValue = table.Column<string>(type: "text", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_AspNetRoleClaims_AspNetRoles_RoleId",
|
||||
column: x => x.RoleId,
|
||||
principalTable: "AspNetRoles",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "AspNetUserClaims",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
UserId = table.Column<string>(type: "text", nullable: false),
|
||||
ClaimType = table.Column<string>(type: "text", nullable: true),
|
||||
ClaimValue = table.Column<string>(type: "text", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_AspNetUserClaims", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_AspNetUserClaims_AspNetUsers_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "AspNetUsers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "AspNetUserLogins",
|
||||
columns: table => new
|
||||
{
|
||||
LoginProvider = table.Column<string>(type: "text", nullable: false),
|
||||
ProviderKey = table.Column<string>(type: "text", nullable: false),
|
||||
ProviderDisplayName = table.Column<string>(type: "text", nullable: true),
|
||||
UserId = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey });
|
||||
table.ForeignKey(
|
||||
name: "FK_AspNetUserLogins_AspNetUsers_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "AspNetUsers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "AspNetUserRoles",
|
||||
columns: table => new
|
||||
{
|
||||
UserId = table.Column<string>(type: "text", nullable: false),
|
||||
RoleId = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId });
|
||||
table.ForeignKey(
|
||||
name: "FK_AspNetUserRoles_AspNetRoles_RoleId",
|
||||
column: x => x.RoleId,
|
||||
principalTable: "AspNetRoles",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_AspNetUserRoles_AspNetUsers_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "AspNetUsers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "AspNetUserTokens",
|
||||
columns: table => new
|
||||
{
|
||||
UserId = table.Column<string>(type: "text", nullable: false),
|
||||
LoginProvider = table.Column<string>(type: "text", nullable: false),
|
||||
Name = table.Column<string>(type: "text", nullable: false),
|
||||
Value = table.Column<string>(type: "text", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name });
|
||||
table.ForeignKey(
|
||||
name: "FK_AspNetUserTokens_AspNetUsers_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "AspNetUsers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_AspNetRoleClaims_RoleId",
|
||||
table: "AspNetRoleClaims",
|
||||
column: "RoleId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "RoleNameIndex",
|
||||
table: "AspNetRoles",
|
||||
column: "NormalizedName",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_AspNetUserClaims_UserId",
|
||||
table: "AspNetUserClaims",
|
||||
column: "UserId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_AspNetUserLogins_UserId",
|
||||
table: "AspNetUserLogins",
|
||||
column: "UserId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_AspNetUserRoles_RoleId",
|
||||
table: "AspNetUserRoles",
|
||||
column: "RoleId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "EmailIndex",
|
||||
table: "AspNetUsers",
|
||||
column: "NormalizedEmail");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "UserNameIndex",
|
||||
table: "AspNetUsers",
|
||||
column: "NormalizedUserName",
|
||||
unique: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "AspNetRoleClaims");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "AspNetUserClaims");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "AspNetUserLogins");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "AspNetUserRoles");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "AspNetUserTokens");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "AspNetRoles");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "AspNetUsers");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,274 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
using USEntryCoach.Server.Data;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace USEntryCoach.Server.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
partial class ApplicationDbContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "9.0.5")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("NormalizedName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedName")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("RoleNameIndex");
|
||||
|
||||
b.ToTable("AspNetRoles", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetRoleClaims", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<int>("AccessFailedCount")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<bool>("EmailConfirmed")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("LockoutEnabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<DateTimeOffset?>("LockoutEnd")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("NormalizedEmail")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("NormalizedUserName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("PhoneNumber")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool>("PhoneNumberConfirmed")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("SecurityStamp")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool>("TwoFactorEnabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("UserName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedEmail")
|
||||
.HasDatabaseName("EmailIndex");
|
||||
|
||||
b.HasIndex("NormalizedUserName")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UserNameIndex");
|
||||
|
||||
b.ToTable("AspNetUsers", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserClaims", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ProviderKey")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ProviderDisplayName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("LoginProvider", "ProviderKey");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserLogins", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("UserId", "RoleId");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetUserRoles", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("UserId", "LoginProvider", "Name");
|
||||
|
||||
b.ToTable("AspNetUserTokens", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,71 +1,87 @@
|
||||
using System.Security.Claims;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using System.Text;
|
||||
using System.Text.Json.Nodes;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Http.HttpResults;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using USEntryCoach.Server.Data;
|
||||
using USEntryCoach.Server.Services;
|
||||
using USEntryCoach.Server.Settings;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Add services to the container.
|
||||
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
|
||||
builder.Services.AddOpenApi();
|
||||
builder.Services.AddSingleton<TokenService>();
|
||||
|
||||
// Configure JWT token generation.
|
||||
builder.Services.AddAuthentication(config =>
|
||||
{
|
||||
config.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
config.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
}).AddJwtBearer(config =>
|
||||
{
|
||||
// TODO: Use Microsoft.Extensions.Options?
|
||||
// var myOptions = new MyOptions(); // Your settings class
|
||||
// builder.Configuration.GetSection("MySection").Bind(myOptions);
|
||||
// myOptions now has the values
|
||||
//
|
||||
// TODO: This is identical in TokenService
|
||||
string? secretToken = builder.Configuration.GetValue<string>("Authentication:Secret");
|
||||
var apiSettingsSection = builder.Configuration.GetSection(ApiSettings.SectionName);
|
||||
var authSettingsSection = builder.Configuration.GetSection(AuthenticationSettings.SectionName);
|
||||
|
||||
if (secretToken == null)
|
||||
{
|
||||
throw new Exception("No Authentication Secret Token set! Please define a value for \"Authentication:SecretToken\" in appsettings.json.");
|
||||
}
|
||||
builder.Services.AddOptionsWithValidateOnStart<ApiSettings>().Bind(apiSettingsSection).ValidateDataAnnotations();
|
||||
builder.Services.AddOptionsWithValidateOnStart<AuthenticationSettings>().Bind(authSettingsSection).ValidateDataAnnotations();
|
||||
|
||||
byte[] secretKey = Encoding.ASCII.GetBytes(secretToken);
|
||||
ApiSettings? apiSettings = apiSettingsSection.Get<ApiSettings>();
|
||||
AuthenticationSettings? authSettings = authSettingsSection.Get<AuthenticationSettings>();
|
||||
//
|
||||
// builder.Services.AddSingleton<TokenService>();
|
||||
//
|
||||
// // Configure JWT token generation.
|
||||
// builder.Services.AddAuthentication(config =>
|
||||
// {
|
||||
// config.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
// config.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
// }).AddJwtBearer(config =>
|
||||
// {
|
||||
// // TODO: Only for debug!
|
||||
// config.RequireHttpsMetadata = false;
|
||||
//
|
||||
// config.SaveToken = true;
|
||||
// config.TokenValidationParameters = new TokenValidationParameters()
|
||||
// {
|
||||
// ValidateIssuerSigningKey = true,
|
||||
// IssuerSigningKey = new SymmetricSecurityKey(authSettings!.JwtGenerationSecretBytes),
|
||||
// ValidateIssuer = false,
|
||||
// ValidateAudience = false
|
||||
// };
|
||||
// });
|
||||
//
|
||||
// builder.Services.AddAuthorization(options =>
|
||||
// {
|
||||
// options.AddPolicy(nameof(UserRole.Developer), policy => policy.RequireRole(nameof(UserRole.Developer)));
|
||||
// options.AddPolicy(nameof(UserRole.User), policy =>
|
||||
// {
|
||||
// // Also allow Developers to do anything a user can do.
|
||||
// policy.RequireRole(nameof(UserRole.User), nameof(UserRole.Developer));
|
||||
// });
|
||||
// });
|
||||
|
||||
// TODO: Only for debug!
|
||||
config.RequireHttpsMetadata = false;
|
||||
config.SaveToken = true;
|
||||
config.TokenValidationParameters = new TokenValidationParameters()
|
||||
{
|
||||
ValidateIssuerSigningKey = true,
|
||||
IssuerSigningKey = new SymmetricSecurityKey(secretKey),
|
||||
ValidateIssuer = false,
|
||||
ValidateAudience = false
|
||||
};
|
||||
});
|
||||
builder.Services.AddDbContext<ApplicationDbContext>(options =>
|
||||
options.UseNpgsql(builder.Configuration.GetConnectionString("Default")));
|
||||
|
||||
builder.Services.AddAuthorization(options =>
|
||||
{
|
||||
options.AddPolicy(nameof(UserRole.Developer), policy => policy.RequireRole(nameof(UserRole.Developer)));
|
||||
options.AddPolicy(nameof(UserRole.User), policy =>
|
||||
{
|
||||
// Also allow Developers to do anything a user can do.
|
||||
policy.RequireRole(nameof(UserRole.User), nameof(UserRole.Developer));
|
||||
});
|
||||
});
|
||||
builder.Services.AddAuthorization();
|
||||
|
||||
builder.Services.AddIdentityApiEndpoints<IdentityUser>()
|
||||
.AddEntityFrameworkStores<ApplicationDbContext>();
|
||||
|
||||
//
|
||||
// builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
|
||||
// .AddEntityFrameworkStores<ApplicationDbContext>();
|
||||
//
|
||||
// builder.Services.AddDbContext<BloggingContext>(options =>
|
||||
// options.UseNpgsql(builder.Configuration.GetConnectionString("Default")));
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
app.MapIdentityApi<IdentityUser>();
|
||||
//
|
||||
// app.UseAuthentication();
|
||||
// app.UseAuthorization();
|
||||
|
||||
app.UseDefaultFiles();
|
||||
app.MapStaticAssets();
|
||||
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
@ -75,49 +91,49 @@ if (app.Environment.IsDevelopment())
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
HttpClient client = new();
|
||||
string? apiKey = app.Configuration.GetValue<string>("API:OpenAI");
|
||||
//string? apiKey = app.Configuration.GetValue<string>("API:OpenAI");
|
||||
client.DefaultRequestHeaders.Clear();
|
||||
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {apiKey}");
|
||||
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {apiSettings.OpenAiToken}");
|
||||
|
||||
app.MapPost("/login", Login);
|
||||
//app.MapPost("/login", Login);
|
||||
|
||||
app.MapGet("/hello", () => "Hello World!").RequireAuthorization("admin_greetings");
|
||||
|
||||
async Task<IResult> Login(LoginCredentials credentials, TokenService tokenService, HttpContext context)
|
||||
{
|
||||
// TODO: Check with database
|
||||
User? user = null;
|
||||
|
||||
if (credentials is {Username:"developer", Password:"dev"})
|
||||
{
|
||||
user = new User()
|
||||
{
|
||||
Username = credentials.Username,
|
||||
Password = credentials.Password,
|
||||
Id = Guid.CreateVersion7(),
|
||||
Role = UserRole.Developer
|
||||
};
|
||||
}
|
||||
else if (credentials is {Username:"user", Password:"us"})
|
||||
{
|
||||
user = new User()
|
||||
{
|
||||
Username = credentials.Username,
|
||||
Password = credentials.Password,
|
||||
Id = Guid.CreateVersion7(),
|
||||
Role = UserRole.User
|
||||
};
|
||||
}
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
return Results.Unauthorized();
|
||||
}
|
||||
|
||||
var token = tokenService.GenerateToken(user);
|
||||
|
||||
return Results.Ok(new { token });
|
||||
}
|
||||
//
|
||||
// async Task<IResult> Login(LoginCredentials credentials, TokenService tokenService, HttpContext context)
|
||||
// {
|
||||
// // TODO: Check with database
|
||||
// User? user = null;
|
||||
//
|
||||
// if (credentials is {Username:"developer", Password:"dev"})
|
||||
// {
|
||||
// user = new User()
|
||||
// {
|
||||
// Username = credentials.Username,
|
||||
// Password = credentials.Password,
|
||||
// Id = Guid.CreateVersion7(),
|
||||
// Role = UserRole.Developer
|
||||
// };
|
||||
// }
|
||||
// else if (credentials is {Username:"user", Password:"us"})
|
||||
// {
|
||||
// user = new User()
|
||||
// {
|
||||
// Username = credentials.Username,
|
||||
// Password = credentials.Password,
|
||||
// Id = Guid.CreateVersion7(),
|
||||
// Role = UserRole.User
|
||||
// };
|
||||
// }
|
||||
//
|
||||
// if (user == null)
|
||||
// {
|
||||
// return Results.Unauthorized();
|
||||
// }
|
||||
//
|
||||
// var token = tokenService.GenerateToken(user);
|
||||
//
|
||||
// return Results.Ok(new { token });
|
||||
// }
|
||||
|
||||
app.MapGet("/developer", (ClaimsPrincipal user) =>
|
||||
{
|
||||
@ -129,10 +145,31 @@ app.MapGet("/user", (ClaimsPrincipal user) =>
|
||||
Results.Ok(new { message = $"Authenticated as { user?.Identity?.Name }" });
|
||||
}).RequireAuthorization(nameof(UserRole.User));
|
||||
|
||||
app.MapGet("/auth/validate", async (HttpContext context, UserManager<IdentityUser> userManager) =>
|
||||
{
|
||||
if (!context.User.Identity?.IsAuthenticated ?? true)
|
||||
{
|
||||
return Results.Unauthorized();
|
||||
}
|
||||
|
||||
IdentityUser? user = await userManager.GetUserAsync(context.User);
|
||||
|
||||
if (user is null)
|
||||
{
|
||||
return Results.InternalServerError("User not found?!");
|
||||
}
|
||||
|
||||
return Results.Ok(new
|
||||
{
|
||||
user.Id,
|
||||
user.Email
|
||||
});
|
||||
}).RequireAuthorization();
|
||||
|
||||
app.MapGet("/ephemeral_token", async () =>
|
||||
{
|
||||
if (apiKey == null)
|
||||
throw new Exception("API key not set");
|
||||
//if (apiKey == null)
|
||||
// throw new Exception("API key not set");
|
||||
|
||||
var options = new
|
||||
{
|
||||
|
||||
@ -1,39 +1,15 @@
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using USEntryCoach.Server.Data;
|
||||
using USEntryCoach.Server.Settings;
|
||||
|
||||
namespace USEntryCoach.Server.Services;
|
||||
|
||||
public class TokenService
|
||||
public class TokenService(IOptions<AuthenticationSettings> authenticationSettings)
|
||||
{
|
||||
private byte[] _secretToken;
|
||||
private double _jwtExpiryMinutes;
|
||||
private const double DefaultJwtExpiryMinutes = 15;
|
||||
|
||||
public TokenService(IConfiguration configuration)
|
||||
{
|
||||
string? secretToken = configuration.GetValue<string>("Authentication:Secret");
|
||||
|
||||
if (secretToken == null)
|
||||
{
|
||||
throw new Exception("No Authentication Secret Token set! Please define a value for \"Authentication:SecretToken\" in appsettings.json.");
|
||||
}
|
||||
|
||||
_secretToken = Encoding.ASCII.GetBytes(secretToken);
|
||||
|
||||
double? jwtExpiryMinutes = configuration.GetValue<double?>("Authentication:JwtExpiryMinutes");
|
||||
|
||||
if (jwtExpiryMinutes == null)
|
||||
{
|
||||
// TODO: Use logger
|
||||
Console.WriteLine($"Warning: No expiry time for jwt session tokens defined. Using {DefaultJwtExpiryMinutes} minutes.");
|
||||
}
|
||||
|
||||
_jwtExpiryMinutes = jwtExpiryMinutes ?? DefaultJwtExpiryMinutes;
|
||||
}
|
||||
|
||||
public string GenerateToken(User user)
|
||||
{
|
||||
JwtSecurityTokenHandler tokenHandler = new();
|
||||
@ -42,11 +18,12 @@ public class TokenService
|
||||
{
|
||||
Subject = new ClaimsIdentity([
|
||||
new Claim(ClaimTypes.Name, user.Username),
|
||||
//new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
|
||||
new Claim(ClaimTypes.Role, user.Role.ToString())
|
||||
]),
|
||||
Expires = DateTime.UtcNow.AddMinutes(_jwtExpiryMinutes),
|
||||
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(_secretToken), SecurityAlgorithms.HmacSha256Signature)
|
||||
Expires = DateTime.UtcNow.Add(authenticationSettings.Value.JwtExpiryTime),
|
||||
SigningCredentials = new SigningCredentials(
|
||||
new SymmetricSecurityKey(authenticationSettings.Value.JwtGenerationSecretBytes),
|
||||
SecurityAlgorithms.HmacSha256Signature)
|
||||
};
|
||||
|
||||
SecurityToken token = tokenHandler.CreateToken(tokenDescriptor);
|
||||
|
||||
42
USEntryCoach.Server/Settings/APISettings.cs
Normal file
42
USEntryCoach.Server/Settings/APISettings.cs
Normal file
@ -0,0 +1,42 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace USEntryCoach.Server.Settings;
|
||||
|
||||
public sealed class ApiSettings
|
||||
{
|
||||
public const string SectionName = "API";
|
||||
|
||||
[Required(ErrorMessage = "OpenAI API token is required!", AllowEmptyStrings = false)]
|
||||
public required string OpenAiToken { get; set; }
|
||||
}
|
||||
|
||||
public sealed class AuthenticationSettings
|
||||
{
|
||||
public const string SectionName = "Authentication";
|
||||
|
||||
private string _jwtGenerationSecret = null!;
|
||||
|
||||
[Required(ErrorMessage = "JWT generation secret token is required!", AllowEmptyStrings = false)]
|
||||
public required string JwtGenerationSecret
|
||||
{
|
||||
get => _jwtGenerationSecret;
|
||||
set
|
||||
{
|
||||
_jwtGenerationSecret = value;
|
||||
JwtGenerationSecretBytes = Encoding.UTF8.GetBytes(value);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] JwtGenerationSecretBytes { get; private set; }
|
||||
|
||||
const string JwtExpiryTimeMin = "00:00:30";
|
||||
const string JwtExpiryTimeMax = "24:00:00";
|
||||
|
||||
[Range(typeof(TimeSpan), JwtExpiryTimeMin, JwtExpiryTimeMax,
|
||||
ErrorMessage = $"JWT expiry time must be in the range from {JwtExpiryTimeMin} to {JwtExpiryTimeMax}.")]
|
||||
public TimeSpan JwtExpiryTime { get; set; } = TimeSpan.FromMinutes(5);
|
||||
}
|
||||
|
||||
@ -14,11 +14,17 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.4" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SpaProxy">
|
||||
<Version>9.*-*</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.5">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.0" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -27,4 +33,8 @@
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Migrations\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@ -6,9 +6,12 @@
|
||||
}
|
||||
},
|
||||
"API": {
|
||||
"OpenAI": "Please set the key in secrets.json! NEVER HERE!!!"
|
||||
"OpenAiToken": "Please set the key in secrets.json! NEVER HERE!!!"
|
||||
},
|
||||
"Authentication": {
|
||||
"Secret": "Please provide a GUID (without dashes) as secret."
|
||||
"JwtGenerationSecret": "Please provide a GUID (without dashes) as secret."
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"Default": "User ID=us-entry-agent-dev;Password=example;Host=localhost;Port=5432;Database=us-entry-agent-dev;Pooling=true;MinPoolSize=0;MaxPoolSize=100;Connection Lifetime=0;"
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,6 +7,13 @@
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"API": {
|
||||
"OpenAI": "[NO_API_KEY]"
|
||||
"OpenAiToken": "[NO_API_KEY]"
|
||||
},
|
||||
"Authentication": {
|
||||
"JwtGenerationSecret": "Please provide a GUID (without dashes) as secret.",
|
||||
"JwtExpiryTime": "00:15:00"
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"Default": "[NO_CONNECTION_STRING]"
|
||||
}
|
||||
}
|
||||
|
||||
13
docker-compose.yml
Normal file
13
docker-compose.yml
Normal file
@ -0,0 +1,13 @@
|
||||
# Use postgres/example user/password credentials
|
||||
|
||||
services:
|
||||
db:
|
||||
image: postgres
|
||||
restart: always
|
||||
# set shared memory limit when using docker compose
|
||||
shm_size: 128mb
|
||||
environment:
|
||||
POSTGRES_USER: us-entry-agent-dev
|
||||
POSTGRES_PASSWORD: example
|
||||
ports:
|
||||
- 5432:5432
|
||||
56
usentrycoach.client/package-lock.json
generated
56
usentrycoach.client/package-lock.json
generated
@ -8,8 +8,10 @@
|
||||
"name": "usentrycoach.client",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"classnames": "^2.5.1",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-router-dom": "^7.6.1",
|
||||
"zod": "^3.25.23"
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -1781,6 +1783,11 @@
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/classnames": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
|
||||
"integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow=="
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
@ -1811,6 +1818,14 @@
|
||||
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/cookie": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
|
||||
"integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
@ -2757,6 +2772,42 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-router": {
|
||||
"version": "7.6.1",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.6.1.tgz",
|
||||
"integrity": "sha512-hPJXXxHJZEsPFNVbtATH7+MMX43UDeOauz+EAU4cgqTn7ojdI9qQORqS8Z0qmDlL1TclO/6jLRYUEtbWidtdHQ==",
|
||||
"dependencies": {
|
||||
"cookie": "^1.0.1",
|
||||
"set-cookie-parser": "^2.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=18",
|
||||
"react-dom": ">=18"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-router-dom": {
|
||||
"version": "7.6.1",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.6.1.tgz",
|
||||
"integrity": "sha512-vxU7ei//UfPYQ3iZvHuO1D/5fX3/JOqhNTbRR+WjSBWxf9bIvpWK+ftjmdfJHzPOuMQKe2fiEdG+dZX6E8uUpA==",
|
||||
"dependencies": {
|
||||
"react-router": "7.6.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=18",
|
||||
"react-dom": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve-from": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
|
||||
@ -2855,6 +2906,11 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/set-cookie-parser": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
|
||||
"integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ=="
|
||||
},
|
||||
"node_modules/shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
|
||||
@ -10,8 +10,10 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"classnames": "^2.5.1",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-router-dom": "^7.6.1",
|
||||
"zod": "^3.25.23"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@ -2,15 +2,39 @@ import './App.css';
|
||||
import {ChatControl} from "./ChatClient/ChatControl.tsx";
|
||||
import Login from './Components/Login.tsx';
|
||||
import useLoginToken from "./Hooks/useLoginToken.tsx";
|
||||
import Home from "./Components/Home.tsx";
|
||||
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';
|
||||
import ProtectedRoute from "./Components/ProtectedRoute.tsx";
|
||||
import {CookiesProvider} from "react-cookie";
|
||||
|
||||
export default function App()
|
||||
{
|
||||
const {token, setToken} = useLoginToken();
|
||||
|
||||
if (!token)
|
||||
{
|
||||
return <Login setToken={setToken} />
|
||||
}
|
||||
const Navigation = () => (
|
||||
<nav>
|
||||
<Link to="/">Home</Link>
|
||||
<Link to="/login">Login</Link>
|
||||
<Link to="/chat">Chat</Link>
|
||||
</nav>
|
||||
);
|
||||
|
||||
return <ChatControl/>
|
||||
return (
|
||||
<>
|
||||
{/*<Navigation/>*/}
|
||||
|
||||
<CookiesProvider defaultSetOptions={{ path: '/' }}>
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route index path="/" element={ <Home /> } />
|
||||
<Route path="login" element={ <Login setToken={setToken} /> } />
|
||||
<Route element={<ProtectedRoute user={token} setUser={setToken}/>}>
|
||||
<Route path="chat" element={ <ChatControl /> } />
|
||||
</Route>
|
||||
<Route path="*" element={ <p>Deine Mamma ist so dick, sie hat diese Seite gefressen. </p> } />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
</CookiesProvider>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
19
usentrycoach.client/src/Components/Home.tsx
Normal file
19
usentrycoach.client/src/Components/Home.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
export default function Home()
|
||||
{
|
||||
return (
|
||||
<>
|
||||
<h1>Welcome to the US Entry Board dings bums Agent!</h1>
|
||||
<Link to="/login">
|
||||
Login
|
||||
</Link>
|
||||
<p>
|
||||
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
|
||||
</p>
|
||||
<p>
|
||||
Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
93
usentrycoach.client/src/Components/Login.css
Normal file
93
usentrycoach.client/src/Components/Login.css
Normal file
@ -0,0 +1,93 @@
|
||||
/*#login-container*/
|
||||
/*{*/
|
||||
/* top: 0;*/
|
||||
/* left: 0;*/
|
||||
/* position: absolute;*/
|
||||
/* width: 100vw;*/
|
||||
/* height: 100vh;*/
|
||||
/* backdrop-filter: blur(5px);*/
|
||||
/*}*/
|
||||
|
||||
form
|
||||
{
|
||||
/*position: absolute;*/
|
||||
/*left: 50vw;*/
|
||||
/*top: 50vh;*/
|
||||
/*transform: translate(-50%, -50%);*/
|
||||
/*width: 120%;*/
|
||||
height: auto;
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 0 25px 20px 25px;
|
||||
border-radius: 10px;
|
||||
border-color: #282828;
|
||||
border-style: solid;
|
||||
border-width: 2px;
|
||||
background: #323232;
|
||||
filter: drop-shadow(0px 0px 8px rgb(146, 97, 251));
|
||||
}
|
||||
|
||||
.input-field
|
||||
{
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.register-row
|
||||
{
|
||||
display: flex;
|
||||
white-space-collapse: preserve;
|
||||
}
|
||||
|
||||
.register-row a
|
||||
{
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
form input
|
||||
{
|
||||
padding: 0.4rem;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
label
|
||||
{
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
label p
|
||||
{
|
||||
justify-self: start;
|
||||
display: block;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#button-login
|
||||
{
|
||||
width: 100%;
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
#login-error
|
||||
{
|
||||
background: #bf830e;
|
||||
border-radius: 5px;
|
||||
width: 0; /* Collapses element so it doesn't affect parent width */
|
||||
min-width: 100%; /* Expands element to parent width */
|
||||
}
|
||||
|
||||
#login-error p {
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.button-back {
|
||||
position: absolute;
|
||||
top: 1.5em;
|
||||
left: 1.5em;
|
||||
width: 2em;
|
||||
height: 2em;
|
||||
padding: 0;
|
||||
}
|
||||
@ -1,33 +1,64 @@
|
||||
import { useState, type FormEvent } from 'react';
|
||||
import { z } from 'zod/v4';
|
||||
import './Login.css';
|
||||
import { useNavigate } from "react-router";
|
||||
|
||||
export default function Login({ setToken } : {setToken: (token: string) => void})
|
||||
{
|
||||
const [username, setUsername] = useState<string>("");
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [email, setEmail] = useState<string>("");
|
||||
const [password, setPassword] = useState<string>("");
|
||||
const [loginError, setLoginError] = useState<string | null>(null);
|
||||
const [waiting, setWaiting] = useState(false);
|
||||
|
||||
async function doLogin(event: FormEvent<HTMLFormElement>)
|
||||
{
|
||||
setWaiting(true);
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
const token = await loginUser({username, password});
|
||||
try
|
||||
{
|
||||
const token = await loginUser({email, password});
|
||||
setToken(token);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
console.error(e);
|
||||
setLoginError("Ein fehler ist aufgetreten.");
|
||||
}
|
||||
|
||||
setToken(token);
|
||||
setWaiting(false);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<form className="login-form" onSubmit={e => void doLogin(e)} method="action">
|
||||
<div id="login-container">
|
||||
<form className="login-form" onSubmit={e => void doLogin(e)} method="action" inert={waiting}>
|
||||
<button className="button-back" onClick={() => void navigate(-1)}>←</button>
|
||||
<h2>
|
||||
Login
|
||||
</h2>
|
||||
{
|
||||
loginError ? (
|
||||
<div id="login-error">
|
||||
<p>{loginError}</p>
|
||||
</div>) : <></>
|
||||
}
|
||||
<label>
|
||||
Email:
|
||||
<input type="text" name="username" placeholder="Username" required onChange={event => setUsername(event.target.value)} />
|
||||
<p>Email:</p>
|
||||
<input type="email" className="form-field" name="email" autoComplete="email" /*required*/
|
||||
onChange={event => setEmail(event.target.value)} />
|
||||
</label>
|
||||
<label>
|
||||
Password:
|
||||
<input type="password" name="password" placeholder="Password" required onChange={event => setPassword(event.target.value)} />
|
||||
<p>Password:</p>
|
||||
<input type="password" name="password" autoComplete="current-password"
|
||||
/*required*/ onChange={event => setPassword(event.target.value)} />
|
||||
</label>
|
||||
|
||||
<button type="submit">Login</button>
|
||||
<button className="button-submit" type="submit" id="button-login">{waiting ? "Please wait..." : "Login"}</button>
|
||||
<div className="register-row">
|
||||
New? <a>Register here!</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
@ -37,10 +68,39 @@ const LoginResponse = z.object({
|
||||
token: z.string()
|
||||
});
|
||||
|
||||
async function loginUser(credentials : {username: string, password: string}): Promise<string>
|
||||
async function loginUser(credentials : {email: string, password: string}): Promise<string>
|
||||
{
|
||||
const queryParams = new URLSearchParams({
|
||||
useCookies: "true"
|
||||
});
|
||||
|
||||
const url = `login?${queryParams.toString()}`;
|
||||
|
||||
// Get a session token for OpenAI Realtime API
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(credentials)
|
||||
});
|
||||
|
||||
if (!response.ok)
|
||||
{
|
||||
throw new Error(response.statusText);
|
||||
// 500: internal server error
|
||||
}
|
||||
|
||||
//const responseJson:unknown = await response.json();
|
||||
//const parsedToken = LoginResponse.parse(responseJson);
|
||||
|
||||
return "abc"; //parsedToken.token;
|
||||
}
|
||||
|
||||
async function registerUser(credentials : {email: string, password: string}): Promise<string>
|
||||
{
|
||||
// Get a session token for OpenAI Realtime API
|
||||
const response = await fetch('login', {
|
||||
const response = await fetch('register', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
|
||||
58
usentrycoach.client/src/Components/ProtectedRoute.tsx
Normal file
58
usentrycoach.client/src/Components/ProtectedRoute.tsx
Normal file
@ -0,0 +1,58 @@
|
||||
import { Outlet } from "react-router-dom";
|
||||
import Login from "./Login.tsx";
|
||||
import { useEffect } from "react";
|
||||
import { z } from 'zod/v4';
|
||||
|
||||
const AuthValidateResult = z.object({
|
||||
id: z.string(),
|
||||
email: z.string()
|
||||
});
|
||||
|
||||
export default function ProtectedRoute({user, setUser} : {user: string | null, setUser: (user: string | null) => void})
|
||||
{
|
||||
|
||||
async function authenticateUser ()
|
||||
{
|
||||
try
|
||||
{
|
||||
console.log("Checkineasdjkf")
|
||||
const response = await fetch('/auth/validate', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok)
|
||||
{
|
||||
console.error(`Failed to validate session: ${response.status} ${response.statusText}`);
|
||||
setUser(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const responseJson:unknown = await response.json();
|
||||
const parsedUser = AuthValidateResult.parse(responseJson);
|
||||
|
||||
setUser(JSON.stringify(parsedUser));
|
||||
|
||||
console.log("User authorized!")
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
console.error(e);
|
||||
setUser(null);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
void authenticateUser();
|
||||
});
|
||||
|
||||
if (user === null)
|
||||
{
|
||||
return <Login setToken={setUser}/>;
|
||||
}
|
||||
|
||||
return <Outlet />;
|
||||
}
|
||||
@ -15,7 +15,7 @@ export default function useLoginToken()
|
||||
|
||||
const [token, setToken] = useState<string | null>(getToken());
|
||||
|
||||
const saveToken = (token: string): void =>
|
||||
const saveToken = (token: string | null): void =>
|
||||
{
|
||||
sessionStorage.setItem('token', JSON.stringify(token));
|
||||
setToken(token);
|
||||
|
||||
@ -43,12 +43,14 @@ button {
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
background-color: #1a1a1a;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.25s;
|
||||
}
|
||||
button:hover {
|
||||
|
||||
button:hover:not([disabled]) {
|
||||
border-color: #646cff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:focus,
|
||||
button:focus-visible {
|
||||
outline: 4px auto -webkit-focus-ring-color;
|
||||
@ -66,3 +68,12 @@ button:focus-visible {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
}
|
||||
|
||||
.button-submit {
|
||||
background-color: #208ddb;
|
||||
}
|
||||
|
||||
.button-submit:hover:not([disabled]) {
|
||||
background-color: #3c9be0;
|
||||
border-color: #c7e3f6;
|
||||
}
|
||||
|
||||
@ -17,8 +17,8 @@
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
//"noUnusedLocals": true,
|
||||
//"noUnusedParameters": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
|
||||
@ -44,7 +44,7 @@ const target = env.ASPNETCORE_HTTPS_PORT ? `https://localhost:${env.ASPNETCORE_H
|
||||
env.ASPNETCORE_URLS ? env.ASPNETCORE_URLS.split(';')[0] : 'https://localhost:7085';
|
||||
|
||||
// Define a list of all existing backend routes.
|
||||
const backendRoutes = ['/login', '/ephemeral_token'];
|
||||
const backendRoutes = ['/login', '/register', '/ephemeral_token', '/auth/validate'];
|
||||
|
||||
// For development, we have a node.js server running that delivers our frontend.
|
||||
// We have to configure proxies for the backend calls, so that node.js will forward them to the backend.
|
||||
|
||||
Reference in New Issue
Block a user