feat: add admin role support to backend JWT authentication
Add role-based access control to the backend authentication system. The User entity now carries a role field (default 'user'), JWT tokens include a 'role' claim, and the login endpoint populates it from the database. Changes: - User entity: add role column (VARCHAR(20), default 'user') with getter/setter - JwtService: add generateToken(email, role) overload and extractRole(token) - AuthController: pass user.getRole() on login, 'user' on register - Flyway V3: ALTER TABLE users ADD COLUMN role - Flyway V4: seed admin user (admin@bilhalsning.se, role='admin') - AuthControllerTest: add tests for admin role in token, role from DB on login - JwtServiceTest: add tests for role extraction and default role - UserServiceTest: assert role defaults to 'user' on createUser
This commit is contained in:
parent
bb4bb0c6c6
commit
8a95483fb8
8 changed files with 76 additions and 4 deletions
|
|
@ -26,14 +26,14 @@ public class AuthController {
|
|||
@PostMapping("/register")
|
||||
public ResponseEntity<AuthResponse> register(@Valid @RequestBody RegisterRequest request) {
|
||||
userService.createUser(request.email(), request.password());
|
||||
String token = jwtService.generateToken(request.email().toLowerCase().trim());
|
||||
String token = jwtService.generateToken(request.email().toLowerCase().trim(), "user");
|
||||
return ResponseEntity.status(HttpStatus.CREATED).body(new AuthResponse(token));
|
||||
}
|
||||
|
||||
@PostMapping("/login")
|
||||
public ResponseEntity<AuthResponse> login(@Valid @RequestBody LoginRequest request) {
|
||||
User user = userService.authenticate(request.email(), request.password());
|
||||
String token = jwtService.generateToken(user.getEmail());
|
||||
String token = jwtService.generateToken(user.getEmail(), user.getRole());
|
||||
return ResponseEntity.ok(new AuthResponse(token));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,9 @@ public class User {
|
|||
@Column(name = "subscription", nullable = false, length = 20)
|
||||
private Subscription subscription = Subscription.NONE;
|
||||
|
||||
@Column(name = "role", nullable = false, length = 20)
|
||||
private String role = "user";
|
||||
|
||||
@Column(name = "created_at", nullable = false)
|
||||
private Instant createdAt;
|
||||
|
||||
|
|
@ -83,6 +86,14 @@ public class User {
|
|||
this.subscription = subscription;
|
||||
}
|
||||
|
||||
public String getRole() {
|
||||
return role;
|
||||
}
|
||||
|
||||
public void setRole(String role) {
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
public Instant getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,8 +24,13 @@ public class JwtService {
|
|||
}
|
||||
|
||||
public String generateToken(String email) {
|
||||
return generateToken(email, "user");
|
||||
}
|
||||
|
||||
public String generateToken(String email, String role) {
|
||||
return Jwts.builder()
|
||||
.subject(email)
|
||||
.claim("role", role)
|
||||
.issuedAt(new Date())
|
||||
.expiration(new Date(System.currentTimeMillis() + expirationMs))
|
||||
.signWith(secretKey)
|
||||
|
|
@ -41,6 +46,15 @@ public class JwtService {
|
|||
.getSubject();
|
||||
}
|
||||
|
||||
public String extractRole(String token) {
|
||||
return Jwts.parser()
|
||||
.verifyWith(secretKey)
|
||||
.build()
|
||||
.parseSignedClaims(token)
|
||||
.getPayload()
|
||||
.get("role", String.class);
|
||||
}
|
||||
|
||||
public boolean isTokenValid(String token) {
|
||||
try {
|
||||
Jwts.parser()
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE users ADD COLUMN role VARCHAR(20) NOT NULL DEFAULT 'user';
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
INSERT INTO users (id, email, password_hash, subscription, role)
|
||||
VALUES (
|
||||
'b1eebc99-9c0b-4ef8-bb6d-6bb9bd380a12',
|
||||
'admin@bilhalsning.se',
|
||||
'$2b$12$18UFRDPgHWuw5FYeu6X1ReisFjjuxs5XxDafi6.wZbsywoU7vUaLG',
|
||||
'none',
|
||||
'admin'
|
||||
);
|
||||
|
|
@ -39,7 +39,7 @@ class AuthControllerTest {
|
|||
@Test
|
||||
void shouldReturn201AndTokenWhenRegisterSucceeds() throws Exception {
|
||||
when(userService.createUser("new@example.com", "password123")).thenReturn(null);
|
||||
when(jwtService.generateToken("new@example.com")).thenReturn("test-jwt-token");
|
||||
when(jwtService.generateToken("new@example.com", "user")).thenReturn("test-jwt-token");
|
||||
|
||||
RegisterRequest request = new RegisterRequest("new@example.com", "password123");
|
||||
mockMvc.perform(post("/api/auth/register")
|
||||
|
|
@ -93,8 +93,9 @@ class AuthControllerTest {
|
|||
void shouldReturn200AndTokenWhenLoginSucceeds() throws Exception {
|
||||
User user = new User();
|
||||
user.setEmail("user@example.com");
|
||||
user.setRole("user");
|
||||
when(userService.authenticate("user@example.com", "password123")).thenReturn(user);
|
||||
when(jwtService.generateToken("user@example.com")).thenReturn("login-jwt-token");
|
||||
when(jwtService.generateToken("user@example.com", "user")).thenReturn("login-jwt-token");
|
||||
|
||||
LoginRequest request = new LoginRequest("user@example.com", "password123");
|
||||
mockMvc.perform(post("/api/auth/login")
|
||||
|
|
@ -104,6 +105,22 @@ class AuthControllerTest {
|
|||
.andExpect(jsonPath("$.token").value("login-jwt-token"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnAdminRoleInTokenWhenAdminLogsIn() throws Exception {
|
||||
User admin = new User();
|
||||
admin.setEmail("admin@bilhalsning.se");
|
||||
admin.setRole("admin");
|
||||
when(userService.authenticate("admin@bilhalsning.se", "admin1234")).thenReturn(admin);
|
||||
when(jwtService.generateToken("admin@bilhalsning.se", "admin")).thenReturn("admin-jwt-token");
|
||||
|
||||
LoginRequest request = new LoginRequest("admin@bilhalsning.se", "admin1234");
|
||||
mockMvc.perform(post("/api/auth/login")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(objectMapper.writeValueAsString(request)))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.token").value("admin-jwt-token"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturn401WhenCredentialsAreInvalid() throws Exception {
|
||||
when(userService.authenticate("user@example.com", "wrongpassword"))
|
||||
|
|
|
|||
|
|
@ -71,4 +71,24 @@ class JwtServiceTest {
|
|||
|
||||
assertFalse(jwtService.isTokenValid(tampered));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldExtractUserRoleFromToken() {
|
||||
JwtService jwtService = new JwtService(SECRET);
|
||||
|
||||
String token = jwtService.generateToken(EMAIL, "admin");
|
||||
String role = jwtService.extractRole(token);
|
||||
|
||||
assertEquals("admin", role);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldDefaultToUserRoleWhenNoRoleSpecified() {
|
||||
JwtService jwtService = new JwtService(SECRET);
|
||||
|
||||
String token = jwtService.generateToken(EMAIL);
|
||||
String role = jwtService.extractRole(token);
|
||||
|
||||
assertEquals("user", role);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ class UserServiceTest {
|
|||
assertEquals("new@example.com", result.getEmail());
|
||||
assertEquals("hashed", result.getPasswordHash());
|
||||
assertEquals(Subscription.NONE, result.getSubscription());
|
||||
assertEquals("user", result.getRole());
|
||||
verify(userRepository).save(any(User.class));
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue