Skip to content

Commit 61f1f5b

Browse files
deepakdeshekarvivek-gofynd
andauthored
Feature/redis get ttl (#53)
* Scopes fixed * Extension clear invalid cookies * MultiLevel storage support --------- Co-authored-by: vivek-gofynd <vivekprajapati@fynd.com>
1 parent 10c1308 commit 61f1f5b

File tree

7 files changed

+309
-28
lines changed

7 files changed

+309
-28
lines changed

README.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ FDK Extension Helper Library
3434
ext :
3535
api_key : <API_KEY>
3636
api_secret : <API_SECRET>
37-
scope : 'company/saleschannel'
3837
base_url : 'https://test.extension.com'
3938
access_mode : 'offline'
4039

@@ -271,7 +270,6 @@ Webhook events can be helpful to handle tasks when certain events occur on platf
271270
ext :
272271
api_key : <API_KEY>
273272
api_secret : <API_SECRET>
274-
scope : ""
275273
base_url : "https://test.extension.com"
276274
access_mode : "offline"
277275
webhook:
@@ -402,7 +400,6 @@ A filter and reducer can be provided to refine the data delivered for each subsc
402400
ext :
403401
api_key : <API_KEY>
404402
api_secret : <API_SECRET>
405-
scope : ""
406403
base_url : "https://test.extension.com"
407404
access_mode : "offline"
408405
webhook:

pom.xml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
</parent>
1010
<groupId>com.fynd</groupId>
1111
<artifactId>fynd-extension-java</artifactId>
12-
<version>1.0.0</version>
12+
<version>1.1.0</version>
1313
<name>fynd-extension-java</name>
1414
<description>Java Fynd Extension Library</description>
1515
<properties>
@@ -56,6 +56,12 @@
5656
<version>${jedis.version}</version>
5757
</dependency>
5858

59+
<dependency>
60+
<groupId>org.mongodb</groupId>
61+
<artifactId>mongodb-driver-sync</artifactId>
62+
<version>4.3.1</version>
63+
</dependency>
64+
5965
<dependency>
6066
<groupId>commons-validator</groupId>
6167
<artifactId>commons-validator</artifactId>

src/main/java/com/fynd/extension/controllers/ExtensionADMController.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,10 +120,11 @@ public ResponseEntity<?> authorize(@RequestParam(value = "organization_id") Stri
120120
if (StringUtils.isNotEmpty(sessionIdForOrganization)) {
121121
Session fdkSession = sessionStorage.getSession(sessionIdForOrganization);
122122
if (Objects.isNull(fdkSession)) {
123+
Extension.clearInvalidCookie(FdkConstants.ADMIN_SESSION_COOKIE_NAME, response);
123124
throw new FdkSessionNotFound("Can not complete oauth process as session not found");
124125
}
125-
if (!fdkSession.getState()
126-
.equalsIgnoreCase(state)) {
126+
if (!fdkSession.getState().equalsIgnoreCase(state)) {
127+
Extension.clearInvalidCookie(FdkConstants.ADMIN_SESSION_COOKIE_NAME, response);
127128
throw new FdkInvalidOAuth("Invalid oauth call");
128129
}
129130
PartnerConfig partnerConfig = ext.getPartnerConfig(fdkSession.getOrganizationId());

src/main/java/com/fynd/extension/controllers/ExtensionController.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ public ResponseEntity<?> install(@RequestParam(value = "company_id") String comp
9999
authCallback += "?application_id=" + applicationId;
100100
}
101101
String redirectUrl = platformConfig.getPlatformOauthClient()
102-
.getAuthorizationURL(session.getScope(), authCallback,
102+
.getAuthorizationURL(ext.getExtensionProperties().getScopes(), authCallback,
103103
session.getState(),
104104
true); // Always generate online mode token for extension launch
105105
sessionStorage.saveSession(session);
@@ -129,10 +129,11 @@ public ResponseEntity<?> authorize(@RequestParam(value = "company_id") String co
129129
if (StringUtils.isNotEmpty(sessionIdForCompany)) {
130130
Session fdkSession = sessionStorage.getSession(sessionIdForCompany);
131131
if (Objects.isNull(fdkSession)) {
132+
Extension.clearInvalidCookie(FdkConstants.SESSION_COOKIE_NAME + DELIMITER + companyId, response);
132133
throw new FdkSessionNotFound("Can not complete oauth process as session not found");
133134
}
134-
if (!fdkSession.getState()
135-
.equalsIgnoreCase(state)) {
135+
if (!fdkSession.getState().equalsIgnoreCase(state)) {
136+
Extension.clearInvalidCookie(FdkConstants.SESSION_COOKIE_NAME + DELIMITER + companyId, response);
136137
throw new FdkInvalidOAuth("Invalid oauth call");
137138
}
138139
PlatformConfig platformConfig = ext.getPlatformConfig(fdkSession.getCompanyId());
@@ -161,7 +162,7 @@ public ResponseEntity<?> authorize(@RequestParam(value = "company_id") String co
161162
session = new Session(sid, true);
162163
}
163164
AccessTokenDto offlineTokenRes = platformConfig.getPlatformOauthClient()
164-
.getOfflineAccessToken(null, code);
165+
.getOfflineAccessToken(String.join(",", ext.getExtensionProperties().getScopes()), code);
165166
session.setCompanyId(companyId);
166167
session.setState(fdkSession.getState());
167168
session.setExtensionId(ext.getExtensionProperties()

src/main/java/com/fynd/extension/model/Extension.java

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,16 @@
2626
import okhttp3.Interceptor;
2727
import org.apache.commons.lang3.StringUtils;
2828
import org.springframework.beans.factory.annotation.Value;
29-
import org.springframework.util.CollectionUtils;
29+
import org.springframework.http.HttpHeaders;
30+
import org.springframework.http.ResponseCookie;
3031
import org.springframework.util.ObjectUtils;
3132
import retrofit2.Response;
3233

3334
import jakarta.servlet.http.Cookie;
35+
import jakarta.servlet.http.HttpServletResponse;
3436
import java.io.IOException;
3537
import java.net.URL;
3638
import java.util.*;
37-
import java.util.stream.Collectors;
3839

3940
import static com.fynd.extension.controllers.ExtensionController.Fields.DELIMITER;
4041

@@ -190,15 +191,15 @@ public String getCookieValue(Cookie[] cookies) {
190191
return StringUtils.EMPTY;
191192
}
192193

193-
private static void verifyScopes(List<String> scopeList, ExtensionDetailsDTO extensionDetailsDTO) {
194-
List<String> missingScopes = scopeList.stream()
195-
.filter(val -> !extensionDetailsDTO.getScope()
196-
.contains(val))
197-
.collect(Collectors.toList());
198-
if (CollectionUtils.isEmpty(scopeList) || !missingScopes.isEmpty()) {
199-
throw new FdkInvalidExtensionConfig(
200-
"Invalid scopes in extension config. Invalid scopes : " + missingScopes);
201-
}
194+
public static void clearInvalidCookie(String cookieName, HttpServletResponse response) {
195+
ResponseCookie resCookie = ResponseCookie.from(cookieName, "")
196+
.httpOnly(true)
197+
.sameSite("None")
198+
.secure(true)
199+
.path("/")
200+
.maxAge(0)
201+
.build();
202+
response.addHeader(HttpHeaders.SET_COOKIE, resCookie.toString());
202203
}
203204

204205
public String getAuthCallback() {
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
package com.fynd.extension.storage;
2+
3+
import redis.clients.jedis.*;
4+
import com.mongodb.client.*;
5+
import org.bson.Document;
6+
import com.mongodb.client.model.IndexOptions;
7+
import com.mongodb.client.model.ReplaceOptions;
8+
9+
import java.util.Date;
10+
import java.util.List;
11+
import java.util.Map;
12+
import java.util.concurrent.TimeUnit;
13+
14+
public class MultiLevelStorage extends BaseStorage {
15+
16+
private boolean isClusterMode;
17+
private JedisPool jedisPool;
18+
private JedisCluster jedisCluster;
19+
private String prefixKey;
20+
private JedisSentinelPool jedisSentinelPool;
21+
private MongoCollection<Document> mongoCollection;
22+
private static final String DEFAULT_COLLECTION_NAME = "fdk_ext_acc_tokens";
23+
24+
public MultiLevelStorage(JedisPool jedisPool, MongoDatabase mongoDatabase, String prefixKey, Map<String, String> options) {
25+
super(prefixKey);
26+
String collectionName = options.getOrDefault("collectionName", DEFAULT_COLLECTION_NAME);
27+
this.jedisPool = jedisPool;
28+
this.mongoCollection = mongoDatabase.getCollection(collectionName);
29+
this.prefixKey = prefixKey;
30+
this.isClusterMode = false;
31+
ensureTTLIndex();
32+
}
33+
34+
public MultiLevelStorage(JedisCluster jedisCluster, MongoDatabase mongoDatabase, String prefixKey, Map<String, String> options) {
35+
super(prefixKey);
36+
String collectionName = options.getOrDefault("collectionName", DEFAULT_COLLECTION_NAME);
37+
this.jedisCluster = jedisCluster;
38+
this.mongoCollection = mongoDatabase.getCollection(collectionName);
39+
this.prefixKey = prefixKey;
40+
this.isClusterMode = true;
41+
ensureTTLIndex();
42+
}
43+
44+
public MultiLevelStorage(JedisSentinelPool jedisSentinelPool, MongoDatabase mongoDatabase, String prefixKey, Map<String, String> options) {
45+
super(prefixKey);
46+
String collectionName = options.getOrDefault("collectionName", DEFAULT_COLLECTION_NAME);
47+
this.jedisSentinelPool = jedisSentinelPool;
48+
this.mongoCollection = mongoDatabase.getCollection(collectionName);
49+
this.prefixKey = prefixKey;
50+
this.isClusterMode = false;
51+
ensureTTLIndex();
52+
}
53+
54+
@Override
55+
public String get(String key) {
56+
String redisKey = generateKey(key);
57+
String value = fetchFromRedis(redisKey);
58+
if (value == null) {
59+
value = fetchFromMongo(redisKey);
60+
if (value != null) {
61+
set(key, value);
62+
}
63+
}
64+
return value;
65+
}
66+
67+
@Override
68+
public String set(String key, String value) {
69+
String redisKey = generateKey(key);
70+
storeInMongo(redisKey, value);
71+
return storeInRedis(redisKey, value);
72+
}
73+
74+
@Override
75+
public Long del(String key) {
76+
String redisKey = generateKey(key);
77+
deleteFromMongo(redisKey);
78+
return deleteFromRedis(redisKey);
79+
}
80+
81+
@Override
82+
public String setex(String key, int ttl, String value) {
83+
String redisKey = generateKey(key);
84+
storeInMongo(redisKey, value, ttl);
85+
return storeInRedisWithTTL(redisKey, ttl, value);
86+
}
87+
88+
private String generateKey(String key) {
89+
return super.prefixKey + key;
90+
}
91+
92+
private void ensureTTLIndex() {
93+
List<Document> indexes = mongoCollection.listIndexes().into(new java.util.ArrayList<>());
94+
boolean ttlIndexExists = indexes.stream()
95+
.anyMatch(index -> index.get("key", Document.class).containsKey("expireAt") &&
96+
index.containsKey("expireAfterSeconds"));
97+
if (!ttlIndexExists) {
98+
mongoCollection.createIndex(new Document("expireAt", 1), new IndexOptions().expireAfter(0L, TimeUnit.SECONDS));
99+
}
100+
}
101+
102+
private String fetchFromMongo(String key) {
103+
Document doc = mongoCollection.find(new Document("key", key)).first();
104+
if (doc != null) {
105+
Date expireAt = doc.getDate("expireAt");
106+
if (expireAt != null && expireAt.getTime() < System.currentTimeMillis()) {
107+
deleteFromMongo(key);
108+
return null;
109+
}
110+
return doc.getString("value");
111+
}
112+
return null;
113+
}
114+
115+
private void storeInMongo(String key, String value, int ttl) {
116+
Date now = new Date();
117+
Date expireAt = new Date(now.getTime() + (ttl * 1000L));
118+
Document doc = new Document("key", key)
119+
.append("value", value)
120+
.append("updatedAt", now)
121+
.append("expireAt", expireAt);
122+
mongoCollection.replaceOne(new Document("key", key), doc, new ReplaceOptions().upsert(true));
123+
}
124+
125+
private void storeInMongo(String key, String value) {
126+
Date now = new Date();
127+
Document doc = new Document("key", key)
128+
.append("value", value)
129+
.append("updatedAt", now);
130+
mongoCollection.replaceOne(new Document("key", key), doc, new ReplaceOptions().upsert(true));
131+
}
132+
133+
private String fetchFromRedis(String key) {
134+
if (isClusterMode) {
135+
return jedisCluster.get(key);
136+
} else if (jedisSentinelPool != null) {
137+
try (Jedis jedis = jedisSentinelPool.getResource()) {
138+
return jedis.get(key);
139+
}
140+
} else {
141+
try (Jedis jedis = jedisPool.getResource()) {
142+
return jedis.get(key);
143+
}
144+
}
145+
}
146+
147+
private String storeInRedis(String key, String value) {
148+
if (isClusterMode) {
149+
return jedisCluster.set(key, value);
150+
} else if (jedisSentinelPool != null) {
151+
try (Jedis jedis = jedisSentinelPool.getResource()) {
152+
return jedis.set(key, value);
153+
}
154+
} else {
155+
try (Jedis jedis = jedisPool.getResource()) {
156+
return jedis.set(key, value);
157+
}
158+
}
159+
}
160+
161+
private String storeInRedisWithTTL(String key, int ttl, String value) {
162+
if (isClusterMode) {
163+
return jedisCluster.setex(key, ttl, value);
164+
} else if (jedisSentinelPool != null) {
165+
try (Jedis jedis = jedisSentinelPool.getResource()) {
166+
return jedis.setex(key, ttl, value);
167+
}
168+
} else {
169+
try (Jedis jedis = jedisPool.getResource()) {
170+
return jedis.setex(key, ttl, value);
171+
}
172+
}
173+
}
174+
175+
private Long deleteFromRedis(String key) {
176+
if (isClusterMode) {
177+
return jedisCluster.del(key);
178+
} else if (jedisSentinelPool != null) {
179+
try (Jedis jedis = jedisSentinelPool.getResource()) {
180+
return jedis.del(key);
181+
}
182+
} else {
183+
try (Jedis jedis = jedisPool.getResource()) {
184+
return jedis.del(key);
185+
}
186+
}
187+
}
188+
189+
private void deleteFromMongo(String key) {
190+
mongoCollection.deleteOne(new Document("key", key));
191+
}
192+
}

0 commit comments

Comments
 (0)