mirror of
https://github.com/vran-dev/databasir.git
synced 2025-09-19 10:16:58 +08:00
feat: support github oauth
This commit is contained in:
@@ -31,6 +31,9 @@ dependencies {
|
||||
implementation 'org.commonmark:commonmark:0.18.1'
|
||||
implementation 'org.freemarker:freemarker:2.3.31'
|
||||
|
||||
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
|
||||
implementation 'com.squareup.retrofit2:converter-jackson:2.9.0'
|
||||
|
||||
// test
|
||||
testImplementation "mysql:mysql-connector-java:${mysqlConnectorVersion}"
|
||||
}
|
||||
|
@@ -65,14 +65,14 @@ public class UserService {
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void create(UserCreateRequest userCreateRequest) {
|
||||
public Integer create(UserCreateRequest userCreateRequest) {
|
||||
userDao.selectByEmailOrUsername(userCreateRequest.getUsername()).ifPresent(data -> {
|
||||
throw DomainErrors.USERNAME_OR_EMAIL_DUPLICATE.exception();
|
||||
});
|
||||
String hashedPassword = bCryptPasswordEncoder.encode(userCreateRequest.getPassword());
|
||||
UserPojo pojo = userPojoConverter.of(userCreateRequest, hashedPassword);
|
||||
try {
|
||||
userDao.insertAndReturnId(pojo);
|
||||
return userDao.insertAndReturnId(pojo);
|
||||
} catch (DuplicateKeyException e) {
|
||||
throw DomainErrors.USERNAME_OR_EMAIL_DUPLICATE.exception();
|
||||
}
|
||||
|
@@ -0,0 +1,71 @@
|
||||
package com.databasir.core.infrastructure.oauth2;
|
||||
|
||||
|
||||
import com.databasir.core.infrastructure.remote.github.GithubRemoteService;
|
||||
import com.databasir.dao.enums.OAuthAppType;
|
||||
import com.databasir.dao.impl.OAuthAppDao;
|
||||
import com.databasir.dao.tables.pojos.OauthAppPojo;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class GithubOauthHandler implements OAuthHandler {
|
||||
|
||||
private final GithubRemoteService githubRemoteService;
|
||||
|
||||
private final OAuthAppDao oAuthAppDao;
|
||||
|
||||
@Override
|
||||
public boolean support(String oauthAppType) {
|
||||
return OAuthAppType.GITHUB.isSame(oauthAppType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String authorization(String registrationId) {
|
||||
OauthAppPojo app = oAuthAppDao.selectByRegistrationId(registrationId);
|
||||
String authUrl = app.getAuthUrl();
|
||||
String clientId = app.getClientId();
|
||||
String authorizeUrl = authUrl + "/login/oauth/authorize";
|
||||
String url = UriComponentsBuilder.fromUriString(authorizeUrl)
|
||||
.queryParam("client_id", clientId)
|
||||
.queryParam("scope", "read:user user:email")
|
||||
.encode()
|
||||
.build()
|
||||
.toUriString();
|
||||
return url;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuthProcessResult process(OAuthProcessContext context) {
|
||||
OauthAppPojo authApp = oAuthAppDao.selectByRegistrationId(context.getRegistrationId());
|
||||
String clientId = authApp.getClientId();
|
||||
String clientSecret = authApp.getClientSecret();
|
||||
String baseUrl = authApp.getResourceUrl();
|
||||
|
||||
Map<String, String> params = context.getCallbackParameters();
|
||||
String code = params.get("code");
|
||||
String accessToken = githubRemoteService.getToken(baseUrl, clientId, clientSecret, code)
|
||||
.get("access_token")
|
||||
.asText();
|
||||
String email = null;
|
||||
for (JsonNode node : githubRemoteService.getEmail(baseUrl, accessToken)) {
|
||||
if (node.get("primary").asBoolean()) {
|
||||
email = node.get("email").asText();
|
||||
}
|
||||
}
|
||||
JsonNode profile = githubRemoteService.getProfile(baseUrl, accessToken);
|
||||
String nickname = profile.get("name").asText();
|
||||
String avatar = profile.get("avatar_url").asText();
|
||||
OAuthProcessResult result = new OAuthProcessResult();
|
||||
result.setEmail(email);
|
||||
result.setNickname(nickname);
|
||||
result.setUsername(email);
|
||||
result.setAvatar(avatar);
|
||||
return result;
|
||||
}
|
||||
}
|
@@ -0,0 +1,61 @@
|
||||
package com.databasir.core.infrastructure.oauth2;
|
||||
|
||||
import com.databasir.core.domain.login.data.LoginKeyResponse;
|
||||
import com.databasir.core.domain.login.service.LoginService;
|
||||
import com.databasir.core.domain.user.data.UserCreateRequest;
|
||||
import com.databasir.core.domain.user.service.UserService;
|
||||
import com.databasir.core.infrastructure.oauth2.data.OAuthAppResponse;
|
||||
import com.databasir.dao.impl.OAuthAppDao;
|
||||
import com.databasir.dao.tables.pojos.OauthAppPojo;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class OAuthAppService {
|
||||
|
||||
private final List<OAuthHandler> oAuthHandlers;
|
||||
|
||||
private final OAuthAppDao oAuthAppDao;
|
||||
|
||||
private final UserService userService;
|
||||
|
||||
private final LoginService loginService;
|
||||
|
||||
public LoginKeyResponse oauthCallback(String registrationId, Map<String, String> params) {
|
||||
|
||||
// match handler
|
||||
OauthAppPojo app = oAuthAppDao.selectByRegistrationId(registrationId);
|
||||
OAuthHandler oAuthHandler = oAuthHandlers.stream()
|
||||
.filter(handler -> handler.support(app.getAppType()))
|
||||
.findFirst()
|
||||
.orElseThrow();
|
||||
|
||||
// process by handler
|
||||
OAuthProcessContext context = OAuthProcessContext.builder()
|
||||
.callbackParameters(params)
|
||||
.registrationId(registrationId)
|
||||
.build();
|
||||
OAuthProcessResult result = oAuthHandler.process(context);
|
||||
|
||||
// create new user
|
||||
UserCreateRequest user = new UserCreateRequest();
|
||||
user.setUsername(result.getUsername());
|
||||
user.setNickname(result.getNickname());
|
||||
user.setEmail(result.getEmail());
|
||||
user.setAvatar(result.getAvatar());
|
||||
user.setPassword(UUID.randomUUID().toString().substring(0, 6));
|
||||
Integer userId = userService.create(user);
|
||||
|
||||
return loginService.generate(userId);
|
||||
}
|
||||
|
||||
public List<OAuthAppResponse> listAll() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,10 @@
|
||||
package com.databasir.core.infrastructure.oauth2;
|
||||
|
||||
public interface OAuthHandler {
|
||||
|
||||
boolean support(String oauthAppType);
|
||||
|
||||
String authorization(String registrationId);
|
||||
|
||||
OAuthProcessResult process(OAuthProcessContext context);
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
package com.databasir.core.infrastructure.oauth2;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class OAuthProcessContext {
|
||||
|
||||
private String registrationId;
|
||||
|
||||
@Builder.Default
|
||||
private Map<String, String> callbackParameters = new HashMap<>();
|
||||
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
package com.databasir.core.infrastructure.oauth2;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class OAuthProcessResult {
|
||||
|
||||
private String email;
|
||||
|
||||
private String username;
|
||||
|
||||
private String nickname;
|
||||
|
||||
private String avatar;
|
||||
|
||||
}
|
@@ -0,0 +1,32 @@
|
||||
package com.databasir.core.infrastructure.oauth2.data;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class OAuthAppResponse {
|
||||
|
||||
private Integer id;
|
||||
|
||||
private String name;
|
||||
|
||||
private String icon;
|
||||
|
||||
private String registrationId;
|
||||
|
||||
private String clientId;
|
||||
|
||||
private String clientSecret;
|
||||
|
||||
private LocalDateTime updateAt;
|
||||
|
||||
private LocalDateTime createAt;
|
||||
|
||||
}
|
@@ -0,0 +1,34 @@
|
||||
package com.databasir.core.infrastructure.remote;
|
||||
|
||||
import com.databasir.core.infrastructure.remote.github.GithubApiClient;
|
||||
import com.databasir.core.infrastructure.remote.github.GithubOauthClient;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import retrofit2.Retrofit;
|
||||
import retrofit2.converter.jackson.JacksonConverterFactory;
|
||||
|
||||
@Configuration
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class ClientConfig {
|
||||
|
||||
@Bean
|
||||
public GithubApiClient githubApiClient() {
|
||||
Retrofit retrofit = new Retrofit.Builder()
|
||||
.baseUrl("https://api.github.com")
|
||||
.addConverterFactory(JacksonConverterFactory.create())
|
||||
.build();
|
||||
return retrofit.create(GithubApiClient.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public GithubOauthClient githubOauthClient() {
|
||||
Retrofit retrofit = new Retrofit.Builder()
|
||||
.baseUrl("https://github.com")
|
||||
.addConverterFactory(JacksonConverterFactory.create())
|
||||
.build();
|
||||
return retrofit.create(GithubOauthClient.class);
|
||||
}
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
package com.databasir.core.infrastructure.remote.github;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.GET;
|
||||
import retrofit2.http.Header;
|
||||
import retrofit2.http.Headers;
|
||||
import retrofit2.http.Url;
|
||||
|
||||
public interface GithubApiClient {
|
||||
|
||||
@GET
|
||||
@Headers(value = {
|
||||
"Accept: application/json"
|
||||
})
|
||||
Call<JsonNode> getEmail(@Url String url, @Header("Authorization") String token);
|
||||
|
||||
@GET
|
||||
@Headers(value = {
|
||||
"Accept: application/json"
|
||||
})
|
||||
Call<JsonNode> getProfile(@Url String url, @Header("Authorization") String token);
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
package com.databasir.core.infrastructure.remote.github;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.Headers;
|
||||
import retrofit2.http.POST;
|
||||
import retrofit2.http.QueryMap;
|
||||
import retrofit2.http.Url;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public interface GithubOauthClient {
|
||||
|
||||
@Headers(value = {
|
||||
"Accept: application/json"
|
||||
})
|
||||
@POST
|
||||
Call<JsonNode> getAccessToken(@Url String url, @QueryMap Map<String, String> request);
|
||||
|
||||
}
|
@@ -0,0 +1,74 @@
|
||||
package com.databasir.core.infrastructure.remote.github;
|
||||
|
||||
import com.databasir.common.SystemException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Response;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class GithubRemoteService {
|
||||
|
||||
private static final String TOKEN_PREFIX = "token ";
|
||||
|
||||
private final GithubApiClient githubApiClient;
|
||||
|
||||
private final GithubOauthClient githubOauthClient;
|
||||
|
||||
public JsonNode getToken(String baseUrl,
|
||||
String clientId,
|
||||
String clientSecret,
|
||||
String code) {
|
||||
TokenRequest request = TokenRequest.builder()
|
||||
.client_id(clientId)
|
||||
.client_secret(clientSecret)
|
||||
.code(code)
|
||||
.build();
|
||||
String path = "/login/oauth/access_token";
|
||||
String url = baseUrl + path;
|
||||
return execute(githubOauthClient.getAccessToken(url, request.toMap()));
|
||||
}
|
||||
|
||||
public JsonNode getEmail(String baseUrl, String token) {
|
||||
String path;
|
||||
if (baseUrl.contains("api.github.com")) {
|
||||
path = "/user/emails";
|
||||
} else {
|
||||
path = "/api/v3/user/emails";
|
||||
}
|
||||
String url = baseUrl + path;
|
||||
return execute(githubApiClient.getEmail(url, TOKEN_PREFIX + token));
|
||||
}
|
||||
|
||||
public JsonNode getProfile(String baseUrl, String token) {
|
||||
String path;
|
||||
if (baseUrl.contains("api.github.com")) {
|
||||
path = "/user";
|
||||
} else {
|
||||
path = "/api/v3/user";
|
||||
}
|
||||
String url = baseUrl + path;
|
||||
return execute(githubApiClient.getProfile(url, TOKEN_PREFIX + token));
|
||||
}
|
||||
|
||||
private <T> T execute(Call<T> call) {
|
||||
try {
|
||||
Response<T> response = call.execute();
|
||||
if (!response.isSuccessful()) {
|
||||
log.error("request error: " + call.request());
|
||||
throw new SystemException("Call Remote Error");
|
||||
} else {
|
||||
T body = response.body();
|
||||
return body;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new SystemException("System Error", e);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,32 @@
|
||||
package com.databasir.core.infrastructure.remote.github;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
@SuppressWarnings("checkstyle:all")
|
||||
@Builder
|
||||
public class TokenRequest {
|
||||
|
||||
private String client_id;
|
||||
|
||||
private String client_secret;
|
||||
|
||||
private String code;
|
||||
|
||||
private String redirect_uri;
|
||||
|
||||
public Map<String, String> toMap() {
|
||||
HashMap<String, String> map = new HashMap<>();
|
||||
map.put("client_id", client_id);
|
||||
map.put("client_secret", client_secret);
|
||||
map.put("code", code);
|
||||
if (redirect_uri != null) {
|
||||
map.put("redirect_uri", redirect_uri);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user