1- using Microsoft . AspNetCore . Mvc ;
1+ using Microsoft . AspNetCore . Authorization ;
2+ using Microsoft . AspNetCore . Identity . Data ;
3+ using Microsoft . AspNetCore . Mvc ;
24using Microsoft . EntityFrameworkCore ;
5+ using Microsoft . Extensions . Options ;
6+ using Microsoft . IdentityModel . Tokens ;
7+ using System . IdentityModel . Tokens . Jwt ;
8+ using System . Security . Claims ;
9+ using System . Text ;
310using UserManagementApi . Data ;
411using UserManagementApi . DTO ;
12+ using UserManagementApi . DTO . Auth ;
13+ using UserManagementApi . Models ;
514
615namespace UserManagementApi . Controllers
716{
@@ -11,17 +20,68 @@ namespace UserManagementApi.Controllers
1120 public class UsersController : ControllerBase
1221 {
1322 private readonly AppDbContext _db ;
14- public UsersController ( AppDbContext db ) => _db = db ;
23+ private readonly JwtOptions _jwt ;
1524
16- // GET: api/users/{userId}/permissions
25+ public UsersController ( AppDbContext db , IOptions < JwtOptions > jwtOptions )
26+ {
27+ _db = db ;
28+ _jwt = jwtOptions . Value ;
29+ }
30+ // --------- NEW: POST /api/users/authenticate ----------
31+ [ HttpPost ( "authenticate" ) ]
32+ public async Task < ActionResult < AuthResponse > > Authenticate ( [ FromBody ] DTO . LoginRequest req )
33+ {
34+ if ( string . IsNullOrWhiteSpace ( req . UserName ) || string . IsNullOrWhiteSpace ( req . Password ) )
35+ return BadRequest ( "Username and password are required." ) ;
36+
37+ // Validate user (plain text as per your seed)
38+ var user = await _db . Users
39+ . Include ( u => u . UserRoles )
40+ . ThenInclude ( ur => ur . Role )
41+ . FirstOrDefaultAsync ( u => u . UserName == req . UserName && u . Password == req . Password ) ;
42+
43+ if ( user == null )
44+ return Unauthorized ( "Invalid credentials." ) ;
45+
46+ // Collect roles & function codes for claims (optional but handy)
47+ var roleIds = user . UserRoles . Select ( ur => ur . RoleId ) . ToList ( ) ;
48+
49+ var functionCodes = await _db . RoleFunctions
50+ . Where ( rf => roleIds . Contains ( rf . RoleId ) )
51+ . Select ( rf => rf . Function . Code )
52+ . Distinct ( )
53+ . ToListAsync ( ) ;
54+
55+ var token = GenerateJwt ( user , user . UserRoles . Select ( ur => ur . Role . Name ) . Distinct ( ) . ToList ( ) , functionCodes , out var expiresAtUtc ) ;
56+
57+ // Get the same permissions tree you already expose
58+ var permissions = await BuildPermissionsForUser ( user . Id ) ;
59+
60+ return Ok ( new AuthResponse ( user . Id , user . UserName , token , expiresAtUtc , permissions ) ) ;
61+ }
62+
63+ // --------- (Existing) GET /api/users/{userId}/permissions ----------
64+ // Now protected by JWT; call with Bearer token returned by /authenticate
65+ [ Authorize ]
1766 [ HttpGet ( "{userId:int}/permissions" ) ]
1867 public async Task < ActionResult < UserPermissionsDto > > GetPermissions ( int userId )
1968 {
69+ // Optional: you can enforce that a user can only view their own permissions
70+ // by comparing userId with the token's sub, if desired.
71+
2072 var user = await _db . Users . FirstOrDefaultAsync ( u => u . Id == userId ) ;
2173 if ( user == null ) return NotFound ( $ "User { userId } not found.") ;
2274
23- // Build a single LINQ query that filters functions to those reachable
24- // via the user's roles (no client-side filtering).
75+ var dto = await BuildPermissionsForUser ( userId ) ;
76+ return Ok ( dto ) ;
77+ }
78+
79+ // ----- helpers -----
80+
81+ private async Task < UserPermissionsDto > BuildPermissionsForUser ( int userId )
82+ {
83+ var user = await _db . Users . FirstAsync ( u => u . Id == userId ) ;
84+
2585 var categories = await _db . Categories
2686 . Select ( c => new CategoryDto (
2787 c . Id ,
@@ -30,19 +90,50 @@ public async Task<ActionResult<UserPermissionsDto>> GetPermissions(int userId)
3090 . Select ( m => new ModuleDto (
3191 m . Id , m . Name , m . Area , m . Controller , m . Action ,
3292 m . Functions
33- . Where ( f => f . RoleFunctions
34- . Any ( rf => rf . Role . UserRoles . Any ( ur => ur . UserId == userId ) ) )
35- . Select ( f => new FunctionDto ( f . Id , f . Code , f . DisplayName ) )
36- . ToList ( )
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 ) )
96+ . ToList ( )
3797 ) )
38- . Where ( md => md . Functions . Any ( ) ) // keep only modules with at least one permitted function
98+ . Where ( md => md . Functions . Any ( ) )
3999 . ToList ( )
40100 ) )
41- . Where ( cd => cd . Modules . Any ( ) ) // keep only categories with at least one permitted module
101+ . Where ( cd => cd . Modules . Any ( ) )
42102 . ToListAsync ( ) ;
43103
44- var dto = new UserPermissionsDto ( user . Id , user . UserName , categories ) ;
45- return Ok ( dto ) ;
104+ return new UserPermissionsDto ( user . Id , user . UserName , categories ) ;
105+ }
106+
107+ private string GenerateJwt ( AppUser user , IEnumerable < string > roles , IEnumerable < string > functionCodes , out DateTime expiresAtUtc )
108+ {
109+ var key = new SymmetricSecurityKey ( Encoding . UTF8 . GetBytes ( _jwt . Key ) ) ;
110+ var creds = new SigningCredentials ( key , SecurityAlgorithms . HmacSha256 ) ;
111+
112+ var claims = new List < Claim >
113+ {
114+ new ( JwtRegisteredClaimNames . Sub , user . Id . ToString ( ) ) ,
115+ new ( JwtRegisteredClaimNames . UniqueName , user . UserName )
116+ } ;
117+
118+ // optional: add role claims
119+ claims . AddRange ( roles . Select ( r => new Claim ( ClaimTypes . Role , r ) ) ) ;
120+
121+ // optional: add function claims (careful: keep token size reasonable)
122+ foreach ( var fn in functionCodes )
123+ claims . Add ( new Claim ( "perm" , fn ) ) ;
124+
125+ var now = DateTime . UtcNow ;
126+ expiresAtUtc = now . AddMinutes ( _jwt . ExpiresMinutes ) ;
127+
128+ var token = new JwtSecurityToken (
129+ issuer : _jwt . Issuer ,
130+ audience : _jwt . Audience ,
131+ claims : claims ,
132+ notBefore : now ,
133+ expires : expiresAtUtc ,
134+ signingCredentials : creds ) ;
135+
136+ return new JwtSecurityTokenHandler ( ) . WriteToken ( token ) ;
46137 }
47138 }
48139}
0 commit comments