Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added brute-force protection #410

Merged
merged 15 commits into from
Oct 14, 2024
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: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ jobs:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN_USER }}
run: |
git fetch --unshallow
mvn clean org.jacoco:jacoco-maven-plugin:prepare-agent install sonar:sonar -Dsonar.projectKey=ita-social-projects-green-city-user -Dsonar.organization=ita-social-projects -Dsonar.host.url=https://sonarcloud.io -Dsonar.sources='src/main/java/greencity' -Dsonar.binaries=target/classes -Dsonar.dynamicAnalysis=reuseReports -Dsonar.coverage.exclusions=**/config/*
mvn clean org.jacoco:jacoco-maven-plugin:prepare-agent install sonar:sonar -Dsonar.projectKey=ita-social-projects-green-city-user -Dsonar.organization=ita-social-projects -Dsonar.host.url=https://sonarcloud.io -Dsonar.sources='src/main/java/greencity' -Dsonar.binaries=target/classes -Dsonar.dynamicAnalysis=reuseReports -Dsonar.coverage.exclusions=**/config/*,**/dto/security/*
# Checks-out your greencity.repository under $GITHUB_WORKSPACE, so your job can access it

- name: Test Reporter
Expand Down
13 changes: 13 additions & 0 deletions core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,20 @@
<version>1.9.20</version>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>4.1.3</version>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2023.0.3</version>
<type>pom</type>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
Expand Down
2 changes: 2 additions & 0 deletions core/src/main/java/greencity/UserApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableScheduling
@EnableFeignClients
public class UserApplication {
/**
* Main method of SpringBoot app.
Expand Down
3 changes: 2 additions & 1 deletion core/src/main/java/greencity/config/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,8 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
.requestMatchers(HttpMethod.POST,
"/ownSecurity/signUp",
"/ownSecurity/signIn",
"/ownSecurity/updatePassword")
"/ownSecurity/updatePassword",
"/ownSecurity/unblockAccount")
.permitAll()
.requestMatchers(HttpMethod.GET,
"/user/shopping-list-items/habits/{habitId}/shopping-list",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
import greencity.exception.exceptions.PasswordsDoNotMatchesException;
import greencity.exception.exceptions.UserAlreadyHasPasswordException;
import greencity.exception.exceptions.UserAlreadyRegisteredException;
import greencity.exception.exceptions.UserBlockedException;
import greencity.exception.exceptions.WrongCaptchaException;
import greencity.exception.exceptions.WrongEmailException;
import greencity.exception.exceptions.WrongIdException;
import greencity.exception.exceptions.WrongPasswordException;
Expand Down Expand Up @@ -499,4 +501,38 @@ public ResponseEntity<Object> handleBase64DecodedException(Base64DecodedExceptio

return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(exceptionResponse);
}

/**
* Handles exceptions of type {@link UserBlockedException}.
*
* @param exception the UserBlockedException instance
* @param request the current web request
* @return a ResponseEntity containing the HTTP status code and error response
* body
*/
@ExceptionHandler(UserBlockedException.class)
public ResponseEntity<Object> handleUserBlockedException(UserBlockedException exception,
WebRequest request) {
log.error(exception.getMessage());
ExceptionResponse exceptionResponse = new ExceptionResponse(getErrorAttributes(request));

return ResponseEntity.status(HttpStatus.LOCKED).body(exceptionResponse);
}

/**
* Handles exceptions of type {@link WrongCaptchaException}.
*
* @param exception the WrongCaptchaException instance
* @param request the current web request
* @return a ResponseEntity containing the HTTP status code and error response
* body
*/
@ExceptionHandler(WrongCaptchaException.class)
public ResponseEntity<Object> handleWrongCaptchaException(WrongCaptchaException exception,
WebRequest request) {
log.error(exception.getMessage());
ExceptionResponse exceptionResponse = new ExceptionResponse(getErrorAttributes(request));

return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(exceptionResponse);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import greencity.security.dto.ownsecurity.OwnSignUpDto;
import greencity.security.dto.ownsecurity.PasswordStatusDto;
import greencity.security.dto.ownsecurity.SetPasswordDto;
import greencity.security.dto.ownsecurity.UnblockAccountDto;
import greencity.security.dto.ownsecurity.UpdatePasswordDto;
import greencity.security.service.OwnSecurityService;
import greencity.security.service.PasswordRecoveryService;
Expand Down Expand Up @@ -298,4 +299,22 @@ public ResponseEntity<Object> deleteUser() {
service.deleteUserByEmail(email);
return ResponseEntity.ok().build();
}

/**
* Unblocks user account by provided token.
*
* @param token {@link String} token for unblocking user account.
* @return {@link ResponseEntity} with 200 status if unblocking is successful.
*/
@Operation(summary = "Unblock user account")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = HttpStatuses.OK),
@ApiResponse(responseCode = "400", description = HttpStatuses.BAD_REQUEST),
@ApiResponse(responseCode = "404", description = ErrorMessage.USER_NOT_FOUND_BY_EMAIL)
})
@PostMapping("/unblockAccount")
public ResponseEntity<Object> unblockAccount(@RequestBody UnblockAccountDto token) {
service.unblockAccount(token.token());
return ResponseEntity.ok().build();
}
}
10 changes: 9 additions & 1 deletion core/src/main/resources/application-dev.properties
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,12 @@ greencityubs.server.address = http://localhost:8050
greencitychat.server.address = ${CHAT_LINK}

#Swagger
springdoc.swagger-ui.doc-expansion=none
springdoc.swagger-ui.doc-expansion=none

#BruteForceSettings
bruteForceSettings.maxAttempts=${MAX_ATTEMPTS:5}
bruteForceSettings.blockTimeInHours=${BLOCK_TIME_IN_HOURS:1}
bruteForceSettings.blockTimeInMinutes=${BLOCK_TIME_IN_MINUTES:15}

#CloudFlare
cloud-flare.secret-key=${CLOUD_FLARE_SECRET_KEY:0x4AAAAAAAxJcrEzmJE2s9LRh2ftN4vIby0}
8 changes: 8 additions & 0 deletions core/src/main/resources/application-docker.properties
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,11 @@ greencitychat.server.address = ${CHAT_LINK}

#Swagger
springdoc.swagger-ui.doc-expansion=none

#BruteForceSettings
bruteForceSettings.maxAttempts=${MAX_ATTEMPTS:5}
bruteForceSettings.blockTimeInHours=${BLOCK_TIME_IN_HOURS:1}
bruteForceSettings.blockTimeInMinutes=${BLOCK_TIME_IN_MINUTES:15}

#CloudFlare
cloud-flare.secret-key=${CLOUD_FLARE_SECRET_KEY:0x4AAAAAAAxJcrEzmJE2s9LRh2ftN4vIby0}
10 changes: 9 additions & 1 deletion core/src/main/resources/application-prod.properties
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,12 @@ spring.web.resources.static-locations=classpath:/static/
greencity.authorization.googleApiKey=${GOOGLE_API_KEY:default-key}

#Swagger
springdoc.swagger-ui.doc-expansion=none
springdoc.swagger-ui.doc-expansion=none

#BruteForceSettings
bruteForceSettings.maxAttempts=${MAX_ATTEMPTS:5}
bruteForceSettings.blockTimeInHours=${BLOCK_TIME_IN_HOURS:1}
bruteForceSettings.blockTimeInMinutes=${BLOCK_TIME_IN_MINUTES:15}

#CloudFlare
cloud-flare.secret-key=${CLOUD_FLARE_SECRET_KEY:0x4AAAAAAAxJcrEzmJE2s9LRh2ftN4vIby0}
9 changes: 9 additions & 0 deletions core/src/main/resources/messages_en.properties
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,15 @@ sincerely.yours.greenCity=Sincerely yours, Green City team.
sincerely.yours.Ubs=Sincerely yours, Pick Up City team.
unsubscribe.text=If you no longer wish to receive these emails, you can
unsubscribe=unsubscribe
unlock=Unlock account
block.account=Account blocked
text.account.ban=Your account has been locked for security reasons due to a possible hacking attempt. For your safety, we recommend changing your password after restoring your account.
warning=Warning!
block.user=Your account is blocked
KizerovDmitriy marked this conversation as resolved.
Show resolved Hide resolved
security.alert=Security Alert: Account Locked
condition.restore=If the restore button does not work, just paste this link into your browser:
condition.unblock=If the unblock button does not work, just paste this link into your browser:
advice.for.block=You received this email for security reasons. To protect your account, we recommend changing your password after unlocking your account.
profile.text=If you no longer wish to receive these emails, you can unsubscribe from them in your
profile=profile
read.more=READ MORE
9 changes: 9 additions & 0 deletions core/src/main/resources/messages_uk.properties
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,15 @@ sincerely.yours.greenCity=\u0429\u0438\u0440\u043e\u0020\u0432\u0430\u0448\u0430
sincerely.yours.Ubs=\u0429\u0438\u0440\u043e\u0020\u0432\u0430\u0448\u0430\u002c\u0020\u043a\u043e\u043c\u0430\u043d\u0434\u0430\u0020\u0050\u0069\u0063\u006b\u0020\u0055\u0070\u0020\u0043\u0069\u0074\u0079\u002e
unsubscribe.text=\u042f\u043a\u0449\u043e \u0432\u0438 \u0431\u0456\u043b\u044c\u0448\u0435 \u043d\u0435 \u0445\u043e\u0447\u0435\u0442\u0435 \u043e\u0442\u0440\u0438\u043c\u0443\u0432\u0430\u0442\u0438 \u0440\u043e\u0437\u0441\u0438\u043b\u043a\u0443\u002c \u0432\u0438 \u043c\u043e\u0436\u0435\u0442\u0435
unsubscribe=\u0432\u0456\u0434\u043f\u0438\u0441\u0430\u0442\u0438\u0441\u044f
unlock=\u0420\u043E\u0437\u0431\u043B\u043E\u043A\u0443\u0432\u0430\u0442\u0438\u0020\u043E\u0431\u043B\u0456\u043A\u043E\u0432\u0438\u0439\u0020\u0437\u0430\u043F\u0438\u0441
block.account=\u041E\u0431\u043B\u0456\u043A\u043E\u0432\u0438\u0439\u0020\u0437\u0430\u043F\u0438\u0441\u0020\u0437\u0430\u0431\u043B\u043E\u043A\u043E\u0432\u0430\u043D\u043E
text.account.ban=\u0412\u0430\u0448\u0020\u043E\u0431\u043B\u0456\u043A\u043E\u0432\u0438\u0439\u0020\u0437\u0430\u043F\u0438\u0441\u0020\u0437\u0430\u0431\u043B\u043E\u043A\u043E\u0432\u0430\u043D\u043E\u0020\u0437\u0020\u043C\u0456\u0440\u043A\u0443\u0432\u0430\u043D\u044C\u0020\u0431\u0435\u0437\u043F\u0435\u043A\u0438\u0020\u0447\u0435\u0440\u0435\u0437\u0020\u043C\u043E\u0436\u043B\u0438\u0432\u0443\u0020\u0441\u043F\u0440\u043E\u0431\u0443\u0020\u0437\u043B\u043E\u043C\u0443\u002E\u0020\u0414\u043B\u044F\u0020\u0432\u0430\u0448\u043E\u0457\u0020\u0431\u0435\u0437\u043F\u0435\u043A\u0438\u0020\u0440\u0435\u043A\u043E\u043C\u0435\u043D\u0434\u0443\u0454\u043C\u043E\u0020\u0437\u043C\u0456\u043D\u0438\u0442\u0438\u0020\u043F\u0430\u0440\u043E\u043B\u044C\u0020\u043F\u0456\u0441\u043B\u044F\u0020\u0432\u0456\u0434\u043D\u043E\u0432\u043B\u0435\u043D\u043D\u044F\u0020\u043E\u0431\u043B\u0456\u043A\u043E\u0432\u043E\u0433\u043E\u0020\u0437\u0430\u043F\u0438\u0441\u0443\u002E
warning=\u0423\u0432\u0430\u0433\u0430\u0021\u000D\u000A
block.user=\u0412\u0430\u0448\u0020\u043E\u0431\u043B\u0456\u043A\u043E\u0432\u0438\u0439\u0020\u0437\u0430\u043F\u0438\u0441\u0020\u0437\u0430\u0431\u043B\u043E\u043A\u043E\u0432\u0430\u043D\u043E
security.alert=\u0421\u043F\u043E\u0432\u0456\u0449\u0435\u043D\u043D\u044F\u0020\u0431\u0435\u0437\u043F\u0435\u043A\u0438\u003A\u0020\u043E\u0431\u043B\u0456\u043A\u043E\u0432\u0438\u0439\u0020\u0437\u0430\u043F\u0438\u0441\u0020\u0437\u0430\u0431\u043B\u043E\u043A\u043E\u0432\u0430\u043D\u043E
condition.restore=\u042F\u043A\u0449\u043E\u0020\u043A\u043D\u043E\u043F\u043A\u0430\u0020\u0432\u0456\u0434\u043D\u043E\u0432\u043B\u0435\u043D\u043D\u044F\u0020\u043D\u0435\u0020\u043F\u0440\u0430\u0446\u044E\u0454\u002C\u0020\u043F\u0440\u043E\u0441\u0442\u043E\u0020\u0432\u0441\u0442\u0430\u0432\u0442\u0435\u0020\u0446\u0435\u0020\u043F\u043E\u0441\u0438\u043B\u0430\u043D\u043D\u044F\u0020\u0443\u0020\u0441\u0432\u0456\u0439\u0020\u0431\u0440\u0430\u0443\u0437\u0435\u0440\u003A
condition.unblock=\u042F\u043A\u0449\u043E\u0020\u043A\u043D\u043E\u043F\u043A\u0430\u0020\u0440\u043E\u0437\u0431\u043B\u043E\u043A\u0443\u0432\u0430\u043D\u043D\u044F\u0020\u043D\u0435\u0020\u043F\u0440\u0430\u0446\u044E\u0454\u002C\u0020\u043F\u0440\u043E\u0441\u0442\u043E\u0020\u0432\u0441\u0442\u0430\u0432\u0442\u0435\u0020\u0446\u0435\u0020\u043F\u043E\u0441\u0438\u043B\u0430\u043D\u043D\u044F\u0020\u0443\u0020\u0441\u0432\u0456\u0439\u0020\u0431\u0440\u0430\u0443\u0437\u0435\u0440\u003A
advice.for.block=\u0412\u0438\u0020\u043E\u0442\u0440\u0438\u043C\u0430\u043B\u0438\u0020\u0446\u0435\u0439\u0020\u0435\u043B\u0435\u043A\u0442\u0440\u043E\u043D\u043D\u0438\u0439\u0020\u043B\u0438\u0441\u0442\u0020\u0437\u0020\u043C\u0456\u0440\u043A\u0443\u0432\u0430\u043D\u044C\u0020\u0431\u0435\u0437\u043F\u0435\u043A\u0438\u002E\u0020\u0429\u043E\u0431\u0020\u0437\u0430\u0445\u0438\u0441\u0442\u0438\u0442\u0438\u0020\u0441\u0432\u0456\u0439\u0020\u043E\u0431\u043B\u0456\u043A\u043E\u0432\u0438\u0439\u0020\u0437\u0430\u043F\u0438\u0441\u002C\u0020\u0440\u0435\u043A\u043E\u043C\u0435\u043D\u0434\u0443\u0454\u043C\u043E\u0020\u0437\u043C\u0456\u043D\u0438\u0442\u0438\u0020\u043F\u0430\u0440\u043E\u043B\u044C\u0020\u043F\u0456\u0441\u043B\u044F\u0020\u0440\u043E\u0437\u0431\u043B\u043E\u043A\u0443\u0432\u0430\u043D\u043D\u044F\u0020\u043E\u0431\u043B\u0456\u043A\u043E\u0432\u043E\u0433\u043E\u0020\u0437\u0430\u043F\u0438\u0441\u0443\u002E
KizerovDmitriy marked this conversation as resolved.
Show resolved Hide resolved
profile.text=\u042f\u043a\u0449\u043e\u0020\u0432\u0438\u0020\u0431\u0456\u043b\u044c\u0448\u0435\u0020\u043d\u0435\u0020\u0445\u043e\u0447\u0435\u0442\u0435\u0020\u043e\u0442\u0440\u0438\u043c\u0443\u0432\u0430\u0442\u0438\u0020\u0440\u043e\u0437\u0441\u0438\u043b\u043a\u0443\u002c\u0020\u0432\u0438\u0020\u043c\u043e\u0436\u0435\u0442\u0435\u0020\u0432\u0456\u0434\u043f\u0438\u0441\u0430\u0442\u0438\u0441\u044f\u0020\u0432\u0456\u0434\u0020\u043d\u0435\u0457\u0020\u0443\u0020\u0441\u0432\u043e\u0454\u043c\u0443
profile=\u043f\u0440\u043e\u0444\u0456\u043b\u0456
read.more=\u0427\u0418\u0422\u0410\u0422\u0418 \u0414\u0410\u041b\u0406
86 changes: 44 additions & 42 deletions core/src/main/resources/templates/core/management_login.html
Original file line number Diff line number Diff line change
Expand Up @@ -45,60 +45,62 @@

}
</script>
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
</head>
<body>
<div id="header" th:insert="core/header"></div>

<div class="container">
<div>
<div class="gc-img">
</div>
<div class="form-content">
<div style="margin-top: 70px; margin-left: 70px; margin-right: 70px">
<div style="font-size: 40px">З поверненням!</div>
</div>
<div style="font-size: 18px;margin-left: 70px; margin-right: 70px">Будь ласка, внеси свої дані для
входу
<div class="gc-img">
</div>
<div class="form-input">
<form th:action="@{/management/login}" th:object="${signInForm}" method="post">
<fieldset>
<div class="form-group">
<div style="font-size: 12px">Електронна пошта</div>
<input style="height: 50px;width: 350px" class="form-control" placeholder="E-mail"
name="email" th:field="*{email}"
type="text">
<p style="color: #bc0000" th:each="error: ${#fields.errors('email')}"
th:text="${error}">Validation error</p>
</div>
<div class="form-group">
<div style="font-size: 12px">Пароль</div>
<input style="height: 50px;width: 350px" class="form-control" placeholder="Password"
name="password"
th:field="*{password}" type="password" value="">
<p style="color: #bc0000" th:each="error: ${#fields.errors('password')}"
th:text="${error}">Validation error</p>
</div>
<input style="height: 50px;width: 350px; margin-top: 65px;background-color: #13aa57"
class="btn btn-lg btn-success btn-block" type="submit" value="Увійти">
</fieldset>
</form>
<div style="margin-left: 160px;margin-top: 5px;margin-bottom: 5px">або</div>
<div class="form-content">
<div style="margin-top: 70px; margin-left: 70px; margin-right: 70px">
<div style="font-size: 40px">З поверненням!</div>
</div>
<div style="font-size: 18px;margin-left: 70px; margin-right: 70px">Будь ласка, внеси свої дані для
входу
</div>
<div class="form-input">
<form th:action="@{/management/login}" th:object="${signInForm}" method="post">
<fieldset>
<div class="form-group">
<div style="font-size: 12px">Електронна пошта</div>
<input style="height: 50px;width: 350px" class="form-control" placeholder="E-mail"
name="email" th:field="*{email}"
type="text">
<p style="color: #bc0000" th:each="error: ${#fields.errors('email')}"
th:text="${error}">Validation error</p>
</div>
<div class="form-group">
<div style="font-size: 12px">Пароль</div>
<input style="height: 50px;width: 350px" class="form-control" placeholder="Password"
name="password"
th:field="*{password}" type="password" value="">
<p style="color: #bc0000" th:each="error: ${#fields.errors('password')}"
th:text="${error}">Validation error</p>
</div>
<div class="cf-turnstile" data-sitekey="0x4AAAAAAAxJcr7nYr6v13PV" data-response-field-name="captchaToken"></div>
<input style="height: 50px;width: 350px; margin-top: 65px;background-color: #13aa57"
class="btn btn-lg btn-success btn-block" type="submit" value="Увійти">
</fieldset>
</form>
<div style="margin-left: 160px;margin-top: 5px;margin-bottom: 5px">або</div>

<a class="g-signin2" data-onsuccess="onSignIn" style="height: 45px;width: 350px">
<div style="height: 45px;width: 350px; background-color: white"
class="btn btn-lg btn-success btn-block">
<div class="google-icon"></div>
<div style="float: left;color: black;margin-left:68px; font-size: 16px">Увійти через
Google
<a class="g-signin2" data-onsuccess="onSignIn" style="height: 45px;width: 350px">
<div style="height: 45px;width: 350px; background-color: white"
class="btn btn-lg btn-success btn-block">
<div class="google-icon"></div>
<div style="float: left;color: black;margin-left:68px; font-size: 16px">Увійти через
Google
</div>
</div>
</div>
</a>
</a>
</div>
</div>
</div>
</div>
</div>
<div id="footer" th:insert="core/footer"></div>
</div>
</body>
</html>
</html>
Loading
Loading