Skip to content

Commit fdeac93

Browse files
In Authenticate api use verify to match hashed password. change property name of key in JwtOptions
1 parent 4bf1296 commit fdeac93

File tree

3 files changed

+83
-22
lines changed

3 files changed

+83
-22
lines changed

UserManagementApi/Controllers/UsersController.cs

Lines changed: 54 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,15 @@ public async Task<ActionResult<AuthResponse>> Authenticate([FromBody] DTO.LoginR
3434
if (string.IsNullOrWhiteSpace(req.UserName) || string.IsNullOrWhiteSpace(req.Password))
3535
return BadRequest("Username and password are required.");
3636

37-
// Validate user (plain text as per your seed)
3837
var user = await _db.Users
3938
.Include(u => u.UserRoles)
4039
.ThenInclude(ur => ur.Role)
41-
.FirstOrDefaultAsync(u => u.UserName == req.UserName && u.Password == BCrypt.Net.BCrypt.HashPassword(req.Password));
40+
.FirstOrDefaultAsync(u => u.UserName == req.UserName);
4241

43-
if (user == null)
42+
if (user == null || !BCrypt.Net.BCrypt.Verify(req.Password, user.Password))
43+
{
4444
return Unauthorized("Invalid credentials.");
45+
}
4546

4647
// Collect roles & function codes for claims (optional but handy)
4748
var roleIds = user.UserRoles.Select(ur => ur.RoleId).ToList();
@@ -80,33 +81,65 @@ public async Task<ActionResult<UserPermissionsDto>> GetPermissions(int userId)
8081

8182
private async Task<UserPermissionsDto> BuildPermissionsForUser(int userId)
8283
{
83-
var user = await _db.Users.FirstAsync(u => u.Id == userId);
84-
85-
var categories = await _db.Categories
86-
.Select(c => new CategoryDto(
87-
c.Id,
88-
c.Name,
89-
c.Modules
90-
.Select(m => new ModuleDto(
91-
m.Id, m.Name, m.Area, m.Controller, m.Action,
92-
m.Functions
93-
.Where(f => f.RoleFunctions
94-
.Any(rf => rf.Role.UserRoles.Any(ur => ur.UserId == userId)))
95-
.Select(f => new FunctionDto(f.Id, f.Code, f.DisplayName))
84+
// Fetch the user (for name in DTO)
85+
var user = await _db.Users.AsNoTracking().FirstAsync(u => u.Id == userId);
86+
87+
// 1) Get all (Category, Module, Function) triples the user is allowed to access.
88+
// This is fully translatable SQL: joins + where.
89+
var triples = await (
90+
from ur in _db.UserRoles.AsNoTracking()
91+
where ur.UserId == userId
92+
join rf in _db.RoleFunctions.AsNoTracking() on ur.RoleId equals rf.RoleId
93+
join f in _db.Functions
94+
.Include(x => x.Module)
95+
.ThenInclude(m => m.Category)
96+
.AsNoTracking()
97+
on rf.FunctionId equals f.Id
98+
select new
99+
{
100+
CategoryId = f.Module.Category.Id,
101+
CategoryName = f.Module.Category.Name,
102+
ModuleId = f.Module.Id,
103+
ModuleName = f.Module.Name,
104+
f.Module.Area,
105+
f.Module.Controller,
106+
f.Module.Action,
107+
FunctionId = f.Id,
108+
f.Code,
109+
f.DisplayName
110+
}
111+
).ToListAsync();
112+
113+
// 2) Group in memory to shape the hierarchical DTOs.
114+
var categoryDtos = triples
115+
.GroupBy(t => new { t.CategoryId, t.CategoryName })
116+
.Select(cg => new CategoryDto(
117+
cg.Key.CategoryId,
118+
cg.Key.CategoryName,
119+
cg.GroupBy(t => new { t.ModuleId, t.ModuleName, t.Area, t.Controller, t.Action })
120+
.Select(mg => new ModuleDto(
121+
mg.Key.ModuleId,
122+
mg.Key.ModuleName,
123+
mg.Key.Area,
124+
mg.Key.Controller,
125+
mg.Key.Action,
126+
mg.GroupBy(x => new { x.FunctionId, x.Code, x.DisplayName }) // distinct functions
127+
.Select(g => new FunctionDto(g.Key.FunctionId, g.Key.Code, g.Key.DisplayName))
128+
.OrderBy(f => f.Code)
96129
.ToList()
97130
))
98-
.Where(md => md.Functions.Any())
131+
.OrderBy(m => m.Name)
99132
.ToList()
100133
))
101-
.Where(cd => cd.Modules.Any())
102-
.ToListAsync();
134+
.OrderBy(c => c.Name)
135+
.ToList();
103136

104-
return new UserPermissionsDto(user.Id, user.UserName, categories);
137+
return new UserPermissionsDto(user.Id, user.UserName, categoryDtos);
105138
}
106139

107140
private string GenerateJwt(AppUser user, IEnumerable<string> roles, IEnumerable<string> functionCodes, out DateTime expiresAtUtc)
108141
{
109-
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwt.Key));
142+
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwt.KeyBase64));
110143
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
111144

112145
var claims = new List<Claim>

UserManagementApi/DTO/Auth/JwtOptions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ public class JwtOptions
44
{
55
public string Issuer { get; set; } = null!;
66
public string Audience { get; set; } = null!;
7-
public string Key { get; set; } = null!;
7+
public string KeyBase64 { get; set; } = null!;
88
public int ExpiresMinutes { get; set; }
99
}
1010
}

UserManagementApi/Program.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,40 @@
6464
Version = "v1",
6565
Description = "API for user authentication, authorization and management"
6666
});
67+
68+
// Bearer token support
69+
var jwtSecurityScheme = new OpenApiSecurityScheme
70+
{
71+
Scheme = "bearer",
72+
BearerFormat = "JWT",
73+
Name = "Authorization",
74+
In = ParameterLocation.Header,
75+
Type = SecuritySchemeType.Http,
76+
Description = "JWT auth using Bearer scheme. Paste **only** the token below.",
77+
78+
Reference = new OpenApiReference
79+
{
80+
Id = "Bearer",
81+
Type = ReferenceType.SecurityScheme
82+
}
83+
};
84+
85+
c.AddSecurityDefinition("Bearer", jwtSecurityScheme);
86+
87+
// Require Bearer token for all operations (you can remove if you prefer per-endpoint)
88+
c.AddSecurityRequirement(new OpenApiSecurityRequirement
89+
{
90+
{ jwtSecurityScheme, Array.Empty<string>() }
91+
});
92+
6793
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
6894
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
6995
c.IncludeXmlComments(xmlPath, includeControllerXmlComments: true); // ok if your Swashbuckle version supports the bool
7096

7197
});
7298

99+
100+
73101
var app = builder.Build();
74102

75103
app.Use(async (ctx, next) =>

0 commit comments

Comments
 (0)