/@me route

This commit is contained in:
Braydon 2024-09-16 21:26:17 -04:00
parent d326767c7c
commit ba473d1dae
9 changed files with 155 additions and 6 deletions

@ -68,6 +68,10 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Libraries -->
<dependency>

@ -5,6 +5,7 @@ import lombok.SneakyThrows;
import lombok.extern.log4j.Log4j2;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration;
import java.io.File;
import java.nio.file.Files;
@ -14,7 +15,7 @@ import java.util.Objects;
/**
* @author Braydon
*/
@SpringBootApplication
@SpringBootApplication(exclude = UserDetailsServiceAutoConfiguration.class)
@Log4j2(topic = "PulseApp")
public class PulseAPI {
@SneakyThrows

@ -25,7 +25,7 @@ import org.springframework.web.bind.annotation.RestController;
@RequestMapping(value = "/v1/auth", produces = MediaType.APPLICATION_JSON_VALUE)
public final class AuthController {
/**
* The user service to use.
* The auth service to use.
*/
@NonNull private final AuthService authService;

@ -0,0 +1,43 @@
package cc.pulseapp.api.controller.v1;
import cc.pulseapp.api.model.user.UserDTO;
import cc.pulseapp.api.service.UserService;
import lombok.NonNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
/**
* This controller is responsible for
* handling user authentication requests.
*
* @author Braydon
*/
@RestController
@RequestMapping(value = "/v1/user", produces = MediaType.APPLICATION_JSON_VALUE)
public final class UserController {
/**
* The user service to use.
*/
@NonNull private final UserService userService;
@Autowired
public UserController(@NonNull UserService userService) {
this.userService = userService;
}
/**
* A GET endpoint to get the
* currently authenticated user.
*
* @return the currently authenticated user
*/
@PostMapping("/@me") @ResponseBody @NonNull
public ResponseEntity<UserDTO> getUser() {
return ResponseEntity.ok(userService.getUser());
}
}

@ -0,0 +1,19 @@
package cc.pulseapp.api.exception.impl;
import cc.pulseapp.api.model.IGenericResponse;
import lombok.NonNull;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
/**
* This exception is raised
* when a resource is not found.
*
* @author Braydon
*/
@ResponseStatus(HttpStatus.NOT_FOUND)
public final class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException(@NonNull IGenericResponse error) {
super(error.name());
}
}

@ -34,12 +34,12 @@ public final class StatusPage {
@NonNull private final String slug;
/**
* The id to the logo of this status page, if any.
* The hash to the logo of this status page, if any.
*/
private final String logo;
/**
* The id to the banner of this status page, if any.
* The hash to the banner of this status page, if any.
*/
private final String banner;

@ -39,6 +39,11 @@ public final class User {
*/
@NonNull private final String passwordSalt;
/**
* The hash to the avatar of this user, if any.
*/
private final String avatar;
/**
* The tier of this user.
*/
@ -47,13 +52,22 @@ public final class User {
/**
* The flags for this user.
*/
private final int flags;
private int flags;
/**
* The date this user last logged in.
*/
@NonNull private Date lastLogin;
/**
* Add a flag to this user.
*
* @param flag the flag to add
*/
public void addFlag(@NonNull UserFlag flag) {
flags |= flag.ordinal();
}
/**
* Check if this user has a given flag.
*

@ -5,6 +5,7 @@ import cc.pulseapp.api.common.HashUtils;
import cc.pulseapp.api.common.RequestUtils;
import cc.pulseapp.api.common.StringUtils;
import cc.pulseapp.api.exception.impl.BadRequestException;
import cc.pulseapp.api.exception.impl.ResourceNotFoundException;
import cc.pulseapp.api.model.IGenericResponse;
import cc.pulseapp.api.model.user.Session;
import cc.pulseapp.api.model.user.User;
@ -17,6 +18,7 @@ import cc.pulseapp.api.repository.UserRepository;
import jakarta.servlet.http.HttpServletRequest;
import lombok.NonNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import java.util.Base64;
@ -81,7 +83,7 @@ public final class AuthService {
return generateSession(request, userRepository.save(new User(
snowflakeService.generateSnowflake(), input.getEmail(), input.getUsername(),
HashUtils.hash(salt, input.getPassword()), Base64.getEncoder().encodeToString(salt),
UserTier.FREE, 0, now
null, UserTier.FREE, 0, now
)));
}
@ -111,6 +113,38 @@ public final class AuthService {
return generateSession(request, userRepository.save(user));
}
/**
* Get the authenticated user.
*
* @return the authenticated user
* @throws ResourceNotFoundException if the user doesn't exist
*/
@NonNull
public User getAuthenticatedUser() throws ResourceNotFoundException {
Session session = (Session) SecurityContextHolder.getContext().getAuthentication().getCredentials();
return getUserFromSnowflake(session.getUserSnowflake());
}
/**
* Get a user from a snowflake, if the user exists.
*
* @param snowflake the snowflake of the user
* @return the user from the snowflake
* @throws BadRequestException if the snowflake is invalid
* @throws ResourceNotFoundException if the user doesn't exist
*/
@NonNull
public User getUserFromSnowflake(long snowflake) {
if (snowflake < 1L) {
throw new ResourceNotFoundException(Error.USER_NOT_FOUND);
}
User user = userRepository.findById(snowflake).orElse(null);
if (user == null) {
throw new ResourceNotFoundException(Error.USER_NOT_FOUND);
}
return user;
}
/**
* Generate an auth token for a user.
*

@ -0,0 +1,34 @@
package cc.pulseapp.api.service;
import cc.pulseapp.api.model.user.User;
import cc.pulseapp.api.model.user.UserDTO;
import lombok.NonNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Date;
/**
* @author Braydon
*/
@Service
public final class UserService {
@NonNull private final AuthService authService;
/**
* The service to use for snowflake timestamp extraction.
*/
@NonNull private final SnowflakeService snowflakeService;
@Autowired
public UserService(@NonNull AuthService authService, @NonNull SnowflakeService snowflakeService) {
this.authService = authService;
this.snowflakeService = snowflakeService;
}
@NonNull
public UserDTO getUser() {
User user = authService.getAuthenticatedUser();
return UserDTO.asDTO(user, new Date(snowflakeService.extractCreationTime(user.getSnowflake())));
}
}