diff --git a/pom.xml b/pom.xml
index 7997679..2a39a8b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -68,6 +68,10 @@
org.springframework.boot
spring-boot-starter-web
+
+ org.springframework.boot
+ spring-boot-starter-security
+
diff --git a/src/main/java/cc/pulseapp/api/PulseAPI.java b/src/main/java/cc/pulseapp/api/PulseAPI.java
index d35e733..57bcdc9 100644
--- a/src/main/java/cc/pulseapp/api/PulseAPI.java
+++ b/src/main/java/cc/pulseapp/api/PulseAPI.java
@@ -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
diff --git a/src/main/java/cc/pulseapp/api/controller/v1/AuthController.java b/src/main/java/cc/pulseapp/api/controller/v1/AuthController.java
index 3e13d84..668fb96 100644
--- a/src/main/java/cc/pulseapp/api/controller/v1/AuthController.java
+++ b/src/main/java/cc/pulseapp/api/controller/v1/AuthController.java
@@ -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;
diff --git a/src/main/java/cc/pulseapp/api/controller/v1/UserController.java b/src/main/java/cc/pulseapp/api/controller/v1/UserController.java
new file mode 100644
index 0000000..15acd4b
--- /dev/null
+++ b/src/main/java/cc/pulseapp/api/controller/v1/UserController.java
@@ -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 getUser() {
+ return ResponseEntity.ok(userService.getUser());
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/cc/pulseapp/api/exception/impl/ResourceNotFoundException.java b/src/main/java/cc/pulseapp/api/exception/impl/ResourceNotFoundException.java
new file mode 100644
index 0000000..d002fdd
--- /dev/null
+++ b/src/main/java/cc/pulseapp/api/exception/impl/ResourceNotFoundException.java
@@ -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());
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/cc/pulseapp/api/model/page/StatusPage.java b/src/main/java/cc/pulseapp/api/model/page/StatusPage.java
index 958968b..f398ea5 100644
--- a/src/main/java/cc/pulseapp/api/model/page/StatusPage.java
+++ b/src/main/java/cc/pulseapp/api/model/page/StatusPage.java
@@ -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;
diff --git a/src/main/java/cc/pulseapp/api/model/user/User.java b/src/main/java/cc/pulseapp/api/model/user/User.java
index a5732fb..4a88567 100644
--- a/src/main/java/cc/pulseapp/api/model/user/User.java
+++ b/src/main/java/cc/pulseapp/api/model/user/User.java
@@ -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.
*
diff --git a/src/main/java/cc/pulseapp/api/service/AuthService.java b/src/main/java/cc/pulseapp/api/service/AuthService.java
index 6cedd75..f495a58 100644
--- a/src/main/java/cc/pulseapp/api/service/AuthService.java
+++ b/src/main/java/cc/pulseapp/api/service/AuthService.java
@@ -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.
*
diff --git a/src/main/java/cc/pulseapp/api/service/UserService.java b/src/main/java/cc/pulseapp/api/service/UserService.java
new file mode 100644
index 0000000..370eda1
--- /dev/null
+++ b/src/main/java/cc/pulseapp/api/service/UserService.java
@@ -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())));
+ }
+}
\ No newline at end of file