From 89942f07313a36a9ec6307ecd5cab345e9ec5176 Mon Sep 17 00:00:00 2001 From: Blitblank Date: Wed, 22 Apr 2026 19:55:47 -0500 Subject: [PATCH] fix build permission errors --- ...60423005530_fixNullPermissions.Designer.cs | 340 ++++++++++++++++++ .../20260423005530_fixNullPermissions.cs | 36 ++ api/Migrations/AppDbContextModelSnapshot.cs | 1 - api/src/Controllers/AuthController.cs | 28 +- api/src/Models/User.cs | 2 +- api/src/Services/JwtService.cs | 8 +- api/src/Services/UserService.cs | 11 + 7 files changed, 408 insertions(+), 18 deletions(-) create mode 100644 api/Migrations/20260423005530_fixNullPermissions.Designer.cs create mode 100644 api/Migrations/20260423005530_fixNullPermissions.cs diff --git a/api/Migrations/20260423005530_fixNullPermissions.Designer.cs b/api/Migrations/20260423005530_fixNullPermissions.Designer.cs new file mode 100644 index 0000000..74743d0 --- /dev/null +++ b/api/Migrations/20260423005530_fixNullPermissions.Designer.cs @@ -0,0 +1,340 @@ +// +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace agologum_api.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20260423005530_fixNullPermissions")] + partial class fixNullPermissions + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("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", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("RefreshToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ExpiresAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsRevoked") + .HasColumnType("boolean"); + + b.Property("Token") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("RefreshTokens"); + }); + + modelBuilder.Entity("agologumApi.Models.Item", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastEditedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Items"); + }); + + modelBuilder.Entity("agologumApi.Models.User", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.PrimitiveCollection>("Permissions") + .HasColumnType("text[]"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("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.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("agologumApi.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("agologumApi.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("agologumApi.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("agologumApi.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/api/Migrations/20260423005530_fixNullPermissions.cs b/api/Migrations/20260423005530_fixNullPermissions.cs new file mode 100644 index 0000000..0247a00 --- /dev/null +++ b/api/Migrations/20260423005530_fixNullPermissions.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace agologum_api.Migrations +{ + /// + public partial class fixNullPermissions : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn>( + name: "Permissions", + table: "AspNetUsers", + type: "text[]", + nullable: true, + oldClrType: typeof(List), + oldType: "text[]"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn>( + name: "Permissions", + table: "AspNetUsers", + type: "text[]", + nullable: false, + oldClrType: typeof(List), + oldType: "text[]", + oldNullable: true); + } + } +} diff --git a/api/Migrations/AppDbContextModelSnapshot.cs b/api/Migrations/AppDbContextModelSnapshot.cs index 26b91d3..26fd533 100644 --- a/api/Migrations/AppDbContextModelSnapshot.cs +++ b/api/Migrations/AppDbContextModelSnapshot.cs @@ -251,7 +251,6 @@ namespace agologum_api.Migrations .HasColumnType("text"); b.PrimitiveCollection>("Permissions") - .IsRequired() .HasColumnType("text[]"); b.Property("PhoneNumber") diff --git a/api/src/Controllers/AuthController.cs b/api/src/Controllers/AuthController.cs index 4e00c53..83f44e7 100644 --- a/api/src/Controllers/AuthController.cs +++ b/api/src/Controllers/AuthController.cs @@ -15,12 +15,14 @@ public class AuthController : ControllerBase { private readonly SignInManager signInManager_; private readonly JwtService jwt_; + private readonly UserService userService_; - public AuthController(UserManager userManager, SignInManager signInManager, JwtService jwt) { + public AuthController(UserManager userManager, SignInManager signInManager, JwtService jwt, UserService userService) { userManager_ = userManager; signInManager_ = signInManager; jwt_ = jwt; + userService_ = userService; } [HttpPost("register")] @@ -31,21 +33,16 @@ public class AuthController : ControllerBase { CreatedAt = DateTime.UtcNow // yeah why not utc }; + // assigning roles to user + if(dto.UserName.StartsWith("x")) { + user.Permissions = new List { Permission.SensitiveData_Read }; + } else if(dto.UserName == "bard") { + user.Permissions = new List { Permission.SensitiveData_Read, Permission.SensitiveData_Modify }; + } + var result = await userManager_.CreateAsync(user, dto.Password); if(!result.Succeeded) return BadRequest(result.Errors); - // assigning roles to user - string role = "base"; - if(dto.UserName == "bard") { - role = "superuser"; - } else if(dto.UserName.StartsWith("x")) { - role = "admin"; - } - await userManager_.AddToRoleAsync(user, role); // TODO: error check this - // these are here just in case you need them - // await _userManager.RemoveFromRoleAsync(user, "admin"); // remove role - // var roles = await _userManager.GetRolesAsync(user); // get list of roles for user - return CreatedAtAction( nameof(Register), new { id = user.Id } @@ -78,6 +75,11 @@ public class AuthController : ControllerBase { await userManager_.AddToRoleAsync(user, "superuser"); } // eventually ill have an endpoint for adding/removing roles + if(dto.UserName == "bard") { + user.Permissions = new List { Permission.SensitiveData_Read, Permission.SensitiveData_Modify }; + await userService_.Update(user.Id, user); + } + return Ok(new { accessToken, refreshToken }); } diff --git a/api/src/Models/User.cs b/api/src/Models/User.cs index a173bfb..5b95f47 100644 --- a/api/src/Models/User.cs +++ b/api/src/Models/User.cs @@ -7,7 +7,7 @@ public class User : IdentityUser { public DateTime CreatedAt { get; set; } - public List Permissions { get; set; } = [ Permission.SensitiveData_Read, Permission.SensitiveData_Modify ]; // just seeding these here initially + public List? Permissions { get; set; } // properties inherited from IdentityUser: /* diff --git a/api/src/Services/JwtService.cs b/api/src/Services/JwtService.cs index 24fa21a..a43788d 100644 --- a/api/src/Services/JwtService.cs +++ b/api/src/Services/JwtService.cs @@ -37,9 +37,11 @@ public class JwtService { new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()) }; - List permissions = user.Permissions; - foreach(var perm in permissions) { - claims.Add(new Claim("permission", perm)); + List? permissions = user.Permissions; + if(permissions != null) { + foreach(var perm in permissions) { + claims.Add(new Claim("permission", perm)); + } } var token = new JwtSecurityToken( diff --git a/api/src/Services/UserService.cs b/api/src/Services/UserService.cs index 1771786..68c1e82 100644 --- a/api/src/Services/UserService.cs +++ b/api/src/Services/UserService.cs @@ -36,4 +36,15 @@ public class UserService { } } + public async Task Update(string id, User user) { + + User? oldUser = await db_.Users.FindAsync(id); + if(oldUser == null) return oldUser; + + oldUser.Permissions = user.Permissions; + + await db_.SaveChangesAsync(); + return oldUser; + } + } \ No newline at end of file