aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/com
diff options
context:
space:
mode:
authorGravatar Vitaly Takmazov2023-06-20 07:10:16 +0300
committerGravatar Vitaly Takmazov2023-06-21 01:02:41 +0300
commit10dcb7324fac83c0190ff4a842360a035449f278 (patch)
tree95b90305ff7e7cdb3fd298878353ed702a0754a1 /src/main/java/com
parent314db08815ae98bff02a7489cddf861cd01f2629 (diff)
VK: read premium status using Callback API
Diffstat (limited to 'src/main/java/com')
-rw-r--r--src/main/java/com/juick/config/SecurityConfig.java2
-rw-r--r--src/main/java/com/juick/service/UserService.java5
-rw-r--r--src/main/java/com/juick/service/UserServiceImpl.java13
-rw-r--r--src/main/java/com/juick/service/VKService.java94
-rw-r--r--src/main/java/com/juick/www/api/webhooks/VkWebhook.java76
-rw-r--r--src/main/java/com/juick/www/controllers/SocialLogin.java109
6 files changed, 218 insertions, 81 deletions
diff --git a/src/main/java/com/juick/config/SecurityConfig.java b/src/main/java/com/juick/config/SecurityConfig.java
index a8f54cfd..e02e32ed 100644
--- a/src/main/java/com/juick/config/SecurityConfig.java
+++ b/src/main/java/com/juick/config/SecurityConfig.java
@@ -200,7 +200,7 @@ public class SecurityConfig {
"/api/swagger-ui/**",
"/api/messages/discussions",
"/api/users", "/api/thread", "/api/tags",
- "/api/tlgmbtwbhk", "/api/fbwbhk", "/api/_patreon",
+ "/api/tlgmbtwbhk", "/api/fbwbhk", "/api/_patreon", "/api/_vk",
"/api/skypebotendpoint", "/api/_fblogin",
"/api/_vklogin", "/api/_tglogin",
"/api/_google", "/api/_applelogin", "/api/signup",
diff --git a/src/main/java/com/juick/service/UserService.java b/src/main/java/com/juick/service/UserService.java
index fe5ce23f..4acc5b6a 100644
--- a/src/main/java/com/juick/service/UserService.java
+++ b/src/main/java/com/juick/service/UserService.java
@@ -25,12 +25,14 @@ import com.juick.model.ExternalToken;
import com.juick.util.UsernameTakenException;
import jakarta.annotation.Nonnull;
+import jakarta.annotation.Nullable;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import java.util.Collection;
import java.util.List;
+import java.util.Map;
import java.util.Optional;
/**
@@ -151,7 +153,8 @@ public interface UserService {
String getTelegramName(int uid);
- List<Pair<String, String>> getVkTokens(List<Integer> uids);
+ @Nullable
+ Pair<String, String> getVkTokens(int uid);
void deleteVKUser(Integer uid);
diff --git a/src/main/java/com/juick/service/UserServiceImpl.java b/src/main/java/com/juick/service/UserServiceImpl.java
index c586886b..d19af067 100644
--- a/src/main/java/com/juick/service/UserServiceImpl.java
+++ b/src/main/java/com/juick/service/UserServiceImpl.java
@@ -741,14 +741,13 @@ public class UserServiceImpl extends BaseJdbcService implements UserService {
@Transactional(readOnly = true)
@Override
- public List<Pair<String, String>> getVkTokens(List<Integer> uids) {
- return getNamedParameterJdbcTemplate().query(
- """
- SELECT vk_id, access_token FROM vk WHERE crosspost = 1 AND access_token <> ''"""
- + (uids.isEmpty() ? "" : " AND user_id IN (:uids)"),
+ public Pair<String, String> getVkTokens(int uid) {
+ var result = getNamedParameterJdbcTemplate().query(
+ "SELECT vk_id, access_token FROM vk WHERE user_id=:uid AND crosspost = 1 AND access_token <> ''",
new MapSqlParameterSource()
- .addValue("uids", uids),
+ .addValue("uid", uid),
(rs, num) -> Pair.of(rs.getString(1), rs.getString(2)));
+ return result.isEmpty() ? null : result.get(0);
}
@Transactional
@@ -885,7 +884,7 @@ public class UserServiceImpl extends BaseJdbcService implements UserService {
@Transactional(readOnly = true)
@Override
public boolean canDeleteTelegramUser(User user) {
- return getEmails(user).size() > 0 || getFbCrossPostStatus(user.getUid()).isConnected() || !getVkTokens(List.of(user.getUid())).isEmpty();
+ return getEmails(user).size() > 0 || getFbCrossPostStatus(user.getUid()).isConnected() || getVkTokens(user.getUid()) != null;
}
private static class TokenMapper implements RowMapper<ExternalToken> {
diff --git a/src/main/java/com/juick/service/VKService.java b/src/main/java/com/juick/service/VKService.java
new file mode 100644
index 00000000..14d7e3e9
--- /dev/null
+++ b/src/main/java/com/juick/service/VKService.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2008-2023, Juick
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.juick.service;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.github.scribejava.apis.VkontakteApi;
+import com.github.scribejava.core.builder.ServiceBuilder;
+import com.github.scribejava.core.model.OAuth2AccessToken;
+import com.github.scribejava.core.model.OAuthRequest;
+import com.github.scribejava.core.model.Response;
+import com.github.scribejava.core.model.Verb;
+import com.github.scribejava.core.oauth.OAuth20Service;
+import jakarta.annotation.PostConstruct;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import javax.inject.Inject;
+
+@Service
+public class VKService {
+ private static final Logger logger = LoggerFactory.getLogger("VK");
+ @Value("${vk_appid:appid}")
+ private String VK_APPID;
+ @Value("${vk_secret:secret}")
+ private String VK_SECRET;
+ private static final String VK_REDIRECT = "https://juick.com/_vklogin";
+ @Inject
+ private ObjectMapper jsonMapper;
+ @Inject
+ private UserService userService;
+ private OAuth20Service vkAuthService;
+
+ @PostConstruct
+ public void init() {
+ ServiceBuilder vkBuilder = new ServiceBuilder(VK_APPID);
+ setVkAuthService(vkBuilder.apiSecret(VK_SECRET)
+ .defaultScope("friends,wall,offline,groups")
+ .callback(VK_REDIRECT)
+ .build(VkontakteApi.instance()));
+ }
+
+ public void updatePremiumStatus(Integer userId) {
+ var vkUser = userService.getVkTokens(userId);
+ if (vkUser != null) {
+ OAuth2AccessToken token = new OAuth2AccessToken(vkUser.getRight());
+ OAuthRequest donRequest = new OAuthRequest(Verb.GET,
+ "https://api.vk.com/method/donut.isDon?owner_id=-67669480&v=5.131");
+ getVkAuthService().signRequest(token, donRequest);
+ try (Response vkResponse = getVkAuthService().execute(donRequest)) {
+ if (vkResponse.isSuccessful()) {
+ logger.info(vkResponse.getBody());
+ var response = jsonMapper.readTree(vkResponse.getBody());
+ if (response.has("response")) {
+ var isDon = response.get("response").intValue() > 0;
+ logger.info("{} is Don: {}", vkUser.getLeft(), isDon);
+ userService.setPremium(userId, isDon);
+ } else {
+ // token is expired or does not have "groups" permissions
+ userService.updateVkToken(userId, "");
+ }
+ }
+ } catch (Exception e) {
+ logger.error("Don request error", e);
+ }
+ } else {
+ logger.warn("User is not connected to VK: {}", userId);
+ }
+ }
+
+ public OAuth20Service getVkAuthService() {
+ return vkAuthService;
+ }
+
+ public void setVkAuthService(OAuth20Service vkAuthService) {
+ this.vkAuthService = vkAuthService;
+ }
+}
diff --git a/src/main/java/com/juick/www/api/webhooks/VkWebhook.java b/src/main/java/com/juick/www/api/webhooks/VkWebhook.java
new file mode 100644
index 00000000..9e4477b1
--- /dev/null
+++ b/src/main/java/com/juick/www/api/webhooks/VkWebhook.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2008-2023, Juick
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.juick.www.api.webhooks;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.juick.service.UserService;
+import com.juick.service.VKService;
+import com.juick.util.HttpBadRequestException;
+import org.apache.commons.io.IOUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.inject.Inject;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+@RestController
+public class VkWebhook {
+ private static final Logger logger = LoggerFactory.getLogger("VK");
+ @Value("${vk_webhook_secret:}")
+ private String secretKey;
+ @Value("${vk_webhook_confirmation:}")
+ private String confirmationCode;
+ @Inject
+ private ObjectMapper jsonMapper;
+ @Inject
+ private UserService userService;
+ @Inject
+ private VKService vkService;
+
+ @PostMapping(value = "/api/_vk")
+ public String processUpdate(InputStream body) throws IOException {
+ String data = IOUtils.toString(body, StandardCharsets.UTF_8);
+ logger.debug("Data: {}", data);
+ JsonNode json = jsonMapper.readTree(data);
+ var type = json.get("type").asText();
+ var secret = json.get("secret").asText("");
+ logger.info("Event received: {}, secret: {}", type, secret);
+ if (type.equals("confirmation")) {
+ return confirmationCode;
+ } else {
+ if (secretKey.equals(secret)) {
+ if (type.startsWith("donut_")) {
+ var vkId = json.get("object").get("user_id").asLong(0);
+ var userId = userService.getUIDbyVKID(vkId);
+ if (userId > 0) {
+ vkService.updatePremiumStatus(userId);
+ }
+ }
+ return "ok";
+ } else {
+ throw new HttpBadRequestException();
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/juick/www/controllers/SocialLogin.java b/src/main/java/com/juick/www/controllers/SocialLogin.java
index b0738fea..0cac26c5 100644
--- a/src/main/java/com/juick/www/controllers/SocialLogin.java
+++ b/src/main/java/com/juick/www/controllers/SocialLogin.java
@@ -27,6 +27,7 @@ import com.juick.model.ext.vk.UsersResponse;
import com.juick.service.EmailService;
import com.juick.service.TelegramService;
import com.juick.service.UserService;
+import com.juick.service.VKService;
import com.juick.service.security.entities.JuickUser;
import com.juick.util.HttpBadRequestException;
@@ -81,21 +82,17 @@ public class SocialLogin {
private String FACEBOOK_SECRET;
@Value("${ap_base_uri:http://localhost:8080/}")
private String baseUri;
- private static final String VK_REDIRECT = "http://juick.com/_vklogin";
private static final String TWITTER_VERIFY_URL = "https://api.twitter.com/1.1/account/verify_credentials.json";
@Inject
private ObjectMapper jsonMapper;
private ServiceBuilder twitterBuilder;
- private OAuth20Service facebookAuthService, vkAuthService, appleSignInService;
-
+ private OAuth20Service facebookAuthService, appleSignInService;
+ @Inject
+ private VKService vkService;
@Value("${twitter_consumer_key:appid}")
private String twitterConsumerKey;
@Value("${twitter_consumer_secret:secret}")
private String twitterConsumerSecret;
- @Value("${vk_appid:appid}")
- private String VK_APPID;
- @Value("${vk_secret:secret}")
- private String VK_SECRET;
@Value("${telegram_token:secret}")
private String telegramToken;
@Value("${apple_app_id:appid}")
@@ -115,13 +112,11 @@ public class SocialLogin {
public void init() {
ServiceBuilder facebookBuilder = new ServiceBuilder(FACEBOOK_APPID);
twitterBuilder = new ServiceBuilder(twitterConsumerKey);
- ServiceBuilder vkBuilder = new ServiceBuilder(VK_APPID);
+
UriComponentsBuilder redirectBuilder = UriComponentsBuilder.fromUriString(baseUri);
String facebookRedirectUri = redirectBuilder.replacePath("/_fblogin").build().toUriString();
facebookAuthService = facebookBuilder.apiSecret(FACEBOOK_SECRET).callback(facebookRedirectUri)
.defaultScope("email").build(FacebookApi.instance());
- vkAuthService = vkBuilder.apiSecret(VK_SECRET).defaultScope("friends,wall,offline,groups").callback(VK_REDIRECT)
- .build(VkontakteApi.instance());
ServiceBuilder appleSignInBuilder = new ServiceBuilder(appleApplicationId);
String appleSignInRedirectUri = redirectBuilder.replacePath("/_apple").build().toUriString();
appleSignInService = appleSignInBuilder.callback(appleSignInRedirectUri).defaultScope("email")
@@ -249,7 +244,7 @@ public class SocialLogin {
vkstate = UUID.randomUUID().toString();
Cookie c = new Cookie("vkstate", vkstate);
response.addCookie(c);
- return "redirect:" + vkAuthService.getAuthorizationUrl(vkstate);
+ return "redirect:" + vkService.getVkAuthService().getAuthorizationUrl(vkstate);
}
if (StringUtils.isBlank(vkstate) || !vkstate.equals(state)) {
@@ -259,43 +254,44 @@ public class SocialLogin {
c.setMaxAge(0);
response.addCookie(c);
}
- OAuth2AccessToken token = vkAuthService.getAccessToken(code);
+ OAuth2AccessToken token = vkService.getVkAuthService().getAccessToken(code);
OAuthRequest meRequest = new OAuthRequest(Verb.GET,
"https://api.vk.com/method/users.get?fields=screen_name&v=5.131");
- vkAuthService.signRequest(token, meRequest);
- Response vkResponse = vkAuthService.execute(meRequest);
- if (vkResponse.isSuccessful()) {
- String graph = vkResponse.getBody();
- com.juick.model.ext.vk.User jsonUser = jsonMapper.readValue(graph, UsersResponse.class).users().stream()
- .findFirst().orElseThrow(HttpBadRequestException::new);
- String vkName = jsonUser.firstName() + " " + jsonUser.lastName();
- String vkLink = jsonUser.screenName();
-
- if (vkName.length() == 1 || StringUtils.isBlank(vkLink)) {
- logger.error("vk user error");
- throw new HttpBadRequestException();
- }
+ vkService.getVkAuthService().signRequest(token, meRequest);
+ try (Response vkResponse = vkService.getVkAuthService().execute(meRequest)) {
+ if (vkResponse.isSuccessful()) {
+ String graph = vkResponse.getBody();
+ com.juick.model.ext.vk.User jsonUser = jsonMapper.readValue(graph, UsersResponse.class).users().stream()
+ .findFirst().orElseThrow(HttpBadRequestException::new);
+ String vkName = jsonUser.firstName() + " " + jsonUser.lastName();
+ String vkLink = jsonUser.screenName();
- long vkID = NumberUtils.toLong(jsonUser.id(), 0);
- int uid = userService.getUIDbyVKID(vkID);
- if (uid > 0) {
- userService.updateVkUser(vkID, token.getAccessToken(), vkName, vkLink);
- Cookie c = new Cookie("hash", userService.getHashByUID(uid));
- c.setMaxAge(50 * 24 * 60 * 60);
- response.addCookie(c);
- return "redirect:/" + Optional.ofNullable(referer).orElse(StringUtils.EMPTY);
- } else {
- String loginhash = UUID.randomUUID().toString();
- if (!userService.createVKUser(vkID, loginhash, token.getAccessToken(), vkName, vkLink)) {
- logger.error("create vk user error");
+ if (vkName.length() == 1 || StringUtils.isBlank(vkLink)) {
+ logger.error("vk user error");
throw new HttpBadRequestException();
}
- return "redirect:/signup?type=vk&hash=" + loginhash;
+
+ long vkID = NumberUtils.toLong(jsonUser.id(), 0);
+ int uid = userService.getUIDbyVKID(vkID);
+ if (uid > 0) {
+ userService.updateVkUser(vkID, token.getAccessToken(), vkName, vkLink);
+ Cookie c = new Cookie("hash", userService.getHashByUID(uid));
+ c.setMaxAge(50 * 24 * 60 * 60);
+ response.addCookie(c);
+ return "redirect:/" + Optional.ofNullable(referer).orElse(StringUtils.EMPTY);
+ } else {
+ String loginhash = UUID.randomUUID().toString();
+ if (!userService.createVKUser(vkID, loginhash, token.getAccessToken(), vkName, vkLink)) {
+ logger.error("create vk user error");
+ throw new HttpBadRequestException();
+ }
+ return "redirect:/signup?type=vk&hash=" + loginhash;
+ }
+ } else {
+ logger.error("vk error {}: {}", vkResponse.getCode(), vkResponse.getBody());
+ throw new HttpBadRequestException();
}
- } else {
- logger.error("vk error {}: {}", vkResponse.getCode(), vkResponse.getBody());
- throw new HttpBadRequestException();
}
}
@@ -375,35 +371,4 @@ public class SocialLogin {
}
throw new HttpBadRequestException();
}
-
- @Scheduled(fixedRate = 3600000)
- public void updatePremium() {
- userService.getVkTokens(List.of())
- .forEach(vkUser -> {
- var userId = userService.getUIDbyVKID(Long.parseLong(vkUser.getLeft()));
- if (userId > 0) {
- OAuth2AccessToken token = new OAuth2AccessToken(vkUser.getRight());
- OAuthRequest donRequest = new OAuthRequest(Verb.GET,
- "https://api.vk.com/method/donut.isDon?owner_id=-67669480&v=5.131");
- vkAuthService.signRequest(token, donRequest);
- try {
- Response vkResponse = vkAuthService.execute(donRequest);
- if (vkResponse.isSuccessful()) {
- logger.info(vkResponse.getBody());
- var response = jsonMapper.readTree(vkResponse.getBody());
- if (response.has("response")) {
- var isDon = response.get("response").intValue() > 0;
- logger.info("{} is Don: {}", vkUser.getLeft(), isDon);
- userService.setPremium(userId, isDon);
- } else {
- // token is expired or does not have "groups" permissions
- userService.updateVkToken(userId, "");
- }
- }
- } catch (Exception e) {
- logger.error("Don request error", e);
- }
- }
- });
- }
}