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