diff --git a/.gitignore b/.gitignore index 6c0de1a..cd85925 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ target/ !**/src/test/**/target/ /config.json resources/* +run ### STS ### .apt_generated diff --git a/pom.xml b/pom.xml index 5700481..5c36dbe 100644 --- a/pom.xml +++ b/pom.xml @@ -113,6 +113,26 @@ compile + + io.jsonwebtoken + jjwt-api + 0.11.2 + + + + io.jsonwebtoken + jjwt-impl + 0.11.2 + runtime + + + + io.jsonwebtoken + jjwt-jackson + 0.11.2 + runtime + + diff --git a/src/main/java/com/imjustdoom/pluginsite/config/custom/JwtConfig.java b/src/main/java/com/imjustdoom/pluginsite/config/custom/JwtConfig.java new file mode 100644 index 0000000..83e5fe6 --- /dev/null +++ b/src/main/java/com/imjustdoom/pluginsite/config/custom/JwtConfig.java @@ -0,0 +1,44 @@ +package com.imjustdoom.pluginsite.config.custom; + +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; +import lombok.Getter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.ConstructorBinding; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.Key; +import java.time.Duration; + +@Getter +@ConfigurationProperties("jwt") +@ConstructorBinding +public class JwtConfig { + private static final Path KEY_PATH = Path.of("./jwt_secret"); + + private final Duration expiryTime; + private final String domain; + private final boolean secureCookie; + private final Key key; + + public JwtConfig(Duration expiryTime, String domain, boolean secureCookie, String key) throws IOException { + this.expiryTime = expiryTime; + this.domain = domain; + this.secureCookie = secureCookie; + byte[] encodedSecret; + if (key == null) { + if (Files.exists(KEY_PATH)) { + encodedSecret = Files.readAllBytes(KEY_PATH); + } else { + encodedSecret = Keys.secretKeyFor(SignatureAlgorithm.HS512).getEncoded(); + Files.write(KEY_PATH, encodedSecret); + } + } else { + encodedSecret = key.getBytes(StandardCharsets.UTF_8); + } + this.key = Keys.hmacShaKeyFor(encodedSecret); + } +} diff --git a/src/main/java/com/imjustdoom/pluginsite/config/custom/SiteConfig.java b/src/main/java/com/imjustdoom/pluginsite/config/custom/SiteConfig.java index cbd476d..48897d5 100644 --- a/src/main/java/com/imjustdoom/pluginsite/config/custom/SiteConfig.java +++ b/src/main/java/com/imjustdoom/pluginsite/config/custom/SiteConfig.java @@ -9,7 +9,7 @@ import org.springframework.util.unit.DataUnit; @Getter -@ConfigurationProperties(prefix = "app") +@ConfigurationProperties("app") @ConstructorBinding @AllArgsConstructor public class SiteConfig { diff --git a/src/main/java/com/imjustdoom/pluginsite/config/security/SecurityConfig.java b/src/main/java/com/imjustdoom/pluginsite/config/security/SecurityConfig.java index ff26b3f..f256609 100644 --- a/src/main/java/com/imjustdoom/pluginsite/config/security/SecurityConfig.java +++ b/src/main/java/com/imjustdoom/pluginsite/config/security/SecurityConfig.java @@ -1,37 +1,74 @@ package com.imjustdoom.pluginsite.config.security; +import com.imjustdoom.pluginsite.config.custom.SiteConfig; +import com.imjustdoom.pluginsite.config.security.jwt.JwtAuthenticationFilter; import lombok.AllArgsConstructor; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.http.HttpStatus; +import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.csrf.CookieCsrfTokenRepository; @Configuration @EnableWebSecurity +@EnableGlobalMethodSecurity(prePostEnabled = true) @AllArgsConstructor public class SecurityConfig extends WebSecurityConfigurerAdapter { - private final DaoAuthenticationProvider authProvider; + private final UserDetailsService userDetailsService; + private final PasswordEncoder passwordEncoder; + + private final JwtAuthenticationFilter jwtAuthenticationFilter; + private final SiteConfig siteConfig; + + @Bean + @Override + public AuthenticationManager authenticationManagerBean() throws Exception { + return super.authenticationManagerBean(); + } @Override - protected void configure(AuthenticationManagerBuilder auth) { - auth.authenticationProvider(this.authProvider); + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth.userDetailsService(this.userDetailsService).passwordEncoder(this.passwordEncoder); } @Override public void configure(HttpSecurity http) throws Exception { http - .csrf().disable() + .cors() + .and().csrf() + .ignoringAntMatchers("/auth/**") + .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) - .authorizeRequests() - .antMatchers("/admin", "/admin/roles").hasRole("ADMIN") - .antMatchers("/resources/create", "/account/details").authenticated() - .antMatchers("/register", "/login").not().authenticated() + .and().authorizeRequests().anyRequest().permitAll() - .anyRequest().permitAll() - .and() - .formLogin().loginProcessingUrl("/login"); + .and().logout().disable() + .httpBasic().disable() + .formLogin().disable() + .exceptionHandling().authenticationEntryPoint((request, response, authException) -> response.sendError(HttpStatus.NOT_FOUND.value())) + + .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) + + .and().addFilterBefore(this.jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); } + +// @Bean +// public CorsConfigurationSource corsConfigurationSource() { +// CorsConfiguration configuration = new CorsConfiguration(); +// configuration.setAllowedOrigins(Collections.singletonList(this.siteConfig.getDomain())); +// configuration.addAllowedHeader("*"); +// configuration.addAllowedMethod("*"); +// UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); +// source.registerCorsConfiguration("/**", configuration); +// return source; +// } } \ No newline at end of file diff --git a/src/main/java/com/imjustdoom/pluginsite/config/security/jwt/JwtAuthenticationFilter.java b/src/main/java/com/imjustdoom/pluginsite/config/security/jwt/JwtAuthenticationFilter.java new file mode 100644 index 0000000..bf67910 --- /dev/null +++ b/src/main/java/com/imjustdoom/pluginsite/config/security/jwt/JwtAuthenticationFilter.java @@ -0,0 +1,55 @@ +package com.imjustdoom.pluginsite.config.security.jwt; + +import lombok.RequiredArgsConstructor; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +@RequiredArgsConstructor +@Component +public class JwtAuthenticationFilter extends OncePerRequestFilter { + private final JwtProvider jwtProvider; + private final UserDetailsService userDetailsService; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + String jwt = this.getJwtToken(request); + System.out.println("Doing filter A " + jwt); + if (jwt != null && !jwt.isEmpty() && this.jwtProvider.validateToken(jwt)) { + System.out.println("Doing filter B"); + String username = this.jwtProvider.getSubjectFromToken(jwt); + System.out.println("Doing filter B " + username); + UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); + System.out.println("Doing filter C " + userDetails); + UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); + System.out.println("Doing filter D " + authentication); + authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(authentication); + System.out.println("Set authentication"); + } + + filterChain.doFilter(request, response); + } + + private String getJwtToken(HttpServletRequest request) { + if (request.getCookies() == null) + return null; + for (Cookie cookie : request.getCookies()) { + if (!cookie.getName().equals(JwtProvider.COOKIE_NAME)) + continue; + return cookie.getValue(); + } + return null; + } +} diff --git a/src/main/java/com/imjustdoom/pluginsite/config/security/jwt/JwtProvider.java b/src/main/java/com/imjustdoom/pluginsite/config/security/jwt/JwtProvider.java new file mode 100644 index 0000000..197e581 --- /dev/null +++ b/src/main/java/com/imjustdoom/pluginsite/config/security/jwt/JwtProvider.java @@ -0,0 +1,71 @@ +package com.imjustdoom.pluginsite.config.security.jwt; + +import com.imjustdoom.pluginsite.config.custom.JwtConfig; +import com.imjustdoom.pluginsite.model.Account; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.MalformedJwtException; +import io.jsonwebtoken.UnsupportedJwtException; +import io.jsonwebtoken.security.SignatureException; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Component; + +import javax.servlet.http.Cookie; +import java.time.Instant; +import java.util.Date; + +@Component +@RequiredArgsConstructor +public class JwtProvider { + public static final String COOKIE_NAME = "JWT-TOKEN"; + private static final String USER_ID = "userId"; + + private final JwtConfig jwtConfig; + + public Cookie generateTokenCookie(Authentication authentication) { + Cookie cookie = new Cookie(COOKIE_NAME, this.generateToken(authentication)); + cookie.setSecure(this.jwtConfig.isSecureCookie()); + cookie.setHttpOnly(true); + cookie.setMaxAge((int) this.jwtConfig.getExpiryTime().getSeconds()); + cookie.setPath("/"); + if (this.jwtConfig.getDomain() != null) + cookie.setDomain(this.jwtConfig.getDomain()); + + return cookie; + } + + public String generateToken(Authentication authentication) { + Account account = (Account) authentication.getPrincipal(); + + Claims claims = Jwts.claims().setSubject(account.getUsername()); + claims.put(USER_ID, account.getId()); + + return Jwts.builder() + .setClaims(claims) + .setIssuedAt(Date.from(Instant.now())) + .setExpiration(Date.from(Instant.now().plus(this.jwtConfig.getExpiryTime()))) + .signWith(this.jwtConfig.getKey()) + .compact(); + } + + public String getSubjectFromToken(String token) { + return Jwts.parserBuilder() + .setSigningKey(this.jwtConfig.getKey()) + .build() + .parseClaimsJws(token) + .getBody().getSubject(); + } + + public boolean validateToken(String token) { + try { + return Jwts.parserBuilder() + .setSigningKey(this.jwtConfig.getKey()) + .build() + .parseClaimsJws(token) != null; + } catch (ExpiredJwtException | UnsupportedJwtException | MalformedJwtException | SignatureException | IllegalArgumentException ex) { + return false; + } + } +} diff --git a/src/main/java/com/imjustdoom/pluginsite/controller/AccountController.java b/src/main/java/com/imjustdoom/pluginsite/controller/AccountController.java index 882e50b..9344a42 100644 --- a/src/main/java/com/imjustdoom/pluginsite/controller/AccountController.java +++ b/src/main/java/com/imjustdoom/pluginsite/controller/AccountController.java @@ -2,7 +2,6 @@ import com.imjustdoom.pluginsite.config.exception.RestErrorCode; import com.imjustdoom.pluginsite.config.exception.RestException; -import com.imjustdoom.pluginsite.dtos.in.account.CreateAccountRequest; import com.imjustdoom.pluginsite.dtos.in.account.UpdateAccountRequest; import com.imjustdoom.pluginsite.dtos.out.account.AccountDto; import com.imjustdoom.pluginsite.dtos.out.account.SelfAccountDto; @@ -14,10 +13,10 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.web.PageableDefault; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @@ -31,21 +30,14 @@ public class AccountController { private final AccountService accountService; private final MessageService messageService; - @PostMapping("/register") - public String signupSubmit(@RequestBody CreateAccountRequest accountRequest) throws RestException { - Account account = this.accountService.register(accountRequest); - - return ""; // todo return a registration DTO of some sort, probably with a session token - } - - // todo login with JWT - @GetMapping("/details") + @PreAuthorize("isAuthenticated()") public SelfAccountDto getSelfAccountDetails(Account account) { return SelfAccountDto.fromAccount(account); } @PatchMapping("/details") + @PreAuthorize("isAuthenticated()") public SelfAccountDto updateAccountDetails(Account account, @RequestBody UpdateAccountRequest request, @RequestParam(value = "profilePicture", required = false) MultipartFile file) throws RestException { return SelfAccountDto.fromAccount(this.accountService.updateAccountDetails(account, request, file)); @@ -57,6 +49,7 @@ public AccountDto getAccountDetails(@PathVariable int id) throws RestException { } @GetMapping("/groups") + @PreAuthorize("isAuthenticated()") public Page getMessageGroups(Account account, @PageableDefault(size = 25) Pageable pageable) throws RestException { if (pageable.getPageSize() > 50) throw new RestException(RestErrorCode.PAGE_SIZE_TOO_LARGE, "Page size is too large (%s > %s)", pageable.getPageSize(), 50); diff --git a/src/main/java/com/imjustdoom/pluginsite/controller/AdminController.java b/src/main/java/com/imjustdoom/pluginsite/controller/AdminController.java index c20a8e6..ce5061f 100644 --- a/src/main/java/com/imjustdoom/pluginsite/controller/AdminController.java +++ b/src/main/java/com/imjustdoom/pluginsite/controller/AdminController.java @@ -9,6 +9,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.web.PageableDefault; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -19,6 +20,7 @@ @RestController @RequestMapping("/admin") @RequiredArgsConstructor +@PreAuthorize("hasAuthority('ROLE_ADMIN')") public class AdminController { private final ReportService reportService; diff --git a/src/main/java/com/imjustdoom/pluginsite/controller/AuthenticationController.java b/src/main/java/com/imjustdoom/pluginsite/controller/AuthenticationController.java new file mode 100644 index 0000000..fdfb95a --- /dev/null +++ b/src/main/java/com/imjustdoom/pluginsite/controller/AuthenticationController.java @@ -0,0 +1,47 @@ +package com.imjustdoom.pluginsite.controller; + +import com.imjustdoom.pluginsite.config.exception.RestException; +import com.imjustdoom.pluginsite.config.security.jwt.JwtProvider; +import com.imjustdoom.pluginsite.dtos.in.LoginRequest; +import com.imjustdoom.pluginsite.dtos.in.account.CreateAccountRequest; +import com.imjustdoom.pluginsite.model.Account; +import com.imjustdoom.pluginsite.service.AccountService; +import lombok.RequiredArgsConstructor; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletResponse; + +@RestController +@RequestMapping("/auth") +@RequiredArgsConstructor +public class AuthenticationController { + private final AuthenticationManager authenticationManager; + private final AccountService accountService; + private final JwtProvider jwtProvider; + + @PostMapping("/register") + public String register(@RequestBody CreateAccountRequest accountRequest) throws RestException { + Account account = this.accountService.register(accountRequest); + + return ""; // todo return a registration DTO of some sort, probably with a session token + } + + @PostMapping("/login") + public void login(@RequestBody LoginRequest request, HttpServletResponse response) { + Authentication authentication = this.authenticationManager + .authenticate(new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword())); + + SecurityContextHolder.getContext().setAuthentication(authentication); + + Cookie cookie = this.jwtProvider.generateTokenCookie(authentication); + response.addCookie(cookie); + } +} diff --git a/src/main/java/com/imjustdoom/pluginsite/controller/MessageController.java b/src/main/java/com/imjustdoom/pluginsite/controller/MessageController.java index 05c835a..8c275b9 100644 --- a/src/main/java/com/imjustdoom/pluginsite/controller/MessageController.java +++ b/src/main/java/com/imjustdoom/pluginsite/controller/MessageController.java @@ -4,6 +4,7 @@ import com.imjustdoom.pluginsite.model.Account; import com.imjustdoom.pluginsite.service.MessageService; import lombok.RequiredArgsConstructor; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @@ -16,6 +17,7 @@ public class MessageController { private final MessageService messageService; @PostMapping + @PreAuthorize("isAuthenticated()") public void sendMessage(Account account, @RequestParam int groupId, @RequestParam String content) throws RestException { this.messageService.sendMessage(account, groupId, content); } diff --git a/src/main/java/com/imjustdoom/pluginsite/controller/MessageGroupController.java b/src/main/java/com/imjustdoom/pluginsite/controller/MessageGroupController.java index 78058b6..05515da 100644 --- a/src/main/java/com/imjustdoom/pluginsite/controller/MessageGroupController.java +++ b/src/main/java/com/imjustdoom/pluginsite/controller/MessageGroupController.java @@ -9,6 +9,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.web.PageableDefault; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; @@ -25,37 +26,44 @@ public class MessageGroupController { private final MessageService messageService; @PostMapping + @PreAuthorize("isAuthenticated()") public void createGroup(Account sender, @RequestParam int targetId) throws RestException { this.messageService.createGroup(sender, targetId); } @GetMapping("/{groupId}") + @PreAuthorize("isAuthenticated()") public MessageGroupDto getGroup(Account account, @PathVariable int groupId) throws RestException { return MessageGroupDto.fromMessageGroup(this.messageService.getGroup(account, groupId)); } @GetMapping("/{groupId}/messages") + @PreAuthorize("isAuthenticated()") public Page getMessages(Account account, @PathVariable int groupId, @PageableDefault(size = 25) Pageable pageable) throws RestException { return this.messageService.getMessages(account, groupId, pageable) .map(MessageDto::fromMessage); } @DeleteMapping("/{groupId}") + @PreAuthorize("isAuthenticated()") public void deleteGroup(Account account, @PathVariable int groupId) throws RestException { this.messageService.deleteGroup(account, groupId); } @PostMapping("/{groupId}/addUser") + @PreAuthorize("isAuthenticated()") public void addUserToGroup(Account account, @PathVariable int groupId, @RequestParam int targetId) throws RestException { this.messageService.addUserToGroup(account, groupId, targetId); } @PostMapping("/{groupId}/leave") + @PreAuthorize("isAuthenticated()") public void leaveGroup(Account account, @PathVariable int groupId) throws RestException { this.messageService.leaveGroup(account, groupId); } @PatchMapping("/{groupId}/name") + @PreAuthorize("isAuthenticated()") public MessageGroupDto editGroupName(Account account, @PathVariable int groupId, @RequestParam String name) throws RestException { return MessageGroupDto.fromMessageGroup(this.messageService.editGroupName(account, groupId, name)); } diff --git a/src/main/java/com/imjustdoom/pluginsite/controller/ReportController.java b/src/main/java/com/imjustdoom/pluginsite/controller/ReportController.java index 0e27017..95b9336 100644 --- a/src/main/java/com/imjustdoom/pluginsite/controller/ReportController.java +++ b/src/main/java/com/imjustdoom/pluginsite/controller/ReportController.java @@ -4,6 +4,7 @@ import com.imjustdoom.pluginsite.model.Account; import com.imjustdoom.pluginsite.service.ReportService; import lombok.RequiredArgsConstructor; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -16,6 +17,7 @@ public class ReportController { private final ReportService reportService; @PostMapping + @PreAuthorize("isAuthenticated()") public void createReport(Account account, @RequestBody CreateReportRequest request) { this.reportService.createReport(account, request); } diff --git a/src/main/java/com/imjustdoom/pluginsite/controller/ResourceController.java b/src/main/java/com/imjustdoom/pluginsite/controller/ResourceController.java index fab06d0..dac2787 100644 --- a/src/main/java/com/imjustdoom/pluginsite/controller/ResourceController.java +++ b/src/main/java/com/imjustdoom/pluginsite/controller/ResourceController.java @@ -15,6 +15,7 @@ import org.springframework.data.domain.Sort; import org.springframework.data.querydsl.binding.QuerydslPredicate; import org.springframework.data.web.PageableDefault; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -40,16 +41,19 @@ public Page searchResources(@PageableDefault(size = 25, sort @PostMapping public void createResource(Account account, CreateResourceRequest request) throws RestException { + System.out.println("Account " + account + " request: " + request); this.resourceService.createResource(account, request); } @PostMapping("/{resourceId}/edit") + @PreAuthorize("isAuthenticated()") public void updateResourceInfo(Account account, @PathVariable int resourceId, @RequestParam(value = "file", required = false) MultipartFile file, CreateResourceRequest request) throws RestException { this.resourceService.updateResource(account, resourceId, file, request); } // todo properly delete @DeleteMapping("/{resourceId}") + @PreAuthorize("isAuthenticated()") public void deleteResource(@PathVariable int resourceId) { this.resourceRepository.updateStatusById(resourceId, "removed"); } diff --git a/src/main/java/com/imjustdoom/pluginsite/controller/ResourceUpdateController.java b/src/main/java/com/imjustdoom/pluginsite/controller/ResourceUpdateController.java index 385bfcd..906e994 100644 --- a/src/main/java/com/imjustdoom/pluginsite/controller/ResourceUpdateController.java +++ b/src/main/java/com/imjustdoom/pluginsite/controller/ResourceUpdateController.java @@ -11,6 +11,7 @@ import org.springframework.http.ContentDisposition; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -30,16 +31,19 @@ public class ResourceUpdateController { private final ResourceUpdateService resourceUpdateService; @PatchMapping("/{updateId}/status") + @PreAuthorize("isAuthenticated()") public Update changeStatus(Account account, @PathVariable int updateId, @RequestParam String status) throws RestException { return this.resourceUpdateService.changeStatus(account, updateId, status); } @PatchMapping("/{updateId}") + @PreAuthorize("isAuthenticated()") public Update editUpdate(Account account, @PathVariable int updateId, @RequestBody EditResourceUpdateRequest request) throws RestException { return this.resourceUpdateService.editUpdate(account, updateId, request); } - @PostMapping("/") + @PostMapping + @PreAuthorize("isAuthenticated()") public void createUpdate(Account account, @RequestParam int resourceId, @RequestParam MultipartFile file, @RequestBody CreateUpdateRequest updateRequest) throws RestException { this.resourceUpdateService.createUpdate(account, resourceId, file, updateRequest); diff --git a/src/main/java/com/imjustdoom/pluginsite/dtos/in/LoginRequest.java b/src/main/java/com/imjustdoom/pluginsite/dtos/in/LoginRequest.java new file mode 100644 index 0000000..cccdffe --- /dev/null +++ b/src/main/java/com/imjustdoom/pluginsite/dtos/in/LoginRequest.java @@ -0,0 +1,13 @@ +package com.imjustdoom.pluginsite.dtos.in; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Setter +@Getter +@NoArgsConstructor +public class LoginRequest { + private String username; + private String password; +} diff --git a/src/main/java/com/imjustdoom/pluginsite/dtos/out/ProfileDto.java b/src/main/java/com/imjustdoom/pluginsite/dtos/out/ProfileDto.java index fed62a7..067e66a 100644 --- a/src/main/java/com/imjustdoom/pluginsite/dtos/out/ProfileDto.java +++ b/src/main/java/com/imjustdoom/pluginsite/dtos/out/ProfileDto.java @@ -3,6 +3,6 @@ import com.imjustdoom.pluginsite.dtos.out.account.AccountDto; public record ProfileDto(int id, int totalDownloads, - AccountDto accountDto) { + AccountDto account) { } diff --git a/src/main/java/com/imjustdoom/pluginsite/model/Account.java b/src/main/java/com/imjustdoom/pluginsite/model/Account.java index d0be555..e125fee 100644 --- a/src/main/java/com/imjustdoom/pluginsite/model/Account.java +++ b/src/main/java/com/imjustdoom/pluginsite/model/Account.java @@ -7,10 +7,21 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; -import javax.persistence.*; +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Lob; +import javax.persistence.ManyToMany; +import javax.persistence.OneToMany; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.List; @Setter @@ -24,7 +35,7 @@ public Account(String username, String email, String password) { this.email = email; this.password = password; this.joined = LocalDateTime.now(); - this.role = "ROLE_USER"; + this.role = Role.USER; } @Id @@ -52,7 +63,8 @@ public Account(String username, String email, String password) { private int totalDownloads; @Column(nullable = false) - private String role; + @Enumerated(EnumType.STRING) + private Role role; @ManyToMany(mappedBy = "members") private List groups; @@ -60,17 +72,13 @@ public Account(String username, String email, String password) { @Lob @Column(name = "profile_picture") private byte[] profilePicture; - + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "author") private List messages = new ArrayList<>(); @Override public Collection getAuthorities() { - List list = new ArrayList<>(); - - list.add(new SimpleGrantedAuthority(getRole())); - - return list; + return Collections.singleton(new SimpleGrantedAuthority(this.role.toString())); } @Override @@ -92,4 +100,14 @@ public boolean isCredentialsNonExpired() { public boolean isEnabled() { return true; } + + public enum Role { + USER, + ADMIN; + + @Override + public String toString() { + return "ROLE_".concat(super.toString()); + } + } } \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index c31a3b4..a82ad1c 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -15,3 +15,7 @@ app.domain=http://localhost:8080 app.max-updates-per-hour=10 app.max-creations-per-hour=2 app.max-upload-size=10MB + +jwt.expiry-time=30m +jwt.domain=localhost +jwt.secure-cookie=false