Implement discord login

This commit is contained in:
SebClem 2022-05-19 01:08:47 +02:00
parent 1f34d041b7
commit 5248069a78
Signed by: sebclem
GPG Key ID: 5A4308F6A359EA50
24 changed files with 337 additions and 566 deletions

View File

@ -38,6 +38,10 @@ dependencies {
implementation("org.springframework.boot:spring-boot-starter-log4j2") implementation("org.springframework.boot:spring-boot-starter-log4j2")
implementation("org.springframework.boot:spring-boot-starter-oauth2-client") implementation("org.springframework.boot:spring-boot-starter-oauth2-client")
implementation('io.jsonwebtoken:jjwt-api:0.11.5')
implementation('io.jsonwebtoken:jjwt-impl:0.11.5')
implementation('io.jsonwebtoken:jjwt-gson:0.11.5')
implementation 'org.codehaus.groovy:groovy-all:3.0.8' implementation 'org.codehaus.groovy:groovy-all:3.0.8'
implementation 'com.sedmelluq:lavaplayer:1.3.77' implementation 'com.sedmelluq:lavaplayer:1.3.77'

View File

@ -1,25 +0,0 @@
package net.Broken.Api;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/v2")
public class AuthController {
@GetMapping("user")
public String helloUser() {
return "Hello User";
}
@GetMapping("admin")
public String helloAdmin() {
return "Hello Admin";
}
}

View File

@ -0,0 +1,28 @@
package net.Broken.Api.Controllers;
import net.Broken.Api.Data.Login;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/v2")
public class AuthController {
private final AuthenticationManager authenticationManager;
public AuthController(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
@PostMapping("login/discord")
public String helloUser(@RequestBody Login login) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(login.redirectUri(), login.code())
);
return "Hello User";
}
}

View File

@ -0,0 +1,5 @@
package net.Broken.Api.Data;
public record Login (String code, String redirectUri){
}

View File

@ -0,0 +1,9 @@
package net.Broken.Api.Security.Data;
public class AccessTokenResponse {
public String access_token;
public String token_type;
public String expires_in;
public String refresh_token;
public String scope;
}

View File

@ -0,0 +1,6 @@
package net.Broken.Api.Security.Data;
public class DiscordOauthUserInfo {
public String id;
public String username;
}

View File

@ -1,18 +1,47 @@
package net.Broken.Api.Security; package net.Broken.Api.Security;
import net.Broken.Api.Security.Data.DiscordOauthUserInfo;
import net.Broken.Api.Security.Exception.OAuthLoginFail;
import net.Broken.Api.Security.Services.DiscordOauthService;
import net.Broken.DB.Entity.UserEntity;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
@Component
public class DiscordAuthenticationProvider implements AuthenticationProvider { public class DiscordAuthenticationProvider implements AuthenticationProvider {
private final Logger logger = LogManager.getLogger();
private final DiscordOauthService discordOauthService;
public DiscordAuthenticationProvider(DiscordOauthService discordOauthService) {
this.discordOauthService = discordOauthService;
}
@Override @Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException { public Authentication authenticate(Authentication authentication) throws AuthenticationException {
authentication.getCredentials() String redirectUri = authentication.getPrincipal().toString();
return null; String code = authentication.getCredentials().toString();
try {
String token = discordOauthService.getAccessToken(code, redirectUri);
DiscordOauthUserInfo discordOauthUserInfo = discordOauthService.getUserInfo(token);
discordOauthService.revokeToken(token);
UserEntity userEntity = discordOauthService.loginOrRegisterDiscordUser(discordOauthUserInfo);
return new UsernamePasswordAuthenticationToken(userEntity, null, new ArrayList<>());
} catch (OAuthLoginFail e) {
throw new BadCredentialsException("Bad response form Discord Oauth server ! Code expired ?");
}
} }
@Override @Override
public boolean supports(Class<?> authentication) { public boolean supports(Class<?> authentication) {
return false; return authentication.equals(UsernamePasswordAuthenticationToken.class);
} }
} }

View File

@ -0,0 +1,4 @@
package net.Broken.Api.Security.Exception;
public class OAuthLoginFail extends Exception{
}

View File

@ -1,16 +0,0 @@
package net.Broken.Api.Security;
import org.springframework.http.HttpHeaders;
import org.springframework.http.RequestEntity;
class OAuth2UserAgentUtils {
static RequestEntity<?> withUserAgent(RequestEntity<?> request) {
HttpHeaders headers = new HttpHeaders();
headers.putAll(request.getHeaders());
headers.add(HttpHeaders.USER_AGENT, DISCORD_BOT_USER_AGENT);
return new RequestEntity<>(request.getBody(), headers, request.getMethod(), request.getUrl());
}
private static final String DISCORD_BOT_USER_AGENT = "DiscordBot (https://github.com/fourscouts/blog/tree/master/oauth2-discord)";
}

View File

@ -1,31 +1,42 @@
package net.Broken.Api.Security; package net.Broken.Api.Security;
import net.Broken.Api.Security.Services.UnauthorizedHandler;
import net.Broken.DB.Repository.UserRepository; import net.Broken.DB.Repository.UserRepository;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; 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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
@EnableWebSecurity @EnableWebSecurity
@Configuration @Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter { public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final UnauthorizedHandler unauthorizedHandler;
private final UserRepository userRepository; public SecurityConfig(UnauthorizedHandler unauthorizedHandler) {
this.unauthorizedHandler = unauthorizedHandler;
public SecurityConfig(UserRepository userRepository) {
this.userRepository = userRepository;
} }
@Override @Override
protected void configure(HttpSecurity http) throws Exception { protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
http.authorizeRequests() .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
// Our private endpoints // Our private endpoints
.anyRequest().authenticated(); .antMatchers("/api/v2/**").permitAll()
.anyRequest().permitAll();
// http.authenticationProvider(discordAuthenticationProvider);
// http.exceptionHandling().authenticationEntryPoint((request, response, authException) -> { // http.exceptionHandling().authenticationEntryPoint((request, response, authException) -> {
// response.sendError(HttpServletResponse.SC_UNAUTHORIZED); // response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
// }); // });
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
} }
} }

View File

@ -0,0 +1,140 @@
package net.Broken.Api.Security.Services;
import com.google.gson.Gson;
import net.Broken.Api.Security.Data.AccessTokenResponse;
import net.Broken.Api.Security.Data.DiscordOauthUserInfo;
import net.Broken.Api.Security.Exception.OAuthLoginFail;
import net.Broken.DB.Entity.UserEntity;
import net.Broken.DB.Repository.UserRepository;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.HashMap;
import java.util.Map;
@Service
public class DiscordOauthService {
private final Logger logger = LogManager.getLogger();
@Value("${discord.client-id}")
private String clientId;
@Value("${discord.client-secret}")
private String clientSecret;
@Value("${discord.token-endpoint}")
private String tokenEndpoint;
@Value("${discord.tokenRevokeEndpoint}")
private String tokenRevokeEndpoint;
@Value("${discord.userInfoEnpoint}")
private String userInfoEnpoint;
private final UserRepository userRepository;
public DiscordOauthService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public String getAccessToken(String code, String redirectUrl) throws OAuthLoginFail {
logger.debug("[OAUTH] Getting access token");
HashMap<String, String> data = new HashMap<>();
data.put("client_id", this.clientId);
data.put("client_secret", this.clientSecret);
data.put("grant_type", "authorization_code");
data.put("code", code);
data.put("redirect_uri", redirectUrl);
try {
HttpResponse<String> response = makeFormPost(this.tokenEndpoint, data);
if (response.statusCode() != 200) {
logger.warn("[OAUTH] Invalid response while getting AccessToken: Status Code: " + response.statusCode() + " Body:" + response.body());
throw new OAuthLoginFail();
}
Gson gson = new Gson();
AccessTokenResponse accessTokenResponse = gson.fromJson(response.body(), AccessTokenResponse.class);
return accessTokenResponse.access_token;
} catch (IOException | InterruptedException e) {
logger.catching(e);
throw new OAuthLoginFail();
}
}
public DiscordOauthUserInfo getUserInfo(String token) throws OAuthLoginFail {
logger.debug("[OAUTH] Getting user info");
try {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(this.userInfoEnpoint))
.header("Authorization", "Bearer " + token)
.GET()
.build();
HttpClient client = HttpClient.newHttpClient();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
logger.warn("[OAUTH] Invalid response while getting UserInfo: Status Code: " + response.statusCode() + " Body:" + response.body());
throw new OAuthLoginFail();
}
Gson gson = new Gson();
return gson.fromJson(response.body(), DiscordOauthUserInfo.class);
} catch (IOException | InterruptedException e) {
logger.catching(e);
throw new OAuthLoginFail();
}
}
public void revokeToken(String token) {
logger.debug("[OAUTH] Revoking access token");
HashMap<String, String> data = new HashMap<>();
data.put("token", token);
try {
HttpResponse<String> response = makeFormPost(this.tokenRevokeEndpoint, data);
if (response.statusCode() != 200) {
logger.warn("OAUTH] Invalid response while token revocation: Status Code: " + response.statusCode() + " Body:" + response.body());
}
} catch (IOException | InterruptedException e) {
logger.catching(e);
}
}
public UserEntity loginOrRegisterDiscordUser(DiscordOauthUserInfo discordOauthUserInfo) {
return userRepository
.findByJdaId(discordOauthUserInfo.id)
.orElseGet(() -> userRepository.save(new UserEntity(discordOauthUserInfo.username, discordOauthUserInfo.id)));
}
private String getFormString(HashMap<String, String> params) throws UnsupportedEncodingException {
StringBuilder result = new StringBuilder();
boolean first = true;
for (Map.Entry<String, String> entry : params.entrySet()) {
if (first)
first = false;
else
result.append("&");
result.append(URLEncoder.encode(entry.getKey(), "UTF-8"));
result.append("=");
result.append(URLEncoder.encode(entry.getValue(), "UTF-8"));
}
return result.toString();
}
private HttpResponse<String> makeFormPost(String endpoint, HashMap<String, String> data) throws IOException, InterruptedException {
HttpRequest.BodyPublisher body = HttpRequest.BodyPublishers.ofString(getFormString(data));
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(tokenEndpoint))
.header("Content-Type", "application/x-www-form-urlencoded")
.POST(body)
.build();
HttpClient client = HttpClient.newHttpClient();
return client.send(request, HttpResponse.BodyHandlers.ofString());
}
}

View File

@ -1,14 +1,12 @@
package net.Broken.Api.Security; package net.Broken.Api.Security.Services;
import net.Broken.DB.Entity.UserEntity; import net.Broken.Api.Security.DiscordUserPrincipal;
import net.Broken.DB.Repository.UserRepository; import net.Broken.DB.Repository.UserRepository;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.List;
@Service @Service
public class DiscordUserDetailsService implements UserDetailsService { public class DiscordUserDetailsService implements UserDetailsService {
private final UserRepository userRepository; private final UserRepository userRepository;
@ -20,10 +18,9 @@ public class DiscordUserDetailsService implements UserDetailsService {
@Override @Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
List<UserEntity> user = userRepository.findByName(username); return new DiscordUserPrincipal(
if(user.isEmpty()){ userRepository.findByJdaId(username)
throw new UsernameNotFoundException(username); .orElseThrow(() -> new UsernameNotFoundException(username))
} );
return new DiscordUserPrincipal(user.get(0));
} }
} }

View File

@ -0,0 +1,38 @@
package net.Broken.Api.Security.Services;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.http.MediaType;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@Component
public class UnauthorizedHandler implements AuthenticationEntryPoint {
private final Logger logger = LogManager.getLogger();
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
logger.error("[API] Unauthorized error: {}", authException.getMessage());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
final Map<String, Object> body = new HashMap<>();
body.put("status", HttpServletResponse.SC_UNAUTHORIZED);
body.put("error", "Unauthorized");
body.put("message", authException.getMessage());
body.put("path", request.getServletPath());
final ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(response.getOutputStream(), body);
}
}

View File

@ -1,46 +0,0 @@
package net.Broken.Api.Services;
import com.google.gson.Gson;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.HashMap;
@Service
public class DiscordOauthService {
@Value("${discord.client-id}")
private String clientId;
@Value("${discord.client-secret}")
private String clientSecret;
@Value("${discord.token-endpoint}")
private String tokenEndpoint;
public String getAccessToken(String code, String redirectUrl){
HashMap<String, String> data = new HashMap<>();
data.put("client_id", this.clientId);
data.put("client_secret", this.clientSecret);
data.put("grant_type", "authorization_code");
data.put("code", code);
data.put("redirect_uri", redirectUrl);
Gson gson = new Gson();
HttpRequest.BodyPublisher body = HttpRequest.BodyPublishers.ofString(gson.toJson(data));
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(tokenEndpoint))
.header("Content-Type", "application/json")
.POST(body)
.build();
HttpClient client = HttpClient.newHttpClient();
client.send(request, HttpResponse.BodyHandlers.ofString());
}
}

View File

@ -1,12 +0,0 @@
package net.Broken.Api.Services;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
public class DiscordUserDetailService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return null;
}
}

View File

@ -21,20 +21,15 @@ public class UserEntity {
private String name; private String name;
@Column(unique=true)
private String jdaId; private String jdaId;
private String apiToken;
private boolean isBotAdmin = false; private boolean isBotAdmin = false;
@JsonIgnore @JsonIgnore
@OneToMany(fetch = FetchType.EAGER, mappedBy = "user") @OneToMany(fetch = FetchType.EAGER, mappedBy = "user")
private List<UserStats> userStats; private List<UserStats> userStats;
@JsonIgnore
private String password;
@OneToMany(mappedBy = "user") @OneToMany(mappedBy = "user")
private List<PlaylistEntity> playlists; private List<PlaylistEntity> playlists;
@ -42,36 +37,17 @@ public class UserEntity {
public UserEntity() { public UserEntity() {
} }
public UserEntity(PendingUserEntity pendingUserEntity, String apiToken) { public UserEntity(User user) {
this.name = pendingUserEntity.getName();
this.jdaId = pendingUserEntity.getJdaId();
this.password = pendingUserEntity.getPassword();
this.apiToken = apiToken;
}
public UserEntity(User user, PasswordEncoder passwordEncoder) {
this.name = user.getName(); this.name = user.getName();
this.jdaId = user.getId(); this.jdaId = user.getId();
this.apiToken = UserUtils.getInstance().generateApiToken();
this.password = passwordEncoder.encode(UserUtils.getInstance().generateCheckToken());
} }
public UserEntity(String name, String id, PasswordEncoder passwordEncoder) { public UserEntity(String name, String id) {
this.name = name; this.name = name;
this.jdaId = id; this.jdaId = id;
this.apiToken = UserUtils.getInstance().generateApiToken();
this.password = passwordEncoder.encode(UserUtils.getInstance().generateCheckToken());
} }
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Integer getId() { public Integer getId() {
return id; return id;
} }
@ -96,14 +72,6 @@ public class UserEntity {
this.jdaId = jdaId; this.jdaId = jdaId;
} }
public String getApiToken() {
return apiToken;
}
public void setApiToken(String apiToken) {
this.apiToken = apiToken;
}
public List<PlaylistEntity> getPlaylists() { public List<PlaylistEntity> getPlaylists() {
return playlists; return playlists;
} }

View File

@ -4,6 +4,7 @@ import net.Broken.DB.Entity.UserEntity;
import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.CrudRepository;
import java.util.List; import java.util.List;
import java.util.Optional;
/** /**
* Repository for UserEntity * Repository for UserEntity
@ -12,7 +13,5 @@ import java.util.List;
public interface UserRepository extends CrudRepository<UserEntity, Integer> { public interface UserRepository extends CrudRepository<UserEntity, Integer> {
List<UserEntity> findByName(String name); List<UserEntity> findByName(String name);
List<UserEntity> findByJdaId(String jdaId); Optional<UserEntity> findByJdaId(String jdaId);
List<UserEntity> findByApiToken(String apiToken);
} }

View File

@ -43,33 +43,33 @@ public class PlaylistAPIController {
} }
@RequestMapping("/myPlaylist") // @RequestMapping("/myPlaylist")
public List<PlaylistEntity> myPlaylist(@CookieValue(value = "token", defaultValue = "") String token) { // public List<PlaylistEntity> myPlaylist(@CookieValue(value = "token", defaultValue = "") String token) {
if (token.isEmpty()) // if (token.isEmpty())
return null; // return null;
else { // else {
UserEntity user = userRepository.findByApiToken(token).get(0); // UserEntity user = userRepository.findByApiToken(token).get(0);
return user.getPlaylists(); // return user.getPlaylists();
} // }
//
// }
} // @RequestMapping("/createPlaylist")
// public ResponseEntity<PlaylistResponseData> createPlaylist(@CookieValue(value = "token", defaultValue = "") String token, @RequestBody CreatePlaylistData data) {
@RequestMapping("/createPlaylist") //
public ResponseEntity<PlaylistResponseData> createPlaylist(@CookieValue(value = "token", defaultValue = "") String token, @RequestBody CreatePlaylistData data) { // if (token.isEmpty())
// return new ResponseEntity<>(new PlaylistResponseData("Unknown Token!\nPlease Re-connect.", "token"), HttpStatus.UNAUTHORIZED);
if (token.isEmpty()) // else {
return new ResponseEntity<>(new PlaylistResponseData("Unknown Token!\nPlease Re-connect.", "token"), HttpStatus.UNAUTHORIZED); // UserEntity user = userRepository.findByApiToken(token).get(0);
else { // PlaylistEntity playlistEntity = new PlaylistEntity(data.name, user);
UserEntity user = userRepository.findByApiToken(token).get(0); // playlistEntity = playlistRepository.save(playlistEntity);
PlaylistEntity playlistEntity = new PlaylistEntity(data.name, user); // user.addPlaylist(playlistEntity);
playlistEntity = playlistRepository.save(playlistEntity); // userRepository.save(user);
user.addPlaylist(playlistEntity); // return new ResponseEntity<>(new PlaylistResponseData("Ok", playlistEntity), HttpStatus.OK);
userRepository.save(user); // }
return new ResponseEntity<>(new PlaylistResponseData("Ok", playlistEntity), HttpStatus.OK); //
} //
// }
}
@RequestMapping("/addToPlaylist") @RequestMapping("/addToPlaylist")
public ResponseEntity<PlaylistResponseData> addToPlaylist(@CookieValue(value = "token", defaultValue = "") String token, @RequestBody AddToPlaylistData data) { public ResponseEntity<PlaylistResponseData> addToPlaylist(@CookieValue(value = "token", defaultValue = "") String token, @RequestBody AddToPlaylistData data) {

View File

@ -1,171 +0,0 @@
package net.Broken.RestApi;
import net.Broken.DB.Entity.UserEntity;
import net.Broken.DB.Repository.PendingUserRepository;
import net.Broken.DB.Repository.UserRepository;
import net.Broken.MainBot;
import net.Broken.RestApi.Data.UserManager.GuildInfo;
import net.Broken.RestApi.Data.UserManager.UserConnectionData;
import net.Broken.RestApi.Data.UserManager.UserInfoData;
import net.Broken.Tools.UserManager.Exceptions.PasswordNotMatchException;
import net.Broken.Tools.UserManager.Exceptions.UnknownTokenException;
import net.Broken.Tools.UserManager.Exceptions.UserNotFoundException;
import net.Broken.Tools.UserManager.Oauth;
import net.Broken.Tools.UserManager.Stats.GuildStatsPack;
import net.Broken.Tools.UserManager.Stats.UserStatsUtils;
import net.Broken.Tools.UserManager.UserUtils;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.User;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
/**
* Rest Api controller for /api/userManagement
*/
@RestController
@RequestMapping("/api/userManagement")
public class UserManagerAPIController {
final
PendingUserRepository pendingUserRepository;
final
UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
Logger logger = LogManager.getLogger();
UserUtils userUtils = UserUtils.getInstance();
@Autowired
public UserManagerAPIController(PendingUserRepository pendingUserRepository, UserRepository userRepository, PasswordEncoder passwordEncoder) {
this.pendingUserRepository = pendingUserRepository;
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}
// @RequestMapping(value = "/preRegister", method = RequestMethod.POST)
// public ResponseEntity<CheckResposeData> command(@RequestBody UserInfoData data) {
// if (data != null && data.name != null) {
// try {
// String id = userUtils.sendCheckToken(pendingUserRepository, userRepository, passwordEncoder, data);
// return new ResponseEntity<>(new CheckResposeData(true, data.name, "Message sent", id), HttpStatus.OK);
// } catch (UserNotFoundException e) {
// logger.warn("User \"" + data.name + "\" not found!");
// return new ResponseEntity<>(new CheckResposeData(false, data.name, "User not found on server!", ""), HttpStatus.NOT_FOUND);
// } catch (PasswordNotMatchException userAlreadyRegistered) {
// return new ResponseEntity<>(new CheckResposeData(false, data.name, "User already registered in pending database and password not match!", ""), HttpStatus.NOT_ACCEPTABLE);
//
// } catch (UserAlreadyRegistered userAlreadyRegistered) {
// return new ResponseEntity<>(new CheckResposeData(false, data.name, "User already registered in database!", ""), HttpStatus.NOT_ACCEPTABLE);
// }
// } else {
// return new ResponseEntity<>(new CheckResposeData(false, "", "Missing parameter(s)", ""), HttpStatus.BAD_REQUEST);
// }
// }
// @RequestMapping(value = "/confirmAccount", method = RequestMethod.POST)
// public ResponseEntity<UserConnectionData> confirAccount(@RequestBody ConfirmData data) {
// try {
// PendingUserEntity pUser = userUtils.confirmCheckToken(pendingUserRepository, Integer.parseInt(data.id), data.checkToken);
// UserEntity user = new UserEntity(pUser, userUtils.generateApiToken());
// userRepository.save(user);
// pendingUserRepository.delete(pUser);
//
// return new ResponseEntity<>(new UserConnectionData(true, user.getName(), user.getApiToken(), ""), HttpStatus.OK);
// } catch (TokenNotMatch tokenNotMatch) {
// logger.warn("Pre token not match for " + data.id + "!");
// return new ResponseEntity<>(new UserConnectionData(false, "Token not match!", "token"), HttpStatus.NOT_ACCEPTABLE);
// } catch (UserNotFoundException e) {
// logger.warn("Id not found in DB (" + data.id + ")");
// return new ResponseEntity<>(new UserConnectionData(false, "User not found on DB!", "user"), HttpStatus.NOT_ACCEPTABLE);
// }
// }
@RequestMapping(value = "/requestToken", method = RequestMethod.POST)
public ResponseEntity<UserConnectionData> requestToken(@RequestBody UserInfoData data) {
try {
UserEntity user = userUtils.getUser(userRepository, passwordEncoder, data);
return new ResponseEntity<>(new UserConnectionData(true, user.getName(), user.getApiToken(), ""), HttpStatus.OK);
} catch (UserNotFoundException e) {
return new ResponseEntity<>(new UserConnectionData(false, "User not registered!", "user"), HttpStatus.NOT_ACCEPTABLE);
} catch (PasswordNotMatchException e) {
return new ResponseEntity<>(new UserConnectionData(false, "Wrong user name or password!", "password"), HttpStatus.NOT_ACCEPTABLE);
}
}
@RequestMapping(value = "/getGuilds", method = RequestMethod.GET)
public ResponseEntity<List<GuildInfo>> getGuilds(@CookieValue("token") String token) {
try {
UserEntity userE = userUtils.getUserWithApiToken(userRepository, token);
User user = MainBot.jda.getUserById(userE.getJdaId());
List<GuildInfo> temp = new ArrayList<>();
if (user != null) {
for (Guild guild : user.getMutualGuilds()) {
temp.add(new GuildInfo(guild.getName(), guild.getId(), guild.getMember(user).hasPermission(Permission.ADMINISTRATOR), guild.getIconUrl()));
}
}
return new ResponseEntity<>(temp, HttpStatus.OK);
} catch (UnknownTokenException e) {
logger.catching(e);
return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
}
}
@RequestMapping(value = "/oauthLogin", method = RequestMethod.POST)
public ResponseEntity<UserConnectionData> oauthLogin(@RequestParam(value = "token") String discordToken) {
logger.debug(discordToken);
UserEntity user = Oauth.getInstance().getUserEntity(discordToken, userRepository, passwordEncoder);
logger.info("OAuth login for " + user.getName());
return new ResponseEntity<>(new UserConnectionData(true, user.getName(), user.getApiToken(), ""), HttpStatus.OK);
}
@RequestMapping(value = "/checkToken", method = RequestMethod.GET)
public ResponseEntity checkToken(@CookieValue(value = "token") String token) {
try {
userUtils.getUserWithApiToken(userRepository, token);
return new ResponseEntity(HttpStatus.OK);
} catch (UnknownTokenException e) {
logger.info("Token check fail");
return new ResponseEntity(HttpStatus.UNAUTHORIZED);
}
}
@RequestMapping(value = "/getStatsPack", method = RequestMethod.GET)
public ResponseEntity<GuildStatsPack> getStatsPack(@CookieValue(value = "token") String token, @RequestParam(value = "guild") String guildID) {
try {
UserEntity user = userUtils.getUserWithApiToken(userRepository, token);
Guild guild = MainBot.jda.getGuildById(guildID);
if (guild == null) {
logger.warn("Request whit no guild!");
return new ResponseEntity(HttpStatus.BAD_REQUEST);
}
return new ResponseEntity<>(UserStatsUtils.getINSTANCE().getStatPack(user, guildID), HttpStatus.OK);
} catch (UnknownTokenException e) {
logger.info("Token check fail");
return new ResponseEntity(HttpStatus.UNAUTHORIZED);
}
}
}

View File

@ -1,75 +0,0 @@
package net.Broken.Tools.UserManager;
import net.Broken.DB.Entity.UserEntity;
import net.Broken.DB.Repository.UserRepository;
import net.Broken.MainBot;
import net.dv8tion.jda.api.entities.User;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.json.JSONObject;
import org.springframework.security.crypto.password.PasswordEncoder;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.List;
public class Oauth {
private static Oauth INSTANCE = new Oauth();
Logger logger = LogManager.getLogger();
private String baseUrl = "https://discordapp.com/api";
private String mePath = "/users/@me";
public static Oauth getInstance() {
return INSTANCE;
}
private JSONObject getUserId(String token) {
StringBuffer content = new StringBuffer();
try {
String httpsURL = baseUrl + mePath;
URL myUrl = new URL(httpsURL);
HttpURLConnection con = (HttpURLConnection) myUrl.openConnection();
con.setRequestProperty("Authorization", "Bearer " + token);
con.setRequestProperty("User-Agent", "DiscordBot (claptrapbot.com, 0.1)");
con.setRequestMethod("GET");
logger.debug("Response code: " + con.getResponseCode());
BufferedReader in = new BufferedReader(
new InputStreamReader(con.getInputStream()));
String inputLine;
while ((inputLine = in.readLine()) != null) {
content.append(inputLine);
}
in.close();
} catch (IOException e) {
e.printStackTrace();
}
JSONObject json = new JSONObject(content.toString());
logger.debug(json);
return json;
}
public UserEntity getUserEntity(String token, UserRepository userRepository, PasswordEncoder passwordEncoder) {
JSONObject discorResponse = getUserId(token);
List<UserEntity> userEntitys = userRepository.findByJdaId(discorResponse.getString("id"));
if (userEntitys.size() != 0) {
return userEntitys.get(0);
} else {
User jdaUser = MainBot.jda.getUserById(discorResponse.getString("id"));
UserEntity user;
if (jdaUser == null)
user = new UserEntity(discorResponse.getString("username"), discorResponse.getString("id"), passwordEncoder);
else
user = new UserEntity(MainBot.jda.getUserById(discorResponse.getString("id")), passwordEncoder);
user = userRepository.save(user);
return user;
}
}
}

View File

@ -1,64 +0,0 @@
package net.Broken.Tools.UserManager;
import net.Broken.DB.Entity.PendingPwdResetEntity;
import net.Broken.DB.Entity.UserEntity;
import net.Broken.DB.Repository.PendingPwdResetRepository;
import net.Broken.DB.Repository.UserRepository;
import net.Broken.SpringContext;
import net.Broken.Tools.UserManager.Exceptions.TokenNotMatch;
import net.Broken.Tools.UserManager.Exceptions.UserNotFoundException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.context.ApplicationContext;
import org.springframework.security.crypto.password.PasswordEncoder;
import java.util.List;
public class PasswordResetUtils {
private static PasswordResetUtils INSTANCE = new PasswordResetUtils();
private Logger logger = LogManager.getLogger();
private PasswordEncoder passwordEncoder;
private PendingPwdResetRepository pendingPwdResetRepository;
private UserRepository userRepository;
/**
* Private default constructor
*/
private PasswordResetUtils() {
ApplicationContext context = SpringContext.getAppContext();
passwordEncoder = (PasswordEncoder) context.getBean("passwordEncoder");
pendingPwdResetRepository = (PendingPwdResetRepository) context.getBean("pendingPwdResetRepository");
userRepository = (UserRepository) context.getBean("userRepository");
}
/**
* Singleton
*
* @return Unique PasswordResetUtils instance
*/
public static PasswordResetUtils getInstance() {
return INSTANCE;
}
public String resetRequest(UserEntity userEntity) {
String token = UserUtils.getInstance().generateCheckToken();
String encodedToken = passwordEncoder.encode(token);
PendingPwdResetEntity entity = new PendingPwdResetEntity(userEntity, encodedToken);
pendingPwdResetRepository.save(entity);
return encodedToken;
}
public void changePass(UserEntity userEntity, String token, String newPassword) throws UserNotFoundException, TokenNotMatch {
List<PendingPwdResetEntity> dbResults = pendingPwdResetRepository.findByUserEntity(userEntity);
if (dbResults.size() == 0)
throw new UserNotFoundException();
PendingPwdResetEntity pendingPwdReset = dbResults.get(0);
if (!passwordEncoder.matches(token, pendingPwdReset.getSecurityToken()))
throw new TokenNotMatch();
userEntity.setPassword(passwordEncoder.encode(newPassword));
userRepository.save(userEntity);
}
}

View File

@ -24,6 +24,7 @@ import java.awt.*;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Optional;
public class UserStatsUtils { public class UserStatsUtils {
@ -105,27 +106,16 @@ public class UserStatsUtils {
} }
public List<UserStats> getUserStats(User user) { public List<UserStats> getUserStats(User user) {
UserEntity userEntity; UserEntity userEntity = userRepository.findByJdaId(user.getId())
List<UserEntity> userList = userRepository.findByJdaId(user.getId()); .orElseGet(() -> genUserEntity(user));
if (userList.size() == 0) {
logger.debug("User not registered, generate it. User: " + user.getName() + " " + user.getDiscriminator());
userEntity = genUserEntity(user);
} else
userEntity = userList.get(0);
return getUserStats(userEntity); return getUserStats(userEntity);
} }
public UserStats getGuildUserStats(Member member) { public UserStats getGuildUserStats(Member member) {
List<UserEntity> userEntityList = userRepository.findByJdaId(member.getUser().getId()); UserEntity userEntity = userRepository.findByJdaId(member.getUser().getId())
UserEntity userEntity; .orElseGet(() -> genUserEntity(member.getUser()));
if (userEntityList.size() == 0) {
logger.debug("UserEntity not found for user " + member.getNickname());
userEntity = genUserEntity(member.getUser());
} else
userEntity = userEntityList.get(0);
List<UserStats> userStatsList = userStatsRepository.findByUserAndGuildId(userEntity, member.getGuild().getId()); List<UserStats> userStatsList = userStatsRepository.findByUserAndGuildId(userEntity, member.getGuild().getId());
if (userStatsList.size() == 0) { if (userStatsList.size() == 0) {
@ -177,7 +167,7 @@ public class UserStatsUtils {
private UserEntity genUserEntity(User user) { private UserEntity genUserEntity(User user) {
UserEntity userEntity = new UserEntity(user, passwordEncoder); UserEntity userEntity = new UserEntity(user);
return userRepository.save(userEntity); return userRepository.save(userEntity);
} }

View File

@ -39,32 +39,6 @@ public class UserUtils {
public static UserUtils getInstance() { public static UserUtils getInstance() {
return INSTANCE; return INSTANCE;
} }
/**
* Get user Entity
*
* @param userRepository User DB interface
* @param passwordEncoder Password encoder
* @param userInfoData Received data
* @return User Entity
* @throws UserNotFoundException User not found in User DB
* @throws PasswordNotMatchException Given password not match
*/
public UserEntity getUser(UserRepository userRepository, PasswordEncoder passwordEncoder, UserInfoData userInfoData) throws UserNotFoundException, PasswordNotMatchException {
List<UserEntity> users = userRepository.findByName(userInfoData.name);
if (users.size() < 1) {
logger.warn("Login with unknown username: " + userInfoData.name);
throw new UserNotFoundException();
} else {
UserEntity user = users.get(0);
if (passwordEncoder.matches(userInfoData.password, user.getPassword())) {
logger.info("Login successful for " + user.getName());
return user;
} else {
logger.warn("Login fail for " + user.getName() + ", wrong password!");
throw new PasswordNotMatchException();
}
}
}
/** /**
* return token's UserEntity * return token's UserEntity
@ -75,36 +49,12 @@ public class UserUtils {
* @throws UnknownTokenException Can't find token on User DB * @throws UnknownTokenException Can't find token on User DB
*/ */
public UserEntity getUserWithApiToken(UserRepository userRepository, String token) throws UnknownTokenException { public UserEntity getUserWithApiToken(UserRepository userRepository, String token) throws UnknownTokenException {
List<UserEntity> users = userRepository.findByApiToken(token); // List<UserEntity> users = userRepository.findByApiToken(token);
if (users.size() > 0) { // if (users.size() > 0) {
return users.get(0); // return users.get(0);
} else // } else
throw new UnknownTokenException(); // throw new UnknownTokenException();
return null;
} }
/**
* Generate API Token
*
* @return UUID String TODO Find something more secure
*/
public String generateApiToken() {
return UUID.randomUUID().toString();
}
/**
* Generate short check token
*
* @return check token as string
*/
public String generateCheckToken() {
SecureRandom random = new SecureRandom();
long longToken = Math.abs(random.nextLong());
String randomStr = Long.toString(longToken, 16);
randomStr = randomStr.substring(0, 4);
randomStr = randomStr.toUpperCase();
return randomStr;
}
} }

View File

@ -39,3 +39,5 @@ discord:
client-id: ${CLIENT_ID} client-id: ${CLIENT_ID}
client-secret: ${CLIENT_SECRET} client-secret: ${CLIENT_SECRET}
token-endpoint: https://discord.com/api/oauth2/token token-endpoint: https://discord.com/api/oauth2/token
tokenRevokeEndpoint: https://discord.com/api/oauth2/token/revoke
userInfoEnpoint: https://discord.com/api/users/@me