Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/maven-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ jobs:
-e ORACLE_DB_USER="${{ secrets.ORACLE_DB_USER }}" \
-e ORACLE_DB_PASSWORD="${{ secrets.ORACLE_DB_PASSWORD }}" \
-e JWT_SECRET="${{ secrets.JWT_SECRET }}" \
-e JWT_MFA="${{ secrets.JWT_MFA }}" \
-e MFA_SECRET="${{ secrets.MFA_SECRET }}" \
-e DOMAIN_NAME="${{ secrets.DOMAIN_NAME }}" \
"${{ secrets.DOCKER_HUB_USER }}"/taskmanagerauth:latest
sudo docker image prune -f
16 changes: 15 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,12 @@
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>

<!-- https://mvnrepository.com/artifact/com.h2database/h2 -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.3.232</version>
<!-- <scope>test</scope>-->
</dependency>

<dependency>
Expand All @@ -79,6 +79,20 @@
<version>4.5.0</version>
</dependency>

<!-- https://mvnrepository.com/artifact/com.warrenstrange/googleauth -->
<dependency>
<groupId>com.warrenstrange</groupId>
<artifactId>googleauth</artifactId>
<version>1.5.0</version>
</dependency>

<!-- https://mvnrepository.com/artifact/com.google.crypto.tink/tink -->
<dependency>
<groupId>com.google.crypto.tink</groupId>
<artifactId>tink</artifactId>
<version>1.17.0</version>
</dependency>

</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,14 @@ protected void doFilterInternal(
return;
}

String cookie_name;

if (isMfaPath(request.getServletPath())) {
cookie_name = "mfa_access_token";
} else {
cookie_name = "taskmanager_access_token";
}

if (request.getCookies() == null || request.getCookies().length == 0) {
exceptionManager.handleJwtNotProvidedException(
new JwtNotProvidedException("No tokens were provided."),
Expand All @@ -74,11 +82,11 @@ protected void doFilterInternal(
return;
}

String access_token = null;
String access_token;

try {
access_token = Arrays.stream(request.getCookies())
.filter(cookie -> cookie.getName().equals("taskmanager_access_token"))
.filter(cookie -> cookie.getName().equals(cookie_name))
.toList().getFirst().getValue();
} catch (Exception exception) {
exceptionManager.handleJwtNotProvidedException(
Expand All @@ -97,7 +105,8 @@ protected void doFilterInternal(

try {

if (jwtService.validateToken(access_token)) {
if (cookie_name.equals("taskmanager_access_token") && jwtService.validateToken(access_token) ||
cookie_name.equals("mfa_access_token") && jwtService.validate2faToken(access_token)) {

authorities = jwtService.extractAuthorities(access_token);
username = jwtService.extractUser(access_token);
Expand Down Expand Up @@ -165,4 +174,8 @@ private boolean isPermitAllPath(String servletPath) {
return SecurityConfig.permitAllPaths.contains(servletPath);
}

}
private boolean isMfaPath(String servletPath) {
return SecurityConfig.mfaPath.contains(servletPath);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ public class SecurityConfig {
"/auth/login"
);

public static List<String> mfaPath = List.of(
"/auth/2fa/setup",
"/auth/2fa/generate"
);

@Bean
public SecurityFilterChain filterChain(HttpSecurity http, CorsConfigurationSource configurationSource) throws Exception {
http
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.example.taskmanagerauth.controller;

import com.example.taskmanagerauth.dto.impl.ApiResponse;
import com.example.taskmanagerauth.dto.impl.MfaRequest;
import com.example.taskmanagerauth.entity.User;
import com.example.taskmanagerauth.service.MfaService;
import com.example.taskmanagerauth.service.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MfaController {

@Autowired
private MfaService mfaService;

@Autowired
private UserService userService;

private static final Logger logger = LoggerFactory.getLogger(MfaController.class);

@PostMapping("/auth/2fa/setup")
public ResponseEntity<ApiResponse<Void>> setup(@RequestBody MfaRequest mfaRequest) {

if (logger.isDebugEnabled()) {
logger.debug("2FA trying to be enabled.");
}

logger.info("POST HTTP request received at /api/auth/2fa/setup");

User user = userService.loadUserByContext();

mfaService.setupMfa(mfaRequest.getTotp(), user);
userService.saveUser(user);

ApiResponse<Void> response = ApiResponse.of(
HttpStatus.OK.value(),
"Success",
null
);

return ResponseEntity.status(HttpStatus.OK).body(response);

}

@GetMapping("/auth/2fa/generate")
public ResponseEntity<ApiResponse<String>> generateUrl() {

if (logger.isDebugEnabled()) {
logger.debug("Trying to generate 2FA URL.");
}

logger.info("GET HTTP request received at /api/auth/2fa/generate");

ApiResponse<String> response = ApiResponse.of(
HttpStatus.OK.value(),
"Success",
mfaService.generateMfaCode(userService.loadUserByContext())
);

return ResponseEntity.status(HttpStatus.OK).body(response);

}

}
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
package com.example.taskmanagerauth.controller;

import com.example.taskmanagerauth.dto.ApiResponse;
import com.example.taskmanagerauth.dto.impl.ApiResponse;
import com.example.taskmanagerauth.dto.impl.LoginRequest;
import com.example.taskmanagerauth.dto.impl.RegisterRequest;
import com.example.taskmanagerauth.dto.responses.LoginResult;
import com.example.taskmanagerauth.dto.responses.MfaRequired;
import com.example.taskmanagerauth.dto.responses.Success;
import com.example.taskmanagerauth.dto.responses.TotpRequired;
import com.example.taskmanagerauth.entity.User;
import com.example.taskmanagerauth.service.MfaService;
import com.example.taskmanagerauth.service.UserService;
import com.example.taskmanagerauth.service.JwtService;
import jakarta.servlet.http.HttpServletResponse;
Expand All @@ -10,6 +17,7 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
Expand All @@ -26,6 +34,9 @@ public class UserController {
@Autowired
private JwtService jwtService;

@Autowired
private MfaService mfaService;

@GetMapping("/auth/validate")
public ResponseEntity<ApiResponse<Void>> validate() {

Expand All @@ -46,15 +57,21 @@ public ResponseEntity<ApiResponse<Void>> validate() {
}

@PostMapping("/auth/register")
public ResponseEntity<ApiResponse<Void>> register(@RequestBody User user) {
public ResponseEntity<ApiResponse<Void>> register(
@RequestBody RegisterRequest registerRequest
) {

if (logger.isDebugEnabled()) {
logger.debug("Attempting to register...");
}

logger.info("POST HTTP request received at /api/auth/register");

userService.registerUser(user);
User user = userService.createDatabaseUser(registerRequest.getUsername(), registerRequest.getPassword());

userService.checkIfUserExists(user);
mfaService.instantiateMfaForUser(user);
userService.saveUser(user);

ApiResponse<Void> response = ApiResponse.of(
HttpStatus.OK.value(),
Expand All @@ -67,8 +84,8 @@ public ResponseEntity<ApiResponse<Void>> register(@RequestBody User user) {
}

@PostMapping("/auth/login")
public ResponseEntity<ApiResponse<Void>> authenticate(
@RequestBody User user,
public ResponseEntity<ApiResponse<Void>> login(
@RequestBody LoginRequest loginRequest,
HttpServletResponse httpServletResponse
) {

Expand All @@ -78,21 +95,54 @@ public ResponseEntity<ApiResponse<Void>> authenticate(

logger.info("POST HTTP request received at /api/auth/login");

httpServletResponse.addCookie(
jwtService.generateJwtCookie(
userService.loadUserByUsernamePassword(
user.getUsername(), user.getPassword()
)
)
);

ApiResponse<Void> response = ApiResponse.of(
HttpStatus.OK.value(),
"Success",
null
);

return ResponseEntity.status(HttpStatus.OK).body(response);
// Process user
LoginResult result = userService.login(loginRequest);

return switch (result) {
case Success success -> {
httpServletResponse.addCookie(
jwtService.generateJwtCookie(
success.userDetails()
)
);
yield ResponseEntity.status(HttpStatus.OK).body(
ApiResponse.of(
HttpStatus.OK.value(),
"Success",
null
)
);
}
case MfaRequired mfa -> {
httpServletResponse.addCookie(
jwtService.generate2faCookie(
mfa.userDetails()
)
);
yield ResponseEntity.status(HttpStatus.OK).body(
ApiResponse.of(
362,
"Please enable mfa.",
null
)
);
}
case TotpRequired totp -> {
httpServletResponse.addCookie(
jwtService.generate2faCookie(
totp.userDetails()
)
);
yield ResponseEntity.status(HttpStatus.OK).body(
ApiResponse.of(
462,
"TOTP not provided.",
null
)
);
}

};

}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.example.taskmanagerauth.dto;
package com.example.taskmanagerauth.dto.impl;

import com.fasterxml.jackson.annotation.JsonFormat;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.example.taskmanagerauth.dto.impl;

public class LoginRequest {

private String username;
private String password;
private String totp;

public LoginRequest() {}

public LoginRequest(String username, String password, String totp) {
this.username = username;
this.password = password;
this.totp = totp;
}

// Getters & Setters

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

public String getTotp() {
return totp;
}

public void setTotp(String totp) {
this.totp = totp;
}

}
21 changes: 21 additions & 0 deletions src/main/java/com/example/taskmanagerauth/dto/impl/MfaRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.example.taskmanagerauth.dto.impl;

public class MfaRequest {

private String totp;

public MfaRequest(String totp) {
this.totp = totp;
}

// Getters & setters

public String getTotp() {
return totp;
}

public void setTotp(String totp) {
this.totp = totp;
}

}
Loading
Loading