aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Vitaly Takmazov2019-12-20 17:24:52 +0300
committerGravatar Vitaly Takmazov2019-12-20 17:24:52 +0300
commitbc7f66b3c7dfaa3100e2ea2513448b0ec9a4dab7 (patch)
treee730b66df2ec988d487eef9ae377051cbf64819a
parent66df022b6908c6e3bc92145b15dba328ca200eb6 (diff)
Apple SignIn API endpoint
-rw-r--r--src/main/java/com/juick/server/api/ApiSocialLogin.java174
1 files changed, 92 insertions, 82 deletions
diff --git a/src/main/java/com/juick/server/api/ApiSocialLogin.java b/src/main/java/com/juick/server/api/ApiSocialLogin.java
index a1b22606..0fa78788 100644
--- a/src/main/java/com/juick/server/api/ApiSocialLogin.java
+++ b/src/main/java/com/juick/server/api/ApiSocialLogin.java
@@ -17,6 +17,8 @@
package com.juick.server.api;
import com.fasterxml.jackson.databind.ObjectMapper;
+import com.github.scribejava.apis.AppleClientSecretGenerator;
+import com.github.scribejava.apis.AppleSignInApi;
import com.github.scribejava.apis.FacebookApi;
import com.github.scribejava.apis.VkontakteApi;
import com.github.scribejava.core.builder.ServiceBuilder;
@@ -35,9 +37,20 @@ import com.juick.model.facebook.User;
import com.juick.server.util.HttpBadRequestException;
import com.juick.service.CrosspostService;
import com.juick.service.EmailService;
-import com.juick.service.TelegramService;
import com.juick.service.UserService;
import com.juick.model.vk.UsersResponse;
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.jwk.source.JWKSource;
+import com.nimbusds.jose.jwk.source.RemoteJWKSet;
+import com.nimbusds.jose.proc.BadJOSEException;
+import com.nimbusds.jose.proc.JWSKeySelector;
+import com.nimbusds.jose.proc.JWSVerificationKeySelector;
+import com.nimbusds.jose.proc.SecurityContext;
+import com.nimbusds.jwt.proc.ConfigurableJWTProcessor;
+import com.nimbusds.jwt.proc.DefaultJWTClaimsVerifier;
+import com.nimbusds.jwt.proc.DefaultJWTProcessor;
+import net.minidev.json.JSONObject;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
@@ -56,8 +69,11 @@ import org.springframework.web.util.UriComponentsBuilder;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import java.io.IOException;
+import java.net.URL;
import java.security.GeneralSecurityException;
+import java.text.ParseException;
import java.util.Collections;
+import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
@@ -79,7 +95,7 @@ public class ApiSocialLogin {
private static final String TWITTER_VERIFY_URL = "https://api.twitter.com/1.1/account/verify_credentials.json";
@Inject
private ObjectMapper jsonMapper;
- private OAuth20Service facebookAuthService, vkAuthService;
+ private OAuth20Service facebookAuthService, vkAuthService, appleSignInService;
@Value("${twitter_consumer_key:appid}")
private String twitterConsumerKey;
@@ -89,10 +105,12 @@ public class ApiSocialLogin {
private String VK_APPID;
@Value("${vk_secret:secret}")
private String VK_SECRET;
- @Value("${telegram_token:secret}")
- private String telegramToken;
@Value("${google_client_id:}")
private String googleClientId;
+ @Value("${apple_app_id:appid}")
+ private String appleApplicationId;
+ @Value("${ap_base_uri:http://localhost:8080/}")
+ private String baseUri;
@Inject
private CrosspostService crosspostService;
@@ -101,7 +119,7 @@ public class ApiSocialLogin {
@Inject
private EmailService emailService;
@Inject
- private TelegramService telegramService;
+ private AppleClientSecretGenerator clientSecretGenerator;
private final HttpTransport transport = new NetHttpTransport();
private final JsonFactory jsonFactory = new JacksonFactory();
@@ -125,6 +143,13 @@ public class ApiSocialLogin {
.defaultScope("friends,wall,offline")
.callback(VK_REDIRECT)
.build(VkontakteApi.instance());
+ ServiceBuilder appleSignInBuilder = new ServiceBuilder(appleApplicationId);
+ UriComponentsBuilder redirectBuilder = UriComponentsBuilder.fromUriString(baseUri);
+ String appleSignInRedirectUri = redirectBuilder.replacePath("/_apple").build().toUriString();
+ appleSignInService = appleSignInBuilder
+ .callback(appleSignInRedirectUri)
+ .defaultScope("email")
+ .build(new AppleSignInApi(clientSecretGenerator));
}
@GetMapping("/api/_fblogin")
@@ -180,56 +205,7 @@ public class ApiSocialLogin {
}
return "redirect:/signup?type=fb&hash=" + state;
}
- }/*
- @GetMapping("/_twitter")
- protected void doTwitterLogin(HttpServletRequest request, HttpServletResponse response)
- throws IOException, ExecutionException, InterruptedException {
- String hash = StringUtils.EMPTY, request_token = StringUtils.EMPTY, request_token_secret = StringUtils.EMPTY;
- String verifier = request.getParameter("oauth_verifier");
- Cookie[] cookies = request.getCookies();
- for (Cookie cookie : cookies) {
- if (cookie.getName().equals("hash")) {
- hash = cookie.getValue();
- }
- if (cookie.getName().equals("request_token")) {
- request_token = cookie.getValue();
- }
- if (cookie.getName().equals("request_token_secret")) {
- request_token_secret = cookie.getValue();
- }
- }
- com.juick.User user = UserUtils.getCurrentUser();
- OAuth10aService oAuthService = twitterBuilder
- .apiSecret(twitterConsumerSecret)
- .callback("http://juick.com/_twitter")
- .build(TwitterApi.instance());
-
- if (request_token.isEmpty() && request_token_secret.isEmpty()
- && (verifier == null || verifier.isEmpty())) {
- OAuth1RequestToken requestToken = oAuthService.getRequestToken();
- String authUrl = oAuthService.getAuthorizationUrl(requestToken);
- response.addCookie(new Cookie("request_token", requestToken.getToken()));
- response.addCookie(new Cookie("request_token_secret", requestToken.getTokenSecret()));
- response.setStatus(HttpServletResponse.SC_FOUND);
- response.setHeader("Location", authUrl);
- } else {
- if (verifier != null && verifier.length() > 0) {
- OAuth1RequestToken requestToken = new OAuth1RequestToken(request_token, request_token_secret);
- OAuth1AccessToken accessToken = oAuthService.getAccessToken(requestToken, verifier);
- OAuthRequest oAuthRequest = new OAuthRequest(Verb.GET, TWITTER_VERIFY_URL);
- oAuthService.signRequest(accessToken, oAuthRequest);
- com.juick.twitter.User twitterUser = jsonMapper.readValue(oAuthService.execute(oAuthRequest).getBody(),
- com.juick.twitter.User.class);
- if (userService.linkTwitterAccount(user, accessToken.getToken(), accessToken.getTokenSecret(),
- twitterUser.getScreenName())) {
- response.setStatus(HttpServletResponse.SC_FOUND);
- response.setHeader("Location", "http://juick.com/settings");
- } else {
- response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
- }
- }
- }
- }*/
+ }
@GetMapping("/api/_vklogin")
protected String doVKLogin(@RequestParam(required = false) String code,
@RequestParam String state) throws IOException, ExecutionException, InterruptedException {
@@ -309,35 +285,69 @@ public class ApiSocialLogin {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(null);
}
}
- /*
- @GetMapping("/_tglogin")
- public String doDurovLogin(HttpServletRequest request,
- @RequestParam Map<String, String> params,
- HttpServletResponse response) {
- String dataCheckString = params.entrySet().stream()
- .filter(p -> !p.getKey().equals("hash"))
- .sorted(Map.Entry.comparingByKey())
- .map(p -> p.getKey() + "=" + p.getValue())
- .collect(Collectors.joining("\n"));
- String hash = params.get("hash");
- byte[] secretKey = DigestUtils.sha256(telegramToken);
- String resultString = new HmacUtils(HmacAlgorithms.HMAC_SHA_256, secretKey).hmacHex(dataCheckString);
- if (hash.equals(resultString)) {
- Long tgUser = Long.valueOf(params.get("id"));
- int uid = telegramService.getUser(tgUser);
- if (uid > 0) {
- Cookie c = new Cookie("hash", userService.getHashByUID(uid));
- c.setMaxAge(50 * 24 * 60 * 60);
- response.addCookie(c);
- return Utils.getPreviousPageByRequest(request).orElse("redirect:/");
+ @GetMapping("/_apple")
+ public String doAppleLogin(@RequestParam(required = false) String code, @RequestParam String state) {
+ if (StringUtils.isBlank(code)) {
+ String astate = UUID.randomUUID().toString();
+ crosspostService.addVKState(astate, state);
+ return "redirect:" + appleSignInService.getAuthorizationUrl(astate);
+ }
+ throw new HttpBadRequestException();
+ }
+ @PostMapping("/_apple")
+ public String doVerifyAppleResponse(@RequestParam Map<String, String> body) throws InterruptedException, ExecutionException, IOException, ParseException, JOSEException, BadJOSEException {
+ OAuth2AccessToken token = appleSignInService.getAccessToken(body.get("code"));
+ var jsonNode = jsonMapper.readTree(token.getRawResponse());
+ var idToken = jsonNode.get("id_token").textValue();
+
+// Create a JWT processor for the access tokens
+ ConfigurableJWTProcessor<SecurityContext> jwtProcessor =
+ new DefaultJWTProcessor<>();
+
+// The public RSA keys to validate the signatures will be sourced from the
+// OAuth 2.0 server's JWK set, published at a well-known URL. The RemoteJWKSet
+// object caches the retrieved keys to speed up subsequent look-ups and can
+// also handle key-rollover
+ JWKSource<SecurityContext> keySource =
+ new RemoteJWKSet<>(new URL("https://appleid.apple.com/auth/keys"));
+
+// The expected JWS algorithm of the access tokens (agreed out-of-band)
+ JWSAlgorithm expectedJWSAlg = JWSAlgorithm.RS256;
+
+// Configure the JWT processor with a key selector to feed matching public
+// RSA keys sourced from the JWK set URL
+ JWSKeySelector<SecurityContext> keySelector =
+ new JWSVerificationKeySelector<>(expectedJWSAlg, keySource);
+
+ jwtProcessor.setJWSKeySelector(keySelector);
+
+// Set the required JWT claims for access tokens issued by the Connect2id
+// server, may differ with other servers
+ jwtProcessor.setJWTClaimsSetVerifier(new DefaultJWTClaimsVerifier<>());
+
+// Process the token
+ JSONObject claimsSet = jwtProcessor.process(idToken, null).toJSONObject();
+
+ var email = claimsSet.getAsString("email");
+ var verified = claimsSet.getAsString("email_verified").equals("true");
+
+ if (verified) {
+ com.juick.User user = userService.getUserByEmail(email);
+ if (!user.isAnonymous()) {
+ String redirectUrl = crosspostService.verifyVKState(body.get("state"));
+ if (StringUtils.isBlank(redirectUrl)) {
+ logger.error("state is missing");
+ throw new HttpBadRequestException();
+ }
+ UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUriString(redirectUrl);
+ uriComponentsBuilder.queryParam("hash", userService.getHashByUID(user.getUid()));
+ return "redirect:" + uriComponentsBuilder.build().toUriString();
} else {
- String username = StringUtils.defaultString(params.get("username"), params.get("first_name"));
- telegramService.createTelegramUser(tgUser, username);
- return "redirect:/signup?type=durov&hash=" + userService.getSignUpHashByTelegramID(tgUser, username);
+ String verificationCode = RandomStringUtils.randomAlphanumeric(8).toUpperCase();
+ emailService.addVerificationCode(null, email, verificationCode);
+ return "redirect:/signup?type=email&hash=" + verificationCode;
}
- } else {
- logger.warn("invalid tg hash {} for {}", resultString, hash);
}
throw new HttpBadRequestException();
- }*/
+ }
}