Skip to content

Commit

Permalink
consistently use FafTokenService in UserService
Browse files Browse the repository at this point in the history
  • Loading branch information
Brutus5000 committed Nov 3, 2017
1 parent 1bf788b commit c7bf63b
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 197 deletions.
22 changes: 20 additions & 2 deletions src/inttest/java/com/faforever/api/user/UserControllerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
import com.faforever.api.data.domain.User;
import com.faforever.api.email.EmailSender;
import com.faforever.api.error.ErrorCode;
import com.faforever.api.security.FafTokenService;
import com.faforever.api.security.FafTokenType;
import com.faforever.api.security.OAuthScope;
import com.google.common.collect.ImmutableMap;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.MockBean;
Expand All @@ -15,6 +18,8 @@
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.util.MultiValueMap;

import java.time.Duration;

import static junitx.framework.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
Expand All @@ -36,6 +41,9 @@ public class UserControllerTest extends AbstractIntegrationTest {
@MockBean
private EmailSender emailSender;

@Autowired
private FafTokenService fafTokenService;

@Autowired
private UserService userService;

Expand Down Expand Up @@ -74,7 +82,13 @@ public void registerWithAuthentication() throws Exception {
@Test
@WithAnonymousUser
public void activateWithSuccess() throws Exception {
String token = userService.createRegistrationToken(NEW_USER, NEW_EMAIL, NEW_PASSWORD);
String token = fafTokenService.createToken(FafTokenType.REGISTRATION,
Duration.ofSeconds(100),
ImmutableMap.of(
UserService.KEY_USERNAME, NEW_USER,
UserService.KEY_EMAIL, NEW_EMAIL,
UserService.KEY_PASSWORD, NEW_PASSWORD
));

mockMvc.perform(get("/users/activate?token=" + token))
.andExpect(status().isFound())
Expand Down Expand Up @@ -160,8 +174,12 @@ public void resetPasswordWithEmail() throws Exception {
@Test
@WithAnonymousUser
public void confirmPasswordReset() throws Exception {
String token = fafTokenService.createToken(FafTokenType.PASSWORD_RESET,
Duration.ofSeconds(100),
ImmutableMap.of(UserService.KEY_USER_ID, String.valueOf(1)));

MultiValueMap<String, String> params = new HttpHeaders();
params.add("token", userService.createPasswordResetToken(1));
params.add("token", token);
params.add("newPassword", NEW_PASSWORD);

mockMvc.perform(
Expand Down
88 changes: 22 additions & 66 deletions src/main/java/com/faforever/api/user/UserService.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,20 @@
import com.faforever.api.error.ErrorCode;
import com.faforever.api.player.PlayerRepository;
import com.faforever.api.security.FafPasswordEncoder;
import com.faforever.api.security.FafTokenService;
import com.faforever.api.security.FafTokenType;
import com.faforever.api.security.FafUserDetails;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableMap;
import com.google.common.hash.Hashing;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.jwt.JwtHelper;
import org.springframework.security.jwt.crypto.sign.MacSigner;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.HashMap;
import java.time.Duration;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Pattern;

Expand All @@ -33,35 +31,29 @@
@Service
@Slf4j
public class UserService {
static final String KEY_ACTION = "action";
static final String KEY_EXPIRY = "expiry";
static final String KEY_USERNAME = "username";
static final String KEY_EMAIL = "email";
static final String KEY_PASSWORD = "password";
static final String KEY_USER_ID = "id";
private static final Pattern USERNAME_PATTERN = Pattern.compile("[A-Za-z][A-Za-z0-9_-]{2,15}$");
private static final String ACTION_ACTIVATE = "activate";
private static final String ACTION_RESET_PASSWORD = "reset_password";
private final EmailService emailService;
private final PlayerRepository playerRepository;
private final UserRepository userRepository;
private final NameRecordRepository nameRecordRepository;
private final ObjectMapper objectMapper;
private final MacSigner macSigner;
private final FafApiProperties properties;
private final FafPasswordEncoder passwordEncoder;
private final AnopeUserRepository anopeUserRepository;
private final FafTokenService fafTokenService;

public UserService(EmailService emailService, PlayerRepository playerRepository, UserRepository userRepository,
NameRecordRepository nameRecordRepository, ObjectMapper objectMapper, FafApiProperties properties, AnopeUserRepository anopeUserRepository) {
NameRecordRepository nameRecordRepository, FafApiProperties properties, AnopeUserRepository anopeUserRepository, FafTokenService fafTokenService) {
this.emailService = emailService;
this.playerRepository = playerRepository;
this.userRepository = userRepository;
this.nameRecordRepository = nameRecordRepository;
this.objectMapper = objectMapper;
this.macSigner = new MacSigner(properties.getJwt().getSecret());
this.properties = properties;
this.anopeUserRepository = anopeUserRepository;
this.fafTokenService = fafTokenService;
this.passwordEncoder = new FafPasswordEncoder();
}

Expand All @@ -80,7 +72,14 @@ void register(String username, String email, String password) {
throw new ApiException(new Error(ErrorCode.USERNAME_RESERVED, username, usernameReservationTimeInMonths));
});

String token = createRegistrationToken(username, email, passwordEncoder.encode(password));
String token = fafTokenService.createToken(FafTokenType.REGISTRATION,
Duration.ofSeconds(properties.getRegistration().getLinkExpirationSeconds()),
ImmutableMap.of(
KEY_USERNAME, username,
KEY_EMAIL, email,
KEY_PASSWORD, passwordEncoder.encode(password)
));

String activationUrl = String.format(properties.getRegistration().getActivationUrlFormat(), token);

emailService.sendActivationMail(username, email, activationUrl);
Expand All @@ -95,22 +94,6 @@ private void validateUsername(String username) {
}
}

@SneakyThrows
@VisibleForTesting
String createRegistrationToken(String username, String email, String passwordHash) {
long expirationSeconds = properties.getRegistration().getLinkExpirationSeconds();

String claim = objectMapper.writeValueAsString(ImmutableMap.of(
KEY_ACTION, ACTION_ACTIVATE,
KEY_EXPIRY, Instant.now().plusSeconds(expirationSeconds).toString(),
KEY_USERNAME, username,
KEY_EMAIL, email,
KEY_PASSWORD, passwordHash
));

return JwtHelper.encode(claim, macSigner).getEncoded();
}

/**
* Creates a new user based on the information in the activation token.
*
Expand All @@ -126,15 +109,7 @@ String createRegistrationToken(String username, String email, String passwordHas
@SuppressWarnings("unchecked")
@Transactional
void activate(String token) {
HashMap<String, String> claims = objectMapper.readValue(JwtHelper.decodeAndVerify(token, macSigner).getClaims(), HashMap.class);

String action = claims.get(KEY_ACTION);
if (!Objects.equals(action, ACTION_ACTIVATE)) {
throw new ApiException(new Error(ErrorCode.TOKEN_INVALID));
}
if (Instant.parse(claims.get(KEY_EXPIRY)).isBefore(Instant.now())) {
throw new ApiException(new Error(ErrorCode.TOKEN_EXPIRED));
}
Map<String, String> claims = fafTokenService.resolveToken(FafTokenType.REGISTRATION, token);

String username = claims.get(KEY_USERNAME);
String email = claims.get(KEY_EMAIL);
Expand Down Expand Up @@ -190,39 +165,20 @@ void resetPassword(String identifier) {
.orElseGet(() -> userRepository.findOneByEmailIgnoreCase(identifier)
.orElseThrow(() -> new ApiException(new Error(ErrorCode.UNKNOWN_IDENTIFIER))));

String token = createPasswordResetToken(user.getId());
String token = fafTokenService.createToken(FafTokenType.PASSWORD_RESET,
Duration.ofSeconds(properties.getRegistration().getLinkExpirationSeconds()),
ImmutableMap.of(KEY_USER_ID, String.valueOf(user.getId())));

String passwordResetUrl = String.format(properties.getPasswordReset().getPasswordResetUrlFormat(), token);

emailService.sendPasswordResetMail(user.getLogin(), user.getEmail(), passwordResetUrl);
}

@SneakyThrows
@VisibleForTesting
String createPasswordResetToken(int userId) {
long expirationSeconds = properties.getRegistration().getLinkExpirationSeconds();

String claim = objectMapper.writeValueAsString(ImmutableMap.of(
KEY_ACTION, ACTION_RESET_PASSWORD,
KEY_EXPIRY, Instant.now().plusSeconds(expirationSeconds).toString(),
KEY_USER_ID, userId
));

return JwtHelper.encode(claim, macSigner).getEncoded();
}

@SneakyThrows
void claimPasswordResetToken(String token, String newPassword) {
HashMap claims = objectMapper.readValue(JwtHelper.decodeAndVerify(token, macSigner).getClaims(), HashMap.class);

String action = (String) claims.get(KEY_ACTION);
if (!Objects.equals(action, ACTION_RESET_PASSWORD)) {
throw new ApiException(new Error(ErrorCode.TOKEN_INVALID));
}
if (Instant.parse((String) claims.get(KEY_EXPIRY)).isBefore(Instant.now())) {
throw new ApiException(new Error(ErrorCode.TOKEN_EXPIRED));
}
Map<String, String> claims = fafTokenService.resolveToken(FafTokenType.PASSWORD_RESET, token);

int userId = (Integer) claims.get(KEY_USER_ID);
int userId = Integer.parseInt(claims.get(KEY_USER_ID));
User user = userRepository.findOne(userId);

if (user == null) {
Expand Down
Loading

0 comments on commit c7bf63b

Please sign in to comment.