Clear sessions on password reset (#9955)
Signed-off-by: Shrikant Sharat Kandula <shrikant@appsmith.com> Co-authored-by: Arpit Mohan <mohanarpit@users.noreply.github.com> Co-authored-by: Nayan <nayan@appsmith.com>
This commit is contained in:
parent
cc3ab115b5
commit
d3d1f8bbf9
|
|
@ -9,7 +9,9 @@ import org.springframework.data.redis.core.ReactiveRedisOperations;
|
|||
import org.springframework.data.redis.core.ReactiveRedisTemplate;
|
||||
import org.springframework.data.redis.listener.ChannelTopic;
|
||||
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
|
||||
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
|
||||
import org.springframework.data.redis.serializer.RedisSerializationContext;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
import org.springframework.session.data.redis.config.annotation.web.server.EnableRedisWebSession;
|
||||
|
||||
|
|
@ -42,4 +44,17 @@ public class RedisConfig {
|
|||
|
||||
return new ReactiveRedisTemplate<>(factory, context);
|
||||
}
|
||||
|
||||
// Lifted from below and turned it into a bean. Wish Spring provided it as a bean.
|
||||
// RedisWebSessionConfiguration.createReactiveRedisTemplate
|
||||
@Bean
|
||||
ReactiveRedisTemplate<String, Object> reactiveRedisTemplate(ReactiveRedisConnectionFactory factory) {
|
||||
RedisSerializer<String> keySerializer = new StringRedisSerializer();
|
||||
RedisSerializer<Object> defaultSerializer = new JdkSerializationRedisSerializer(getClass().getClassLoader());
|
||||
RedisSerializationContext<String, Object> serializationContext = RedisSerializationContext
|
||||
.<String, Object>newSerializationContext(defaultSerializer).key(keySerializer).hashKey(keySerializer)
|
||||
.build();
|
||||
return new ReactiveRedisTemplate<>(factory, serializationContext);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,13 +3,16 @@ package com.appsmith.server.services;
|
|||
import com.appsmith.server.repositories.UserRepository;
|
||||
import com.appsmith.server.services.ce.SessionUserServiceCEImpl;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.redis.core.ReactiveRedisOperations;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class SessionUserServiceImpl extends SessionUserServiceCEImpl implements SessionUserService {
|
||||
|
||||
public SessionUserServiceImpl(UserRepository userRepository) {
|
||||
super(userRepository);
|
||||
public SessionUserServiceImpl(
|
||||
UserRepository userRepository,
|
||||
ReactiveRedisOperations<String, Object> redisOperations) {
|
||||
super(userRepository, redisOperations);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,4 +10,5 @@ public interface SessionUserServiceCE {
|
|||
|
||||
Mono<User> refreshCurrentUser(ServerWebExchange exchange);
|
||||
|
||||
Mono<Void> logoutAllSessions(String email);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,9 +3,12 @@ package com.appsmith.server.services.ce;
|
|||
import com.appsmith.server.domains.User;
|
||||
import com.appsmith.server.exceptions.AppsmithError;
|
||||
import com.appsmith.server.exceptions.AppsmithException;
|
||||
import com.appsmith.server.helpers.CollectionUtils;
|
||||
import com.appsmith.server.repositories.UserRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.data.redis.core.ReactiveRedisOperations;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
||||
|
|
@ -14,6 +17,7 @@ import org.springframework.security.oauth2.client.authentication.OAuth2Authentic
|
|||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebSession;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.util.function.Tuple2;
|
||||
|
||||
import static org.springframework.security.web.server.context.WebSessionServerSecurityContextRepository.DEFAULT_SPRING_SECURITY_CONTEXT_ATTR_NAME;
|
||||
|
||||
|
|
@ -22,6 +26,7 @@ import static org.springframework.security.web.server.context.WebSessionServerSe
|
|||
public class SessionUserServiceCEImpl implements SessionUserServiceCE {
|
||||
|
||||
private final UserRepository userRepository;
|
||||
private final ReactiveRedisOperations<String, Object> redisOperations;
|
||||
|
||||
@Override
|
||||
public Mono<User> getCurrentUser() {
|
||||
|
|
@ -60,4 +65,33 @@ public class SessionUserServiceCEImpl implements SessionUserServiceCE {
|
|||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> logoutAllSessions(String email) {
|
||||
// This pattern string comes from calling `ReactiveRedisSessionRepository.getSessionKey("*")` private method.
|
||||
return redisOperations.keys("spring:session:sessions:*")
|
||||
.flatMap(key -> Mono.zip(
|
||||
Mono.just(key),
|
||||
// The values are maps, containing various pieces of session related information.
|
||||
// One of them, holds the serialized User object. We want just that.
|
||||
redisOperations.opsForHash().entries(key)
|
||||
.filter(e -> e.getValue() != null &&
|
||||
("sessionAttr:" + DEFAULT_SPRING_SECURITY_CONTEXT_ATTR_NAME).equals(e.getKey())
|
||||
)
|
||||
.map(e -> (User) ((SecurityContext) e.getValue()).getAuthentication().getPrincipal())
|
||||
.next()
|
||||
))
|
||||
// Now we have tuples of session keys, and the corresponding user objects.
|
||||
// Filter the ones we need to clear out.
|
||||
.filter(tuple -> StringUtils.equalsIgnoreCase(email, tuple.getT2().getEmail()))
|
||||
.map(Tuple2::getT1)
|
||||
.collectList()
|
||||
.flatMap(keys ->
|
||||
CollectionUtils.isNullOrEmpty(keys)
|
||||
? Mono.just(0L)
|
||||
: redisOperations.delete(keys.toArray(String[]::new))
|
||||
)
|
||||
.doOnError(error -> log.error("Error clearing user sessions", error))
|
||||
.then();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ import reactor.core.Exceptions;
|
|||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.scheduler.Scheduler;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
|
||||
import javax.validation.Validator;
|
||||
import java.net.URLEncoder;
|
||||
|
|
@ -368,6 +369,12 @@ public class UserServiceCEImpl extends BaseService<UserRepository, User, String>
|
|||
)))
|
||||
.flatMap(passwordResetTokenRepository::delete)
|
||||
.then(repository.save(userFromDb))
|
||||
.doOnSuccess(result ->
|
||||
// In a separate thread, we delete all other sessions of this user.
|
||||
sessionUserService.logoutAllSessions(userFromDb.getEmail())
|
||||
.subscribeOn(Schedulers.boundedElastic())
|
||||
.subscribe()
|
||||
)
|
||||
.thenReturn(true);
|
||||
}));
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user