diff --git a/pom.xml b/pom.xml
index f4f7a06..7e26128 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,113 +1,126 @@
- 4.0.0
-
- org.springframework.boot
- spring-boot-starter-parent
- 3.5.0
-
-
- com.backend.user.service
- anyame-backend
- 0.0.1-SNAPSHOT
- anyame-backend
- User service for anyame
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 21
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.5.0
+
+
+ com.backend.user.service
+ anyame-backend
+ 0.0.1-SNAPSHOT
+ anyame-backend
+ User service for anyame
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 21
- 0.12.6
- 4.0.0
- 1.6.6
-
-
-
- org.springframework.boot
- spring-boot-starter-data-jpa
-
-
- org.springframework.boot
- spring-boot-starter-security
-
-
- org.springframework.boot
- spring-boot-starter-web
-
-
- org.springframework.boot
- spring-boot-starter-validation
-
-
- org.springframework.security
- spring-security-oauth2-authorization-server
-
+ 0.12.6
+ 4.0.0
+ 1.6.6
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+ org.springframework.boot
+ spring-boot-starter-oauth2-client
+
+
+ org.springframework.security
+ spring-security-oauth2-authorization-server
+
-
- io.jsonwebtoken
- jjwt-api
- ${jjwt.version}
-
-
- io.jsonwebtoken
- jjwt-impl
- ${jjwt.version}
- runtime
-
-
- io.jsonwebtoken
- jjwt-jackson
- ${jjwt.version}
- runtime
-
-
- me.paulschwarz
- spring-dotenv
- ${spring-dotenv.version}
-
-
- org.passay
- passay
- ${passay.version}
-
-
- org.postgresql
- postgresql
- runtime
-
+
+ org.flywaydb
+ flyway-core
+
+
+ org.flywaydb
+ flyway-database-postgresql
+
-
- org.springframework.boot
- spring-boot-starter-test
- test
-
+
+ io.jsonwebtoken
+ jjwt-api
+ ${jjwt.version}
+
+
+ io.jsonwebtoken
+ jjwt-impl
+ ${jjwt.version}
+ runtime
+
+
+ io.jsonwebtoken
+ jjwt-jackson
+ ${jjwt.version}
+ runtime
+
+
+ me.paulschwarz
+ spring-dotenv
+ ${spring-dotenv.version}
+
+
+ org.passay
+ passay
+ ${passay.version}
+
+
+ org.postgresql
+ postgresql
+ runtime
+
-
- org.springframework.security
- spring-security-test
- test
-
-
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
-
-
-
- org.springframework.boot
- spring-boot-maven-plugin
-
-
-
+
+ org.springframework.security
+ spring-security-test
+ test
+
+
-
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/java/com/backend/user/service/anyame/RoleDataInitializer.java b/src/main/java/com/backend/user/service/anyame/RoleDataInitializer.java
new file mode 100644
index 0000000..98f3b33
--- /dev/null
+++ b/src/main/java/com/backend/user/service/anyame/RoleDataInitializer.java
@@ -0,0 +1,101 @@
+package com.backend.user.service.anyame;
+
+import com.backend.user.service.anyame.entity.Permission;
+import com.backend.user.service.anyame.entity.Role;
+import com.backend.user.service.anyame.repository.PermissionRepository;
+import com.backend.user.service.anyame.repository.RoleRepository;
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.stereotype.Component;
+
+import java.util.Arrays;
+
+@Component
+public class RoleDataInitializer implements ApplicationRunner {
+ private final RoleRepository roleRepository;
+ private final PermissionRepository permissionRepository;
+
+ public RoleDataInitializer(RoleRepository roleRepository, PermissionRepository permissionRepository) {
+ this.roleRepository = roleRepository;
+ this.permissionRepository = permissionRepository;
+ }
+
+ @Override
+ public void run(ApplicationArguments args) {
+ if (permissionRepository.count() == 0) {
+ initializePermissions();
+ initializeRoles();
+ }
+ }
+
+ private void initializePermissions() {
+ savePermission("CREATE_ANIME");
+ savePermission("EDIT_ANIME");
+ savePermission("DELETE_ANIME");
+ savePermission("APPROVE_ANIME");
+
+ savePermission("VIEW_USERS");
+ savePermission("EDIT_USER");
+ savePermission("BAN_USER");
+ savePermission("DELETE_USER");
+
+ savePermission("MODERATE_COMMENTS");
+ savePermission("MODERATE_REVIEWS");
+ savePermission("HANDLE_REPORTS");
+ savePermission("BLACKLIST_CONTENT");
+
+ savePermission("MANAGE_ROLES");
+ savePermission("VIEW_ANALYTICS");
+ savePermission("SYSTEM_CONFIG");
+ savePermission("BACKUP_DATA");
+
+ savePermission("VIEW_PREMIUM_CONTENT");
+ savePermission("DOWNLOAD_CONTENT");
+ }
+
+ private void initializeRoles() {
+ Role userRole = saveRole("USER");
+ Role moderatorRole = saveRole("MODERATOR");
+ Role adminRole = saveRole("ADMIN");
+
+ moderatorRole.getParentRoles().add(userRole);
+ moderatorRole.getPermissions().addAll(Arrays.asList(
+ findPermission("MODERATE_COMMENTS"),
+ findPermission("MODERATE_REVIEWS"),
+ findPermission("HANDLE_REPORTS"),
+ findPermission("EDIT_ANIME"),
+ findPermission("APPROVE_ANIME"),
+ findPermission("VIEW_USERS")
+ ));
+
+ adminRole.getParentRoles().add(moderatorRole);
+ adminRole.getPermissions().addAll(Arrays.asList(
+ findPermission("DELETE_ANIME"),
+ findPermission("EDIT_USER"),
+ findPermission("BAN_USER"),
+ findPermission("DELETE_USER"),
+ findPermission("MANAGE_ROLES"),
+ findPermission("VIEW_ANALYTICS"),
+ findPermission("SYSTEM_CONFIG"),
+ findPermission("BACKUP_DATA"),
+ findPermission("BLACKLIST_CONTENT")
+ ));
+
+ roleRepository.saveAll(Arrays.asList(moderatorRole, adminRole));
+ }
+
+ private Permission savePermission(String name) {
+ Permission permission = new Permission(name);
+ return permissionRepository.save(permission);
+ }
+
+ private Role saveRole(String name) {
+ Role role = new Role(name);
+ return roleRepository.save(role);
+ }
+
+ private Permission findPermission(String name) {
+ return permissionRepository.findByName(name)
+ .orElseThrow(() -> new RuntimeException("Permission not found: " + name));
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/backend/user/service/anyame/component/AuthorizedUserMapper.java b/src/main/java/com/backend/user/service/anyame/component/AuthorizedUserMapper.java
new file mode 100644
index 0000000..1068924
--- /dev/null
+++ b/src/main/java/com/backend/user/service/anyame/component/AuthorizedUserMapper.java
@@ -0,0 +1,43 @@
+package com.backend.user.service.anyame.component;
+
+import com.backend.user.service.anyame.entity.User;
+import com.backend.user.service.anyame.model.AuthorizedUser;
+import com.backend.user.service.anyame.service.UserAuthorityService;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.stereotype.Component;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+@Component
+public class AuthorizedUserMapper {
+ private final UserAuthorityService authorityService;
+
+ public AuthorizedUserMapper(UserAuthorityService authorityService) {
+ this.authorityService = authorityService;
+ }
+
+ public AuthorizedUser mapFormLogin(User userEntity) {
+ Set authorities = authorityService.getUserAuthorities(userEntity);
+ return new AuthorizedUser.Builder(userEntity.getEmail(), userEntity.getPassword(), Collections.emptyList())
+ .id(userEntity.getId())
+ .username(userEntity.getName())
+ .active(userEntity.isActive())
+ .authorities(authorities)
+ .accountNonExpired(true)
+ .credentialsNonExpired(true)
+ .accountNonLocked(true)
+ .build();
+ }
+
+ public AuthorizedUser mapOAuth2User(User userEntity, Map attributes) {
+ Set authorities = authorityService.getUserAuthorities(userEntity);
+ return new AuthorizedUser.Builder(userEntity.getEmail(), "", Collections.emptyList())
+ .id(userEntity.getId())
+ .username(userEntity.getName())
+ .active(userEntity.isActive())
+ .authorities(authorities)
+ .build();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/backend/user/service/anyame/config/SecurityConfig.java b/src/main/java/com/backend/user/service/anyame/config/SecurityConfig.java
index 23cbf0d..60549a8 100644
--- a/src/main/java/com/backend/user/service/anyame/config/SecurityConfig.java
+++ b/src/main/java/com/backend/user/service/anyame/config/SecurityConfig.java
@@ -1,22 +1,35 @@
package com.backend.user.service.anyame.config;
+import com.backend.user.service.anyame.service.CustomOAuth2UserService;
+import com.backend.user.service.anyame.service.CustomUserDetailsService;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
+import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
-import org.springframework.security.core.userdetails.User;
-import org.springframework.security.core.userdetails.UserDetails;
-import org.springframework.security.core.userdetails.UserDetailsService;
-import org.springframework.security.provisioning.InMemoryUserDetailsManager;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.cors.CorsConfigurationSource;
import static org.springframework.security.config.Customizer.withDefaults;
@Configuration(proxyBeanMethods = false)
+@EnableWebSecurity
+@EnableMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
+ private final CustomUserDetailsService userDetailsService;
+ private final CustomOAuth2UserService oauth2UserService;
+
+ public SecurityConfig(CustomUserDetailsService userDetailsService,
+ CustomOAuth2UserService oauth2UserService) {
+ this.userDetailsService = userDetailsService;
+ this.oauth2UserService = oauth2UserService;
+ }
+
@Bean
@Order(2)
public SecurityFilterChain filterChain(HttpSecurity http, @Qualifier("corsConfigurationSource") CorsConfigurationSource configurationSource) throws Exception {
@@ -26,17 +39,17 @@ public class SecurityConfig {
c.anyRequest().authenticated()
)
.formLogin(withDefaults())
+ .oauth2Login(c ->
+ c.userInfoEndpoint(userInfo -> userInfo
+ .userService(oauth2UserService)
+ )
+ )
+ .userDetailsService(userDetailsService)
.build();
}
@Bean
- public UserDetailsService users() {
- UserDetails user = User.builder()
- .username("admin")
- .password("{noop}password")
- .roles("USER")
- .build();
- return new InMemoryUserDetailsManager(user);
+ public PasswordEncoder passwordEncoder() {
+ return new BCryptPasswordEncoder();
}
-
}
\ No newline at end of file
diff --git a/src/main/java/com/backend/user/service/anyame/configurer/SocialConfigurer.java b/src/main/java/com/backend/user/service/anyame/configurer/SocialConfigurer.java
new file mode 100644
index 0000000..0e21c08
--- /dev/null
+++ b/src/main/java/com/backend/user/service/anyame/configurer/SocialConfigurer.java
@@ -0,0 +1,25 @@
+package com.backend.user.service.anyame.configurer;
+
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
+import org.springframework.security.web.authentication.AuthenticationFailureHandler;
+import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
+import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
+
+public class SocialConfigurer extends AbstractHttpConfigurer {
+ private AuthenticationFailureHandler failureHandler;
+ private AuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
+
+
+ @Override
+ public void init(HttpSecurity http) throws Exception {
+ http.oauth2Login(c -> {
+ if (this.successHandler != null) {
+ c.successHandler(this.successHandler);
+ }
+ if (this.failureHandler != null) {
+ c.failureHandler(this.failureHandler);
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/backend/user/service/anyame/controller/HelloController.java b/src/main/java/com/backend/user/service/anyame/controller/HelloController.java
index 8b5ffaa..1943684 100644
--- a/src/main/java/com/backend/user/service/anyame/controller/HelloController.java
+++ b/src/main/java/com/backend/user/service/anyame/controller/HelloController.java
@@ -1,5 +1,7 @@
package com.backend.user.service.anyame.controller;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@@ -11,4 +13,11 @@ public class HelloController {
public String hello() {
return "Hello World";
}
-}
+
+ @GetMapping("/principalhello")
+ public String principalhello() {
+ Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+ return authentication.getName();
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/backend/user/service/anyame/entity/Permission.java b/src/main/java/com/backend/user/service/anyame/entity/Permission.java
new file mode 100644
index 0000000..e5d56e1
--- /dev/null
+++ b/src/main/java/com/backend/user/service/anyame/entity/Permission.java
@@ -0,0 +1,28 @@
+package com.backend.user.service.anyame.entity;
+
+import jakarta.persistence.*;
+
+@Entity
+@Table(name = "permissions")
+public class Permission {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+ @Column(unique = true, nullable = false)
+ private String name;
+
+ protected Permission() {
+ }
+
+ public Permission(String name) {
+ this.name = name;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/backend/user/service/anyame/entity/Role.java b/src/main/java/com/backend/user/service/anyame/entity/Role.java
new file mode 100644
index 0000000..d9c5b55
--- /dev/null
+++ b/src/main/java/com/backend/user/service/anyame/entity/Role.java
@@ -0,0 +1,64 @@
+package com.backend.user.service.anyame.entity;
+
+import jakarta.persistence.*;
+
+import java.util.HashSet;
+import java.util.Set;
+
+@Entity
+@Table(name = "roles")
+public class Role {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+ @Column(unique = true, nullable = false)
+ private String name;
+
+ @ManyToMany(fetch = FetchType.LAZY)
+ @JoinTable(
+ name = "role_hierarchy",
+ joinColumns = @JoinColumn(name = "child_role_id"),
+ inverseJoinColumns = @JoinColumn(name = "parent_role_id")
+ )
+ private Set parentRoles = new HashSet<>();
+
+ @ManyToMany(fetch = FetchType.EAGER)
+ @JoinTable(name = "role_permissions",
+ joinColumns = @JoinColumn(name = "role_id"),
+ inverseJoinColumns = @JoinColumn(name = "permission_id")
+ )
+ private Set permissions = new HashSet<>();
+
+ protected Role() {
+ }
+
+ public Role(String name) {
+ this.name = name;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Set getParentRoles() {
+ return parentRoles;
+ }
+
+ public Set getPermissions() {
+ return permissions;
+ }
+
+ public Set getAllPermissions() {
+ Set allPermissions = new HashSet<>(permissions);
+
+ for (Role parentRole : parentRoles) {
+ allPermissions.addAll(parentRole.getAllPermissions());
+ }
+
+ return allPermissions;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/backend/user/service/anyame/entity/User.java b/src/main/java/com/backend/user/service/anyame/entity/User.java
new file mode 100644
index 0000000..9843379
--- /dev/null
+++ b/src/main/java/com/backend/user/service/anyame/entity/User.java
@@ -0,0 +1,88 @@
+package com.backend.user.service.anyame.entity;
+
+import jakarta.persistence.*;
+
+import java.util.HashSet;
+import java.util.Set;
+
+@Entity
+@Table(name = "users")
+public class User {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+ @Column(unique = true, nullable = false)
+ private String name;
+ @Column(unique = true)
+ private String email;
+ private String password;
+ private boolean active;
+
+ @ManyToMany(fetch = FetchType.EAGER)
+ @JoinTable(
+ name = "user_roles",
+ joinColumns = @JoinColumn(name = "user_id"),
+ inverseJoinColumns = @JoinColumn(name = "role_id")
+ )
+ private Set roles = new HashSet<>();
+
+ private String provider;
+ private String providerId;
+
+ protected User() {
+ }
+
+ public User(String name, String email, String provider, String providerId) {
+ this.name = name;
+ this.email = email;
+ this.provider = provider;
+ this.providerId = providerId;
+ }
+
+ public Set getRoles() {
+ return roles;
+ }
+
+ public Set getAllPermissions() {
+ Set allPermissions = new HashSet<>();
+
+ for (Role role : roles) {
+ allPermissions.addAll(role.getAllPermissions());
+ }
+
+ return allPermissions;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public boolean isActive() {
+ return active;
+ }
+
+ public void setActive(boolean active) {
+ this.active = active;
+ }
+
+ public String getProvider() {
+ return provider;
+ }
+
+ public String getProviderId() {
+ return providerId;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/backend/user/service/anyame/model/AuthorizedUser.java b/src/main/java/com/backend/user/service/anyame/model/AuthorizedUser.java
new file mode 100644
index 0000000..1ef8547
--- /dev/null
+++ b/src/main/java/com/backend/user/service/anyame/model/AuthorizedUser.java
@@ -0,0 +1,136 @@
+package com.backend.user.service.anyame.model;
+
+import com.backend.user.service.anyame.entity.Role;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.oauth2.core.user.OAuth2User;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+public class AuthorizedUser extends User implements OAuth2User {
+ private Long id;
+ private String email;
+ private boolean active;
+ private Set roles;
+ private Map attributes;
+
+ public Long getId() {
+ return id;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public boolean isActive() {
+ return active;
+ }
+
+ private AuthorizedUser(Builder builder) {
+ super(
+ builder.username,
+ builder.password,
+ builder.enabled,
+ builder.accountNonExpired,
+ builder.credentialsNonExpired,
+ builder.accountNonLocked,
+ builder.authorities
+ );
+ this.id = builder.id;
+ this.email = builder.email;
+ this.active = builder.active;
+ }
+
+ @Override
+ public Map getAttributes() {
+ return attributes;
+ }
+
+ @Override
+ public String getName() {
+ return getUsername();
+ }
+
+ public static class Builder {
+ private String email;
+ private String password;
+ private Collection extends GrantedAuthority> authorities;
+ private Long id;
+ private String username;
+ private boolean active;
+ private boolean enabled = true;
+ private boolean accountNonExpired = true;
+ private boolean credentialsNonExpired = true;
+ private boolean accountNonLocked = true;
+
+ public Builder(String email, String password, Collection extends GrantedAuthority> authorities) {
+ this.email = email;
+ this.password = password;
+ this.authorities = authorities;
+ }
+
+ public Builder id(Long id) {
+ this.id = id;
+ return this;
+ }
+
+ public Builder username(String username) {
+ this.username = username;
+ return this;
+ }
+
+ public Builder password(String password) {
+ this.password = password;
+ return this;
+ }
+
+ public Builder email(String email) {
+ this.email = email;
+ return this;
+ }
+
+ public Builder active(boolean active) {
+ this.active = active;
+ return this;
+ }
+
+ public Builder enabled(boolean enabled) {
+ this.enabled = enabled;
+ return this;
+ }
+
+ public Builder accountNonExpired(boolean accountNonExpired) {
+ this.accountNonExpired = accountNonExpired;
+ return this;
+ }
+
+ public Builder credentialsNonExpired(boolean credentialsNonExpired) {
+ this.credentialsNonExpired = credentialsNonExpired;
+ return this;
+ }
+
+ public Builder accountNonLocked(boolean accountNonLocked) {
+ this.accountNonLocked = accountNonLocked;
+ return this;
+ }
+
+ public Builder authorities(Collection extends GrantedAuthority> authorities) {
+ this.authorities = authorities;
+ return this;
+ }
+
+ public AuthorizedUser build() {
+ return new AuthorizedUser(this);
+ }
+ }
+
+ public AuthorizedUser(String username, String password, Collection extends GrantedAuthority> authorities) {
+ super(username, password, authorities);
+ }
+
+ public AuthorizedUser(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection extends GrantedAuthority> authorities) {
+ super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/backend/user/service/anyame/repository/PermissionRepository.java b/src/main/java/com/backend/user/service/anyame/repository/PermissionRepository.java
new file mode 100644
index 0000000..630df1f
--- /dev/null
+++ b/src/main/java/com/backend/user/service/anyame/repository/PermissionRepository.java
@@ -0,0 +1,21 @@
+package com.backend.user.service.anyame.repository;
+
+import com.backend.user.service.anyame.entity.Permission;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import org.springframework.stereotype.Repository;
+
+import java.util.Optional;
+import java.util.Set;
+
+@Repository
+public interface PermissionRepository extends JpaRepository {
+ Optional findByName(String name);
+
+ @Query("SELECT DISTINCT p FROM User u " +
+ "JOIN u.roles r " +
+ "JOIN r.permissions p " +
+ "WHERE u.name = :name")
+ Set findByUserName(@Param("name") String name);
+}
\ No newline at end of file
diff --git a/src/main/java/com/backend/user/service/anyame/repository/RoleRepository.java b/src/main/java/com/backend/user/service/anyame/repository/RoleRepository.java
new file mode 100644
index 0000000..9e1dcf6
--- /dev/null
+++ b/src/main/java/com/backend/user/service/anyame/repository/RoleRepository.java
@@ -0,0 +1,22 @@
+package com.backend.user.service.anyame.repository;
+
+import com.backend.user.service.anyame.entity.Role;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+@Repository
+public interface RoleRepository extends JpaRepository {
+ Optional findByName(String name);
+
+ @Query("SELECT r FROM Role r " +
+ "LEFT JOIN FETCH r.permissions " +
+ "LEFT JOIN FETCH r.parentRoles " +
+ "WHERE r.id IN :roleIds")
+ List findRolesWithPermissionsAndParents(@Param("roleIds") Set roleIds);
+}
\ No newline at end of file
diff --git a/src/main/java/com/backend/user/service/anyame/repository/UserRepository.java b/src/main/java/com/backend/user/service/anyame/repository/UserRepository.java
new file mode 100644
index 0000000..7c42e95
--- /dev/null
+++ b/src/main/java/com/backend/user/service/anyame/repository/UserRepository.java
@@ -0,0 +1,21 @@
+package com.backend.user.service.anyame.repository;
+
+import com.backend.user.service.anyame.entity.User;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import org.springframework.stereotype.Repository;
+
+import java.util.Optional;
+
+@Repository
+public interface UserRepository extends JpaRepository {
+ Optional findByEmail(String email);
+
+ Optional findByEmailOrName(String email, String name);
+
+ @Query("SELECT u FROM User u " +
+ "LEFT JOIN FETCH u.roles " +
+ "WHERE u.name = :name")
+ Optional findByNameWithRoles(@Param("name") String name);
+}
\ No newline at end of file
diff --git a/src/main/java/com/backend/user/service/anyame/service/CustomOAuth2UserService.java b/src/main/java/com/backend/user/service/anyame/service/CustomOAuth2UserService.java
new file mode 100644
index 0000000..8252ddb
--- /dev/null
+++ b/src/main/java/com/backend/user/service/anyame/service/CustomOAuth2UserService.java
@@ -0,0 +1,77 @@
+package com.backend.user.service.anyame.service;
+
+import com.backend.user.service.anyame.component.AuthorizedUserMapper;
+import com.backend.user.service.anyame.entity.Role;
+import com.backend.user.service.anyame.entity.User;
+import com.backend.user.service.anyame.repository.RoleRepository;
+import com.backend.user.service.anyame.repository.UserRepository;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
+import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
+import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
+import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
+import org.springframework.security.oauth2.core.user.OAuth2User;
+import org.springframework.stereotype.Service;
+
+@Service
+public class CustomOAuth2UserService implements OAuth2UserService {
+ private static final Logger log = LoggerFactory.getLogger(CustomOAuth2UserService.class);
+ private final UserRepository userRepository;
+ private final RoleRepository roleRepository;
+ private final DefaultOAuth2UserService delegate = new DefaultOAuth2UserService();
+ private final AuthorizedUserMapper authorizedUserMapper;
+
+ public CustomOAuth2UserService(UserRepository userRepository, RoleRepository roleRepository, AuthorizedUserMapper authorizedUserMapper) {
+ this.userRepository = userRepository;
+ this.roleRepository = roleRepository;
+ this.authorizedUserMapper = authorizedUserMapper;
+ }
+
+ @Override
+ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
+ OAuth2User oauth2User = delegate.loadUser(userRequest);
+
+ return processOAuth2User(userRequest, oauth2User);
+ }
+
+ private OAuth2User processOAuth2User(OAuth2UserRequest userRequest, OAuth2User oauth2User) {
+ if (!oauth2User.getAttributes().containsKey("id")) {
+ throw new OAuth2AuthenticationException("oauth2User id is not provided");
+ }
+ if (!oauth2User.getAttributes().containsKey("name")) {
+ throw new OAuth2AuthenticationException("oauth2User name is not provided");
+ }
+ log.info("NAME IS: {}, {}, {}", oauth2User.getName(), oauth2User.getAttribute("login"), oauth2User.getAttribute("name"));
+ log.info("NAME IS: {}, {}, {}", oauth2User.getName(), oauth2User.getAttribute("login"), oauth2User.getAttribute("name"));
+ String registrationId = userRequest.getClientRegistration().getRegistrationId();
+ String email = oauth2User.getAttribute("email");
+ String name = oauth2User.getAttribute("name");
+ String providerId = oauth2User.getAttribute("id").toString();
+
+ User user = userRepository.findByEmail(email)
+ .orElseGet(() -> createNewUser(email, name, registrationId, providerId));
+
+ // TODO: Should be toggleable nor documented behaviour
+ // First user becomes admin
+ if (userRepository.count() == 1) {
+ Role adminRole = roleRepository.findByName("ADMIN")
+ .orElseThrow(() -> new RuntimeException("Admin role not found"));
+ user.getRoles().add(adminRole);
+ userRepository.save(user);
+ }
+
+ return authorizedUserMapper.mapOAuth2User(user, oauth2User.getAttributes());
+ }
+
+ private User createNewUser(String email, String name, String provider, String providerId) {
+ User user = new User(name, email, provider, providerId);
+ user.setActive(true);
+
+ Role userRole = roleRepository.findByName("USER")
+ .orElseThrow(() -> new RuntimeException("User role not found"));
+ user.getRoles().add(userRole);
+
+ return userRepository.save(user);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/backend/user/service/anyame/service/CustomUserDetailsService.java b/src/main/java/com/backend/user/service/anyame/service/CustomUserDetailsService.java
new file mode 100644
index 0000000..cecb620
--- /dev/null
+++ b/src/main/java/com/backend/user/service/anyame/service/CustomUserDetailsService.java
@@ -0,0 +1,31 @@
+package com.backend.user.service.anyame.service;
+
+import com.backend.user.service.anyame.component.AuthorizedUserMapper;
+import com.backend.user.service.anyame.entity.User;
+import com.backend.user.service.anyame.repository.UserRepository;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.stereotype.Service;
+
+import java.util.Optional;
+
+@Service
+public class CustomUserDetailsService implements UserDetailsService {
+ private final UserRepository userRepository;
+ private final AuthorizedUserMapper authorizedUserMapper;
+
+ public CustomUserDetailsService(UserRepository userRepository, AuthorizedUserMapper authorizedUserMapper) {
+ this.userRepository = userRepository;
+ this.authorizedUserMapper = authorizedUserMapper;
+ }
+
+ @Override
+ public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
+ Optional userOptional = userRepository.findByEmailOrName(username, username);
+ if (userOptional.isEmpty()) {
+ throw new UsernameNotFoundException("user not found " + username);
+ }
+ return authorizedUserMapper.mapFormLogin(userOptional.get());
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/backend/user/service/anyame/service/UserAuthorityService.java b/src/main/java/com/backend/user/service/anyame/service/UserAuthorityService.java
new file mode 100644
index 0000000..889ebb0
--- /dev/null
+++ b/src/main/java/com/backend/user/service/anyame/service/UserAuthorityService.java
@@ -0,0 +1,39 @@
+package com.backend.user.service.anyame.service;
+
+import com.backend.user.service.anyame.entity.User;
+import com.backend.user.service.anyame.repository.PermissionRepository;
+import com.backend.user.service.anyame.repository.UserRepository;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.HashSet;
+import java.util.Set;
+
+@Service
+@Transactional(readOnly = true)
+public class UserAuthorityService {
+
+ private final UserRepository userRepository;
+ private final PermissionRepository permissionRepository;
+
+ public UserAuthorityService(UserRepository userRepository, PermissionRepository permissionRepository) {
+ this.userRepository = userRepository;
+ this.permissionRepository = permissionRepository;
+ }
+
+ public Set getUserAuthorities(User user) {
+ Set authorities = new HashSet<>();
+
+ user.getRoles().forEach(role ->
+ authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getName()))
+ );
+
+ permissionRepository.findByUserName(user.getName()).forEach(permission ->
+ authorities.add(new SimpleGrantedAuthority(permission.getName()))
+ );
+
+ return authorities;
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml
index b39d7ef..a49f315 100644
--- a/src/main/resources/application.yaml
+++ b/src/main/resources/application.yaml
@@ -6,10 +6,27 @@ spring:
authorizationserver:
issuer-url: http://localhost:8080
introspection-endpoint: /oauth2/token-info
+ client:
+ registration:
+ github:
+ clientId: ${GITHUB_CLIENT_ID}
+ clientSecret: ${GITHUB_CLIENT_SECRET}
+ scope:
+ - user:email
+ - read:user
+
+ google:
+ clientId: ${GOOGLE_CLIENT_ID}
+ clientSecret: ${GOOGLE_CLIENT_SECRET}
datasource:
url: ${DATABASE_URL}
username: ${DATABASE_USERNAME}
password: ${DATABASE_PASSWORD}
+ flyway:
+ enabled: true
+ locations: classpath:db/migration/structure, classpath:db/migration/data
+ validate-on-migrate: true
+ default-schema: main
jpa:
hibernate:
ddl-auto: create