Skip to content

Commit

Permalink
Merge pull request #561 from ita-social-projects/develop
Browse files Browse the repository at this point in the history
refresh_token_logic
  • Loading branch information
fortamt authored Aug 9, 2022
2 parents 90e229f + 49fe9c1 commit fd1d97c
Show file tree
Hide file tree
Showing 16 changed files with 463 additions and 146 deletions.
3 changes: 0 additions & 3 deletions .idea/codeStyles/Project.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
package com.softserveinc.dokazovi.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;

import java.util.ArrayList;
import java.util.List;

@ConfigurationProperties(prefix = "app")
public class AppProperties {

private final Auth auth = new Auth();
private final OAuth2 oauth2 = new OAuth2();

public static class Auth {

private String tokenSecret;
private long tokenExpirationMsec;
@Value("${app.auth.tokenExpirationMsec}")
private Long tokenExpirationMsec;
@Value("${app.auth.refreshTokenExpirationMsec}")
private Long refreshTokenExpirationMsec;

public String getTokenSecret() {
return tokenSecret;
Expand All @@ -29,9 +35,18 @@ public long getTokenExpirationMsec() {
public void setTokenExpirationMsec(long tokenExpirationMsec) {
this.tokenExpirationMsec = tokenExpirationMsec;
}

public long getRefreshTokenExpirationMsec() {
return refreshTokenExpirationMsec;
}

public void setRefreshTokenExpirationMsec(long refreshTokenExpirationMsec) {
this.refreshTokenExpirationMsec = refreshTokenExpirationMsec;
}
}

public static final class OAuth2 {

private List<String> authorizedRedirectUris = new ArrayList<>();

public List<String> getAuthorizedRedirectUris() {
Expand Down
100 changes: 67 additions & 33 deletions src/main/java/com/softserveinc/dokazovi/controller/AuthController.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,17 @@

import com.softserveinc.dokazovi.dto.payload.AuthResponse;
import com.softserveinc.dokazovi.dto.payload.LoginRequest;
import com.softserveinc.dokazovi.dto.payload.RefreshToken;
import com.softserveinc.dokazovi.dto.payload.RefreshTokenRequest;
import com.softserveinc.dokazovi.entity.UserEntity;
import com.softserveinc.dokazovi.exception.BadRequestException;
import com.softserveinc.dokazovi.exception.TokenRefreshException;
import com.softserveinc.dokazovi.security.TokenProvider;
import com.softserveinc.dokazovi.security.UserPrincipal;
import com.softserveinc.dokazovi.service.ProviderService;
import com.softserveinc.dokazovi.service.UserService;
import com.softserveinc.dokazovi.service.impl.MailSenderServiceImpl;
import com.softserveinc.dokazovi.security.RefreshTokenService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
Expand All @@ -19,10 +24,15 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;

import java.time.Instant;

import static com.softserveinc.dokazovi.controller.EndPoints.AUTH;
import static com.softserveinc.dokazovi.controller.EndPoints.AUTH_LOGIN;
import static com.softserveinc.dokazovi.controller.EndPoints.REFRESH_TOKEN;

/**
* The Auth controller responsible for handling requests for authentication.
Expand All @@ -32,38 +42,62 @@
@RequiredArgsConstructor
public class AuthController {

private final AuthenticationManager authenticationManager;
private final TokenProvider tokenProvider;
private final MailSenderServiceImpl mailSenderServiceImpl;
private final UserService userService;
private final ProviderService providerService;
private final AuthenticationManager authenticationManager;
private final TokenProvider tokenProvider;
private final MailSenderServiceImpl mailSenderServiceImpl;
private final UserService userService;
private final ProviderService providerService;
private final RefreshTokenService refreshTokenService;

/**
* Authenticates user using email and password.
*
* <p>Creates new authentication token for user. Checks if user has confirmed email (enabled == true),
* if not - throws BadRequestException to confirm it.</p>
*
* @param loginRequest data class that stores user email and password
* @return authorizes user and sets access token
*/
@PostMapping(AUTH_LOGIN)
public ResponseEntity<AuthResponse> authenticateUser(@Valid @RequestBody LoginRequest loginRequest,
HttpServletResponse response) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
loginRequest.getEmail(),
loginRequest.getPassword()
)
);
SecurityContextHolder.getContext().setAuthentication(authentication);
UserEntity userEntity = userService.findByEmail(loginRequest.getEmail());
if (!userEntity.getEnabled()) {
throw new BadRequestException("Please confirm your email!");
} else {
RefreshToken refreshToken = refreshTokenService.createRefreshToken(userEntity.getId());
Cookie refreshTokenCookie = new Cookie("refreshToken", refreshToken.getToken());
refreshTokenCookie.setMaxAge(
Math.toIntExact(refreshToken.getExpiryDate().getEpochSecond() - Instant.now().getEpochSecond()));
refreshTokenCookie.setSecure(true);
refreshTokenCookie.setHttpOnly(true);
response.addCookie(refreshTokenCookie);
String token = tokenProvider.createToken(authentication);
AuthResponse authResponse = new AuthResponse(token, refreshToken.getToken());
authResponse.setAccessToken(token);
return ResponseEntity.ok(authResponse);
}
}

@PostMapping(REFRESH_TOKEN)
public ResponseEntity<AuthResponse> refreshToken(@Valid @RequestBody RefreshTokenRequest request) {
String requestRefreshToken = request.getRefreshToken();

/**
* Authenticates user using email and password.
*
* <p>Creates new authentication token for user. Checks if user has confirmed email (enabled == true),
* if not - throws BadRequestException to confirm it.</p>
*
* @param loginRequest data class that stores user email and password
* @return authorizes user and sets access token
*/
@PostMapping(AUTH_LOGIN)
public ResponseEntity<AuthResponse> authenticateUser(@Valid @RequestBody LoginRequest loginRequest) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
loginRequest.getEmail(),
loginRequest.getPassword()
)
);
SecurityContextHolder.getContext().setAuthentication(authentication);
String token = tokenProvider.createToken(authentication);
UserEntity userEntity = userService.findByEmail(loginRequest.getEmail());
if (!userEntity.getEnabled()) {
throw new BadRequestException("Please confirm your email!");
} else {
AuthResponse authResponse = new AuthResponse(token);
authResponse.setAccessToken(token);
return ResponseEntity.ok(authResponse);
}
}
return refreshTokenService.findByToken(requestRefreshToken)
.map(refreshTokenService::verifyExpiration)
.map(RefreshToken::getUser)
.map(user -> {
String token = tokenProvider.createToken(UserPrincipal.create(user));
return ResponseEntity.ok(new AuthResponse(token, requestRefreshToken));
})
.orElseThrow(() -> new TokenRefreshException(requestRefreshToken,
"Refresh token is not in database!"));
}
}
107 changes: 54 additions & 53 deletions src/main/java/com/softserveinc/dokazovi/controller/EndPoints.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,58 +9,59 @@
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class EndPoints {

public static final String VERSION = "/version";
public static final String POST = "/post";
public static final String POST_TYPE = "/type";
public static final String POST_LATEST = "/latest-all";
public static final String POST_LATEST_BY_POST_TYPES_AND_ORIGINS = "/latest";
public static final String POST_LATEST_BY_POST_TYPES_AND_ORIGINS_FOR_MOBILE = "/latestMobile";
public static final String POST_IMPORTANT = "/important";
public static final String POST_SET_IMPORTANT = "/set-important";
public static final String POST_SET_FAKE_VIEW = "/set-fake-view/{postId}";
public static final String POST_GET_POST_BY_ID = "/{postId}";
public static final String POST_GET_POST_BY_AUTHOR_ID_AND_DIRECTIONS = "/by-authorid-and-directions";
public static final String POST_ALL_POSTS = "/all-posts";
public static final String USER = "/user";
public static final String USER_RESET_PASSWORD = "/reset-password";
public static final String USER_CHANGE_PASSWORD = "/change-password";
public static final String USER_UPDATE_PASSWORD = "/update-password";
public static final String USER_CHECK_TOKEN = "/check-token";
public static final String USER_RANDOM_EXPERTS = "/random-experts";
public static final String USER_ALL_EXPERTS = "/all-experts";
public static final String USER_GET_USER_BY_ID = "/{userId}";
public static final String USER_GET_AUTHORITIES = "/get-authorities";
public static final String POST_GET_USER_BY_ID = "/{userId}";
public static final String USER_GET_CURRENT_USER = "/me";
public static final String TAG = "/tag";
public static final String TAG_FIND_BY_VALUE = "/find-by-value";
public static final String POST_LATEST_BY_DIRECTION = "/latest-by-direction";
public static final String POST_LATEST_BY_EXPERT = "/latest-by-expert";
public static final String POST_LATEST_BY_EXPERT_AND_STATUS = "/latest-by-expert-and-status";
public static final String POST_VIEW_COUNT = "/post-view-count";
public static final String POST_FAKE_VIEW_COUNT = "/post-fake-view-count";
public static final String DIRECTION = "/direction";
public static final String ORIGIN = "/origin";
public static final String REGION = "/region";
public static final String AUTH = "/auth";
public static final String AUTH_VERIFICATION = "/verification";
public static final String AUTH_LOGIN = "/login";
public static final String AUTH_SIGNUP = "/signup";
public static final String POST_TYPES = "/post-types";
public static final String POST_TYPES_ALL_TYPES_BY_USER = "/{userId}";
public static final String BY_USER_ENDPOINT = "/{userId}";
public static final String PLATFORM_INFORMATION = "/platform-information";
public static final String PLATFORM_INFORMATION_BY_ID = "/{infoId}";
public static final String POST_GET_BY_IMPORTANT_IMAGE = "/get-by-important-image";
public static final String USER_EXPERT_ALL_POST_DIRECTIONS = "/experts/{expertId}/post-directions";
public static final String VERSION = "/version";
public static final String POST = "/post";
public static final String POST_TYPE = "/type";
public static final String POST_LATEST = "/latest-all";
public static final String POST_LATEST_BY_POST_TYPES_AND_ORIGINS = "/latest";
public static final String POST_LATEST_BY_POST_TYPES_AND_ORIGINS_FOR_MOBILE = "/latestMobile";
public static final String POST_IMPORTANT = "/important";
public static final String POST_SET_IMPORTANT = "/set-important";
public static final String POST_SET_FAKE_VIEW = "/set-fake-view/{postId}";
public static final String POST_GET_POST_BY_ID = "/{postId}";
public static final String POST_GET_POST_BY_AUTHOR_ID_AND_DIRECTIONS = "/by-authorid-and-directions";
public static final String POST_ALL_POSTS = "/all-posts";
public static final String USER = "/user";
public static final String USER_RESET_PASSWORD = "/reset-password";
public static final String USER_CHANGE_PASSWORD = "/change-password";
public static final String USER_UPDATE_PASSWORD = "/update-password";
public static final String USER_CHECK_TOKEN = "/check-token";
public static final String USER_RANDOM_EXPERTS = "/random-experts";
public static final String USER_ALL_EXPERTS = "/all-experts";
public static final String USER_GET_USER_BY_ID = "/{userId}";
public static final String USER_GET_AUTHORITIES = "/get-authorities";
public static final String POST_GET_USER_BY_ID = "/{userId}";
public static final String USER_GET_CURRENT_USER = "/me";
public static final String TAG = "/tag";
public static final String TAG_FIND_BY_VALUE = "/find-by-value";
public static final String POST_LATEST_BY_DIRECTION = "/latest-by-direction";
public static final String POST_LATEST_BY_EXPERT = "/latest-by-expert";
public static final String POST_LATEST_BY_EXPERT_AND_STATUS = "/latest-by-expert-and-status";
public static final String POST_VIEW_COUNT = "/post-view-count";
public static final String POST_FAKE_VIEW_COUNT = "/post-fake-view-count";
public static final String DIRECTION = "/direction";
public static final String ORIGIN = "/origin";
public static final String REGION = "/region";
public static final String AUTH = "/auth";
public static final String AUTH_VERIFICATION = "/verification";
public static final String REFRESH_TOKEN = "/refreshtoken";
public static final String AUTH_LOGIN = "/login";
public static final String AUTH_SIGNUP = "/signup";
public static final String POST_TYPES = "/post-types";
public static final String POST_TYPES_ALL_TYPES_BY_USER = "/{userId}";
public static final String BY_USER_ENDPOINT = "/{userId}";
public static final String PLATFORM_INFORMATION = "/platform-information";
public static final String PLATFORM_INFORMATION_BY_ID = "/{infoId}";
public static final String POST_GET_BY_IMPORTANT_IMAGE = "/get-by-important-image";
public static final String USER_EXPERT_ALL_POST_DIRECTIONS = "/experts/{expertId}/post-directions";

/**
* Method that adds slash after each endpoint while calling
*
* @param api endpoint route
* @return full endpoint route
*/
public static String openApi(String api) {
return api + "/**";
}
/**
* Method that adds slash after each endpoint while calling
*
* @param api endpoint route
* @return full endpoint route
*/
public static String openApi(String api) {
return api + "/**";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
public class AuthResponse {
private String accessToken;
private String tokenType = "Bearer";
private String refreshToken;

public AuthResponse(String accessToken) {
public AuthResponse(String accessToken, String refreshToken) {
this.accessToken = accessToken;
this.refreshToken = refreshToken;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.softserveinc.dokazovi.dto.payload;

import com.softserveinc.dokazovi.entity.UserEntity;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import java.time.Instant;

@Data
@NoArgsConstructor
@Entity(name = "refresh_token")
@Table(name = "refreshtoken")
public class RefreshToken {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "refreshtoken_id")
private Integer id;

@OneToOne
@JoinColumn(name = "user_id", referencedColumnName = "user_id")
private UserEntity user;

@Column(nullable = false, unique = true)
private String token;

@Column(nullable = false)
private Instant expiryDate;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.softserveinc.dokazovi.dto.payload;

import lombok.Data;

import javax.validation.constraints.NotBlank;

@Data
public class RefreshTokenRequest {

@NotBlank
private String refreshToken;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.softserveinc.dokazovi.exception;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(HttpStatus.FORBIDDEN)
public class TokenRefreshException extends RuntimeException {

public TokenRefreshException(String token, String message) {
super(String.format("Failed for [%s]: %s", token, message));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.softserveinc.dokazovi.repositories;

import com.softserveinc.dokazovi.dto.payload.RefreshToken;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.Optional;

@Repository
public interface RefreshTokenRepository extends JpaRepository<RefreshToken, Integer> {

Optional<RefreshToken> findByToken(String token);
}
Loading

0 comments on commit fd1d97c

Please sign in to comment.