Initial commit
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
package net.miarma.backend.core;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class CoreApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(CoreApplication.class, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package net.miarma.backend.core.config;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import net.miarma.backend.core.model.User;
|
||||
import net.miarma.backend.core.service.JwtService;
|
||||
import net.miarma.backend.core.service.UserService;
|
||||
|
||||
@Component
|
||||
public class JwtFilter extends OncePerRequestFilter {
|
||||
|
||||
private final JwtService jwtService;
|
||||
private final UserService userService;
|
||||
private final long refreshThreshold = 300_000;
|
||||
|
||||
public JwtFilter(JwtService jwtService, UserService userService) {
|
||||
this.jwtService = jwtService;
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
FilterChain filterChain) throws ServletException, IOException {
|
||||
|
||||
String authHeader = request.getHeader("Authorization");
|
||||
if (authHeader != null && authHeader.startsWith("Bearer ")) {
|
||||
String token = authHeader.substring(7);
|
||||
|
||||
try {
|
||||
if (jwtService.validateToken(token)) {
|
||||
UUID userId = jwtService.getUserId(token);
|
||||
short serviceId = jwtService.getServiceId(token);
|
||||
|
||||
User user = userService.getById(userId);
|
||||
|
||||
List<GrantedAuthority> authorities = List.of(
|
||||
new SimpleGrantedAuthority("ROLE_" + user.getGlobalRole())
|
||||
);
|
||||
|
||||
UsernamePasswordAuthenticationToken auth =
|
||||
new UsernamePasswordAuthenticationToken(user, null, authorities);
|
||||
auth.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
||||
SecurityContextHolder.getContext().setAuthentication(auth);
|
||||
|
||||
long timeLeft = jwtService.getExpiration(token).getTime() - System.currentTimeMillis();
|
||||
if (timeLeft < refreshThreshold) {
|
||||
String newToken = jwtService.generateToken(userId, serviceId);
|
||||
response.setHeader("X-Refresh-Token", newToken);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid or expired token");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package net.miarma.backend.core.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class SecurityConfig {
|
||||
private final JwtFilter jwtFilter;
|
||||
|
||||
public SecurityConfig(JwtFilter jwtFilter) {
|
||||
this.jwtFilter = jwtFilter;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.csrf(csrf -> csrf.disable())
|
||||
.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
.requestMatchers("/auth/**").permitAll()
|
||||
.anyRequest().authenticated()
|
||||
);
|
||||
|
||||
http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder(12);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package net.miarma.backend.core.controller;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
import net.miarma.backend.core.dto.LoginRequest;
|
||||
import net.miarma.backend.core.dto.LoginResponse;
|
||||
import net.miarma.backend.core.service.AuthService;
|
||||
import net.miarma.backend.core.service.JwtService;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/auth")
|
||||
public class AuthController {
|
||||
|
||||
private final AuthService authService;
|
||||
private final JwtService jwtService;
|
||||
|
||||
public AuthController(AuthService authService, JwtService jwtService) {
|
||||
this.authService = authService;
|
||||
this.jwtService = jwtService;
|
||||
}
|
||||
|
||||
@PostMapping("/login")
|
||||
public ResponseEntity<LoginResponse> login(@Valid @RequestBody LoginRequest request) {
|
||||
LoginResponse response = authService.login(request);
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
@PostMapping("/refresh")
|
||||
public ResponseEntity<?> refreshToken(@RequestHeader("Authorization") String authHeader) {
|
||||
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
|
||||
return ResponseEntity.status(401).body("Token missing");
|
||||
}
|
||||
|
||||
String token = authHeader.substring(7);
|
||||
if (!jwtService.validateToken(token)) {
|
||||
return ResponseEntity.status(401).body("Invalid token");
|
||||
}
|
||||
|
||||
UUID userId = jwtService.getUserId(token);
|
||||
short serviceId = jwtService.getServiceId(token);
|
||||
|
||||
String newToken = jwtService.generateToken(userId, serviceId);
|
||||
|
||||
return ResponseEntity.ok(Map.of(
|
||||
"token", newToken,
|
||||
"userId", userId,
|
||||
"serviceId", serviceId
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package net.miarma.backend.core.controller;
|
||||
|
||||
public class CredentialController {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package net.miarma.backend.core.controller;
|
||||
|
||||
public class FileController {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package net.miarma.backend.core.controller;
|
||||
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/users")
|
||||
public class UserController {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package net.miarma.backend.core.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
|
||||
public class LoginRequest {
|
||||
@NotBlank
|
||||
private String username;
|
||||
|
||||
@NotBlank
|
||||
private String password;
|
||||
|
||||
private short serviceId;
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public short getServiceId() {
|
||||
return serviceId;
|
||||
}
|
||||
|
||||
public void setServiceId(short serviceId) {
|
||||
this.serviceId = serviceId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package net.miarma.backend.core.dto;
|
||||
|
||||
public class LoginResponse {
|
||||
private String token;
|
||||
private short serviceId;
|
||||
private UserDto user;
|
||||
|
||||
public LoginResponse(String token, short serviceId, UserDto user) {
|
||||
this.token = token;
|
||||
this.serviceId = serviceId;
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public void setToken(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
public short getServiceId() {
|
||||
return serviceId;
|
||||
}
|
||||
|
||||
public void setServiceId(short serviceId) {
|
||||
this.serviceId = serviceId;
|
||||
}
|
||||
|
||||
public UserDto getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
public void setUser(UserDto user) {
|
||||
this.user = user;
|
||||
}
|
||||
}
|
||||
81
core/src/main/java/net/miarma/backend/core/dto/UserDto.java
Normal file
81
core/src/main/java/net/miarma/backend/core/dto/UserDto.java
Normal file
@@ -0,0 +1,81 @@
|
||||
package net.miarma.backend.core.dto;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.UUID;
|
||||
|
||||
public class UserDto {
|
||||
private UUID userId;
|
||||
private String displayName;
|
||||
private String avatar;
|
||||
private Byte globalStatus;
|
||||
private Byte globalRole;
|
||||
private Instant createdAt;
|
||||
private Instant updatedAt;
|
||||
|
||||
public UserDto(UUID userId, String displayName, String avatar, Byte globalStatus, Byte globalRole,
|
||||
Instant createdAt, Instant updatedAt) {
|
||||
this.userId = userId;
|
||||
this.displayName = displayName;
|
||||
this.avatar = avatar;
|
||||
this.globalStatus = globalStatus;
|
||||
this.globalRole = globalRole;
|
||||
this.createdAt = createdAt;
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
|
||||
public UUID getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public void setUserId(UUID userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public void setDisplayName(String displayName) {
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
public String getAvatar() {
|
||||
return avatar;
|
||||
}
|
||||
|
||||
public void setAvatar(String avatar) {
|
||||
this.avatar = avatar;
|
||||
}
|
||||
|
||||
public Byte getGlobalStatus() {
|
||||
return globalStatus;
|
||||
}
|
||||
|
||||
public void setGlobalStatus(Byte globalStatus) {
|
||||
this.globalStatus = globalStatus;
|
||||
}
|
||||
|
||||
public Byte getGlobalRole() {
|
||||
return globalRole;
|
||||
}
|
||||
|
||||
public void setGlobalRole(Byte globalRole) {
|
||||
this.globalRole = globalRole;
|
||||
}
|
||||
|
||||
public Instant getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(Instant createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
public Instant getUpdatedAt() {
|
||||
return updatedAt;
|
||||
}
|
||||
|
||||
public void setUpdatedAt(Instant updatedAt) {
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
}
|
||||
177
core/src/main/java/net/miarma/backend/core/model/Credential.java
Normal file
177
core/src/main/java/net/miarma/backend/core/model/Credential.java
Normal file
@@ -0,0 +1,177 @@
|
||||
package net.miarma.backend.core.model;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.time.Instant;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.hibernate.annotations.CreationTimestamp;
|
||||
import org.hibernate.annotations.UpdateTimestamp;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.PostLoad;
|
||||
import jakarta.persistence.PrePersist;
|
||||
import jakarta.persistence.PreUpdate;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.Transient;
|
||||
import jakarta.persistence.UniqueConstraint;
|
||||
|
||||
@Entity
|
||||
@Table(
|
||||
name = "credentials",
|
||||
uniqueConstraints = {
|
||||
@UniqueConstraint(columnNames = {"service_id", "username"}),
|
||||
@UniqueConstraint(columnNames = {"service_id", "email"})
|
||||
}
|
||||
)
|
||||
public class Credential {
|
||||
|
||||
@Id
|
||||
@Column(columnDefinition = "BINARY(16)")
|
||||
private byte[] credentialIdBin;
|
||||
|
||||
@Column(name = "user_id", columnDefinition = "BINARY(16)")
|
||||
private byte[] userIdBin;
|
||||
|
||||
@Transient
|
||||
private UUID credentialId;
|
||||
|
||||
@Transient
|
||||
private UUID userId;
|
||||
|
||||
@Column(name = "service_id")
|
||||
private Byte serviceId;
|
||||
|
||||
private String username;
|
||||
private String email;
|
||||
private String password;
|
||||
|
||||
private Byte status;
|
||||
|
||||
@CreationTimestamp
|
||||
private Instant createdAt;
|
||||
|
||||
@UpdateTimestamp
|
||||
private Instant updatedAt;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "user_id", insertable = false, updatable = false)
|
||||
private User user;
|
||||
|
||||
@PrePersist
|
||||
@PreUpdate
|
||||
private void prePersist() {
|
||||
if (credentialId != null) {
|
||||
ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
|
||||
bb.putLong(credentialId.getMostSignificantBits());
|
||||
bb.putLong(credentialId.getLeastSignificantBits());
|
||||
credentialIdBin = bb.array();
|
||||
}
|
||||
if (userId != null) {
|
||||
ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
|
||||
bb.putLong(userId.getMostSignificantBits());
|
||||
bb.putLong(userId.getLeastSignificantBits());
|
||||
userIdBin = bb.array();
|
||||
}
|
||||
}
|
||||
|
||||
@PostLoad
|
||||
private void postLoad() {
|
||||
if (credentialIdBin != null) {
|
||||
ByteBuffer bb = ByteBuffer.wrap(credentialIdBin);
|
||||
long high = bb.getLong();
|
||||
long low = bb.getLong();
|
||||
credentialId = new UUID(high, low);
|
||||
}
|
||||
if (userIdBin != null) {
|
||||
ByteBuffer bb = ByteBuffer.wrap(userIdBin);
|
||||
long high = bb.getLong();
|
||||
long low = bb.getLong();
|
||||
userId = new UUID(high, low);
|
||||
}
|
||||
}
|
||||
|
||||
public UUID getCredentialId() {
|
||||
return credentialId;
|
||||
}
|
||||
|
||||
public void setCredentialId(UUID credentialId) {
|
||||
this.credentialId = credentialId;
|
||||
}
|
||||
|
||||
public UUID getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public void setUserId(UUID userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
public Byte getServiceId() {
|
||||
return serviceId;
|
||||
}
|
||||
|
||||
public void setServiceId(Byte serviceId) {
|
||||
this.serviceId = serviceId;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public Byte getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(Byte status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public Instant getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(Instant createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
public Instant getUpdatedAt() {
|
||||
return updatedAt;
|
||||
}
|
||||
|
||||
public void setUpdatedAt(Instant updatedAt) {
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
|
||||
public User getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
public void setUser(User user) {
|
||||
this.user = user;
|
||||
}
|
||||
}
|
||||
106
core/src/main/java/net/miarma/backend/core/model/File.java
Normal file
106
core/src/main/java/net/miarma/backend/core/model/File.java
Normal file
@@ -0,0 +1,106 @@
|
||||
package net.miarma.backend.core.model;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.UUID;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.PrePersist;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.Transient;
|
||||
|
||||
@Entity
|
||||
@Table(name = "files")
|
||||
public class File {
|
||||
|
||||
@Id
|
||||
@Column(name = "file_id", columnDefinition = "BINARY(16)")
|
||||
private byte[] fileIdBin;
|
||||
|
||||
@Transient
|
||||
private UUID fileId;
|
||||
|
||||
@Column(name = "file_name", nullable = false, length = 128)
|
||||
private String fileName;
|
||||
|
||||
@Column(name = "file_path", nullable = false, length = 256)
|
||||
private String filePath;
|
||||
|
||||
@Column(name = "mime_type", nullable = false, length = 64)
|
||||
private String mimeType;
|
||||
|
||||
@Column(name = "uploaded_by", columnDefinition = "BINARY(16)", nullable = false)
|
||||
private byte[] uploadedByBin;
|
||||
|
||||
@Transient
|
||||
private UUID uploadedBy;
|
||||
|
||||
@Column(name = "uploaded_at", nullable = false, updatable = false)
|
||||
private Instant uploadedAt;
|
||||
|
||||
@Column(name = "context", nullable = false)
|
||||
private Short context;
|
||||
|
||||
@PrePersist
|
||||
public void prePersist() {
|
||||
if (fileId == null) {
|
||||
fileId = UUID.randomUUID();
|
||||
}
|
||||
if (uploadedAt == null) {
|
||||
uploadedAt = Instant.now();
|
||||
}
|
||||
}
|
||||
|
||||
public UUID getFileId() {
|
||||
return fileId;
|
||||
}
|
||||
|
||||
public void setFileId(UUID fileId) {
|
||||
this.fileId = fileId;
|
||||
}
|
||||
|
||||
public String getFileName() {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public void setFileName(String fileName) {
|
||||
this.fileName = fileName;
|
||||
}
|
||||
|
||||
public String getFilePath() {
|
||||
return filePath;
|
||||
}
|
||||
|
||||
public void setFilePath(String filePath) {
|
||||
this.filePath = filePath;
|
||||
}
|
||||
|
||||
public String getMimeType() {
|
||||
return mimeType;
|
||||
}
|
||||
|
||||
public void setMimeType(String mimeType) {
|
||||
this.mimeType = mimeType;
|
||||
}
|
||||
|
||||
public UUID getUploadedBy() {
|
||||
return uploadedBy;
|
||||
}
|
||||
|
||||
public void setUploadedBy(UUID uploadedBy) {
|
||||
this.uploadedBy = uploadedBy;
|
||||
}
|
||||
|
||||
public Instant getUploadedAt() {
|
||||
return uploadedAt;
|
||||
}
|
||||
|
||||
public Short getContext() {
|
||||
return context;
|
||||
}
|
||||
|
||||
public void setContext(Short context) {
|
||||
this.context = context;
|
||||
}
|
||||
}
|
||||
123
core/src/main/java/net/miarma/backend/core/model/User.java
Normal file
123
core/src/main/java/net/miarma/backend/core/model/User.java
Normal file
@@ -0,0 +1,123 @@
|
||||
package net.miarma.backend.core.model;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.time.Instant;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.hibernate.annotations.CreationTimestamp;
|
||||
import org.hibernate.annotations.UpdateTimestamp;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.PostLoad;
|
||||
import jakarta.persistence.PrePersist;
|
||||
import jakarta.persistence.PreUpdate;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.Transient;
|
||||
|
||||
@Entity
|
||||
@Table(name = "users")
|
||||
public class User {
|
||||
|
||||
@Id
|
||||
@Column(name = "user_id", columnDefinition = "BINARY(16)")
|
||||
private byte[] userIdBin;
|
||||
|
||||
@Transient
|
||||
private UUID userId;
|
||||
|
||||
@Column(name = "display_name", nullable = false)
|
||||
private String displayName;
|
||||
|
||||
private String avatar;
|
||||
|
||||
@Column(name = "global_status")
|
||||
private Byte globalStatus;
|
||||
|
||||
@Column(name = "global_role")
|
||||
private Byte globalRole;
|
||||
|
||||
@CreationTimestamp
|
||||
private Instant createdAt;
|
||||
|
||||
@UpdateTimestamp
|
||||
private Instant updatedAt;
|
||||
|
||||
@PrePersist
|
||||
@PreUpdate
|
||||
private void prePersist() {
|
||||
if (userId != null) {
|
||||
ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
|
||||
bb.putLong(userId.getMostSignificantBits());
|
||||
bb.putLong(userId.getLeastSignificantBits());
|
||||
userIdBin = bb.array();
|
||||
}
|
||||
}
|
||||
|
||||
@PostLoad
|
||||
private void postLoad() {
|
||||
if (userIdBin != null) {
|
||||
ByteBuffer bb = ByteBuffer.wrap(userIdBin);
|
||||
long high = bb.getLong();
|
||||
long low = bb.getLong();
|
||||
userId = new UUID(high, low);
|
||||
}
|
||||
}
|
||||
|
||||
public UUID getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public void setUserId(UUID userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public void setDisplayName(String displayName) {
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
public String getAvatar() {
|
||||
return avatar;
|
||||
}
|
||||
|
||||
public void setAvatar(String avatar) {
|
||||
this.avatar = avatar;
|
||||
}
|
||||
|
||||
public Byte getGlobalStatus() {
|
||||
return globalStatus;
|
||||
}
|
||||
|
||||
public void setGlobalStatus(Byte globalStatus) {
|
||||
this.globalStatus = globalStatus;
|
||||
}
|
||||
|
||||
public Byte getGlobalRole() {
|
||||
return globalRole;
|
||||
}
|
||||
|
||||
public void setGlobalRole(Byte globalRole) {
|
||||
this.globalRole = globalRole;
|
||||
}
|
||||
|
||||
public Instant getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(Instant createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
public Instant getUpdatedAt() {
|
||||
return updatedAt;
|
||||
}
|
||||
|
||||
public void setUpdatedAt(Instant updatedAt) {
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package net.miarma.backend.core.repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import net.miarma.backend.core.model.Credential;
|
||||
|
||||
public interface CredentialRepository extends JpaRepository<Credential, UUID> {
|
||||
|
||||
Optional<Credential> findByServiceIdAndUsername(short serviceId, String username);
|
||||
|
||||
Optional<Credential> findByServiceIdAndEmail(short serviceId, String email);
|
||||
|
||||
Optional<Credential> findByUserIdAndServiceId(UUID userId, short serviceId);
|
||||
|
||||
List<Credential> findByUserId(UUID userId);
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package net.miarma.backend.core.repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import net.miarma.backend.core.model.File;
|
||||
|
||||
public interface FileRepository extends JpaRepository<File, UUID> {
|
||||
|
||||
Optional<File> findById(UUID fileId);
|
||||
|
||||
List<File> findByUploadedBy(UUID uploadedBy);
|
||||
|
||||
List<File> findByContext(short context);
|
||||
|
||||
List<File> findByUploadedByAndContext(UUID uploadedBy, short context);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package net.miarma.backend.core.repository;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import net.miarma.backend.core.model.User;
|
||||
|
||||
public interface UserRepository extends JpaRepository<User, UUID> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package net.miarma.backend.core.service;
|
||||
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import net.miarma.backend.core.dto.LoginRequest;
|
||||
import net.miarma.backend.core.dto.LoginResponse;
|
||||
import net.miarma.backend.core.dto.UserDto;
|
||||
import net.miarma.backend.core.model.Credential;
|
||||
|
||||
@Service
|
||||
public class AuthService {
|
||||
|
||||
private final CredentialService credentialService;
|
||||
private final JwtService jwtService;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
|
||||
public AuthService(CredentialService credentialService, JwtService jwtService,
|
||||
PasswordEncoder passwordEncoder) {
|
||||
this.credentialService = credentialService;
|
||||
this.jwtService = jwtService;
|
||||
this.passwordEncoder = passwordEncoder;
|
||||
}
|
||||
|
||||
public LoginResponse login(LoginRequest request) {
|
||||
Credential cred = credentialService.getByUserIdAndService(request.getServiceId(), request.getUsername());
|
||||
|
||||
if (!passwordEncoder.matches(request.getPassword(), cred.getPassword())) {
|
||||
throw new RuntimeException("Invalid credentials");
|
||||
}
|
||||
|
||||
String token = jwtService.generateToken(cred.getUserId(), request.getServiceId());
|
||||
|
||||
UserDto userDto = new UserDto(
|
||||
cred.getUser().getUserId(),
|
||||
cred.getUser().getDisplayName(),
|
||||
cred.getUser().getAvatar(),
|
||||
cred.getUser().getGlobalStatus(),
|
||||
cred.getUser().getGlobalRole(),
|
||||
cred.getUser().getCreatedAt(),
|
||||
cred.getUser().getUpdatedAt()
|
||||
);
|
||||
|
||||
return new LoginResponse(token, request.getServiceId(), userDto);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package net.miarma.backend.core.service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import net.miarma.backend.core.model.Credential;
|
||||
import net.miarma.backend.core.repository.CredentialRepository;
|
||||
|
||||
@Service
|
||||
@Transactional
|
||||
public class CredentialService {
|
||||
|
||||
private final CredentialRepository credentialRepository;
|
||||
|
||||
public CredentialService(CredentialRepository credentialRepository) {
|
||||
this.credentialRepository = credentialRepository;
|
||||
}
|
||||
|
||||
public Credential getById(UUID id) {
|
||||
return credentialRepository.findById(id)
|
||||
.orElseThrow(() -> new RuntimeException("Credential not found"));
|
||||
}
|
||||
|
||||
public Credential create(Credential credential) {
|
||||
// TODO: validate duplicates here
|
||||
return credentialRepository.save(credential);
|
||||
}
|
||||
|
||||
public List<Credential> getByUserId(UUID userId) {
|
||||
List<Credential> creds = credentialRepository.findByUserId(userId);
|
||||
if (creds.isEmpty()) {
|
||||
throw new RuntimeException("User has no credentials");
|
||||
}
|
||||
return creds;
|
||||
}
|
||||
|
||||
public Credential getByUserIdAndService(short serviceId, String username) {
|
||||
return credentialRepository.findByServiceIdAndUsername(serviceId, username)
|
||||
.orElseThrow(() -> new RuntimeException("Credential not found in this site"));
|
||||
}
|
||||
|
||||
public Credential getForLogin(short serviceId, String username) {
|
||||
return credentialRepository.findByServiceIdAndUsername(serviceId, username)
|
||||
.orElseThrow(() -> new RuntimeException("Invalid credentials"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package net.miarma.backend.core.service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import net.miarma.backend.core.model.File;
|
||||
import net.miarma.backend.core.repository.FileRepository;
|
||||
|
||||
@Service
|
||||
@Transactional
|
||||
public class FileService {
|
||||
|
||||
private final FileRepository fileRepository;
|
||||
|
||||
public FileService(FileRepository fileRepository) {
|
||||
this.fileRepository = fileRepository;
|
||||
}
|
||||
|
||||
public File get(UUID fileId) {
|
||||
return fileRepository.findById(fileId)
|
||||
.orElseThrow(() -> new RuntimeException("File not found"));
|
||||
}
|
||||
|
||||
public List<File> listByUser(UUID userId) {
|
||||
return fileRepository.findByUploadedBy(userId);
|
||||
}
|
||||
|
||||
public List<File> listByContext(short context) {
|
||||
return fileRepository.findByContext(context);
|
||||
}
|
||||
|
||||
public File create(File file) {
|
||||
return fileRepository.save(file);
|
||||
}
|
||||
|
||||
public void delete(UUID fileId) {
|
||||
if (!fileRepository.existsById(fileId)) {
|
||||
throw new RuntimeException("File not found");
|
||||
}
|
||||
fileRepository.deleteById(fileId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package net.miarma.backend.core.service;
|
||||
|
||||
import io.jsonwebtoken.*;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.security.Key;
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
|
||||
@Service
|
||||
public class JwtService {
|
||||
|
||||
private final String secret = "miarma-esto-es-un-secreto-super-largo-para-jwt-1234567890";
|
||||
private final Key key = Keys.hmacShaKeyFor(secret.getBytes());
|
||||
|
||||
private final long expiration = 3600_000;
|
||||
|
||||
public String generateToken(UUID userId, short serviceId) {
|
||||
Date now = new Date();
|
||||
Date exp = new Date(now.getTime() + expiration);
|
||||
|
||||
return Jwts.builder()
|
||||
.setSubject(userId.toString())
|
||||
.claim("service", serviceId)
|
||||
.setIssuedAt(now)
|
||||
.setExpiration(exp)
|
||||
.signWith(key, SignatureAlgorithm.HS256)
|
||||
.compact();
|
||||
}
|
||||
|
||||
public boolean validateToken(String token) {
|
||||
try {
|
||||
Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
|
||||
return true;
|
||||
} catch (JwtException | IllegalArgumentException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public UUID getUserId(String token) {
|
||||
Claims claims = Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
|
||||
return UUID.fromString(claims.getSubject());
|
||||
}
|
||||
|
||||
public short getServiceId(String token) {
|
||||
Claims claims = Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
|
||||
return ((Number) claims.get("service")).shortValue();
|
||||
}
|
||||
|
||||
public Date getExpiration(String token) {
|
||||
Claims claims = Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
|
||||
return claims.getExpiration();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package net.miarma.backend.core.service;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import jakarta.transaction.Transactional;
|
||||
import net.miarma.backend.core.model.User;
|
||||
import net.miarma.backend.core.repository.UserRepository;
|
||||
|
||||
@Service
|
||||
@Transactional
|
||||
public class UserService {
|
||||
private final UserRepository userRepository;
|
||||
|
||||
public UserService(UserRepository userRepository) {
|
||||
this.userRepository = userRepository;
|
||||
}
|
||||
|
||||
public User getById(UUID userId) {
|
||||
return userRepository.findById(userId)
|
||||
.orElseThrow(() -> new RuntimeException("User not found"));
|
||||
}
|
||||
|
||||
public User create(User user) {
|
||||
// TODO: basic validation
|
||||
return userRepository.save(user);
|
||||
}
|
||||
|
||||
public User update(User user) {
|
||||
if(!userRepository.existsById(user.getUserId()))
|
||||
throw new RuntimeException("User not found");
|
||||
return userRepository.save(user);
|
||||
}
|
||||
|
||||
public void delete(UUID userId) {
|
||||
if(!userRepository.existsById(userId))
|
||||
throw new RuntimeException("User not found");
|
||||
userRepository.deleteById(userId);
|
||||
}
|
||||
}
|
||||
12
core/src/main/resources/application.yml
Normal file
12
core/src/main/resources/application.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
spring:
|
||||
datasource:
|
||||
url: jdbc:mariadb://localhost:3306/miarma_v2
|
||||
username: admin
|
||||
password: ositovito
|
||||
jpa:
|
||||
hibernate:
|
||||
ddl-auto: update
|
||||
show-sql: true
|
||||
properties:
|
||||
hibernate:
|
||||
format_sql: true
|
||||
Reference in New Issue
Block a user