Security/critical fixes (#4)
## What - Addressing security issues: critical and not so critical (more fixed will be pushed soon) ## Why - The app needs to be secure and safe. ## Mood <img width="200" src="https://media2.giphy.com/media/CSpfd57m9WGHnxMWXm/100.webp?cid=36b14facw100seggylsefdj0ap2oopoux3bn3jn6qu59xggq&ep=v1_gifs_search&rid=100.webp&ct=g"/> Reviewed-on: #4
This commit was merged in pull request #4.
This commit is contained in:
@@ -127,6 +127,19 @@ const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }: Pro
|
||||
.finally(() => setLoading(false));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!signed) return;
|
||||
const TWENTY_FIVE_MINUTES = 25 * 60 * 1000;
|
||||
const intervalId = setInterval(() => {
|
||||
checkCurrentAuthUser(window.location.pathname).catch(() => {
|
||||
setSigned(false);
|
||||
setUser(undefined);
|
||||
localStorage.clear();
|
||||
});
|
||||
}, TWENTY_FIVE_MINUTES);
|
||||
return () => clearInterval(intervalId);
|
||||
}, [signed]);
|
||||
|
||||
const updateUser = (userUpdated: UserResponse): void => {
|
||||
setUser(userUpdated);
|
||||
localStorage.setItem(USER_DATA, JSON.stringify(userUpdated));
|
||||
|
||||
@@ -29,7 +29,7 @@ export default defineConfig(({ mode }: ConfigEnv) => {
|
||||
],
|
||||
build: {
|
||||
outDir: 'dist',
|
||||
sourcemap: true
|
||||
sourcemap: mode === 'development'
|
||||
},
|
||||
server: {
|
||||
port: 5000
|
||||
|
||||
@@ -23,13 +23,13 @@ services:
|
||||
POSTGRES_DB: tasknote
|
||||
POSTGRES_HOST: tasknote-db
|
||||
POSTGRES_USER: tasknoteuser
|
||||
POSTGRES_PASSWORD: default
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
POSTGRES_PORT: 5432
|
||||
CORS_ALLOWED_ORIGINS: http://tasknote-web:5000, http://localhost:5000, https://flattop-depth-dropper.ngrok-free.dev
|
||||
CORS_ALLOWED_ORIGINS: http://tasknote-web:5000, http://localhost:5000
|
||||
SERVER_SERVLET_CONTEXT_PATH: /
|
||||
TARGET_ENV: production
|
||||
SECURITY_KEY: ${SECURITY_KEY:-default-security-key}
|
||||
MAILGUN_APIKEY: ${MAILGUN_APIKEY:-default-mailgun-apikey}
|
||||
SECURITY_KEY: ${SECURITY_KEY}
|
||||
MAILGUN_APIKEY: ${MAILGUN_APIKEY}
|
||||
ports: ["8585:8585"]
|
||||
image: ghcr.io/rmcampos/tasknote/api:latest
|
||||
healthcheck:
|
||||
@@ -47,8 +47,7 @@ services:
|
||||
environment:
|
||||
POSTGRES_DB: tasknote
|
||||
POSTGRES_USER: tasknoteuser
|
||||
POSTGRES_PASSWORD: default
|
||||
ports: ["5432:5432"]
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
healthcheck:
|
||||
test: psql -q -U $${POSTGRES_USER} -d $${POSTGRES_DB} -c 'SELECT 1'
|
||||
interval: 1m30s
|
||||
|
||||
@@ -56,7 +56,7 @@ public class SecurityConfig {
|
||||
.requestMatchers("/rest/**")
|
||||
.authenticated()
|
||||
.anyRequest()
|
||||
.permitAll())
|
||||
.denyAll())
|
||||
.httpBasic(AbstractHttpConfigurer::disable)
|
||||
.formLogin(AbstractHttpConfigurer::disable)
|
||||
.sessionManagement(
|
||||
@@ -77,7 +77,7 @@ public class SecurityConfig {
|
||||
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
return new BCryptPasswordEncoder(12);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,6 +2,7 @@ package br.com.tasknoteapp.server.repository;
|
||||
|
||||
import br.com.tasknoteapp.server.entity.TaskEntity;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
@@ -11,6 +12,8 @@ public interface TaskRepository extends JpaRepository<TaskEntity, Long> {
|
||||
|
||||
List<TaskEntity> findAllByUser_id(Long userId);
|
||||
|
||||
Optional<TaskEntity> findByIdAndUser_id(Long id, Long userId);
|
||||
|
||||
@Query(
|
||||
"""
|
||||
select distinct t
|
||||
|
||||
@@ -78,12 +78,8 @@ public class LoginRequest {
|
||||
+ "email='"
|
||||
+ email
|
||||
+ '\''
|
||||
+ ", password='"
|
||||
+ password
|
||||
+ '\''
|
||||
+ ", passwordAgain='"
|
||||
+ passwordAgain
|
||||
+ '\''
|
||||
+ ", password='[REDACTED]'"
|
||||
+ ", passwordAgain='[REDACTED]'"
|
||||
+ ", lang='"
|
||||
+ lang
|
||||
+ '\''
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
package br.com.tasknoteapp.server.request;
|
||||
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import java.util.List;
|
||||
|
||||
/** This record represents a note patch payload. */
|
||||
public record NotePatchRequest(
|
||||
String title,
|
||||
String description,
|
||||
@Pattern(
|
||||
@Size(max = 100) String title,
|
||||
@Size(max = 50000) String description,
|
||||
@Size(max = 200)
|
||||
@Pattern(
|
||||
regexp = "^(https?://.*|#.*)?$",
|
||||
message = "URL must start with https:// or #")
|
||||
String url,
|
||||
|
||||
@@ -2,13 +2,15 @@ package br.com.tasknoteapp.server.request;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import java.util.List;
|
||||
|
||||
/** This record represents a note request to be created. */
|
||||
public record NoteRequest(
|
||||
@NotNull String title,
|
||||
@NotNull String description,
|
||||
@Pattern(
|
||||
@NotNull @Size(max = 100) String title,
|
||||
@NotNull @Size(max = 50000) String description,
|
||||
@Size(max = 200)
|
||||
@Pattern(
|
||||
regexp = "^(https?://.*|#.*)?$",
|
||||
message = "URL must start with https:// or #")
|
||||
String url,
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
package br.com.tasknoteapp.server.request;
|
||||
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import java.util.List;
|
||||
|
||||
/** This record represents a task patch payload. */
|
||||
public record TaskPatchRequest(
|
||||
String description,
|
||||
@Size(max = 2000) String description,
|
||||
Boolean done,
|
||||
List<
|
||||
@Size(max = 200)
|
||||
@Pattern(
|
||||
regexp = "^(https?://.*|#.*)?$",
|
||||
message = "URL must start with https:// or #")
|
||||
|
||||
@@ -3,12 +3,14 @@ package br.com.tasknoteapp.server.request;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import java.util.List;
|
||||
|
||||
/** This record represents a task request to be created. */
|
||||
public record TaskRequest(
|
||||
@NotNull @NotEmpty String description,
|
||||
@NotNull @NotEmpty @Size(max = 2000) String description,
|
||||
List<
|
||||
@Size(max = 200)
|
||||
@Pattern(
|
||||
regexp = "^(https?://.*|#.*)?$",
|
||||
message = "URL must start with https:// or #")
|
||||
|
||||
@@ -2,4 +2,10 @@ package br.com.tasknoteapp.server.request;
|
||||
|
||||
/** This record represents a user patch payload. */
|
||||
public record UserPatchRequest(
|
||||
String name, String email, String password, String passwordAgain, String lang) {}
|
||||
String name,
|
||||
String email,
|
||||
String password,
|
||||
String passwordAgain,
|
||||
String lang,
|
||||
String currentPassword) {}
|
||||
|
||||
|
||||
@@ -292,7 +292,7 @@ public class AuthService {
|
||||
|
||||
String token = jwtService.generateToken(currentUser);
|
||||
|
||||
logger.info("User refreshed! Token {}", token);
|
||||
logger.info("User refreshed! Token {}...", token.substring(0, 6));
|
||||
return token;
|
||||
}
|
||||
|
||||
@@ -329,11 +329,26 @@ public class AuthService {
|
||||
boolean shouldUpdate = false;
|
||||
boolean emailChanged = false;
|
||||
|
||||
boolean changingEmail =
|
||||
!Objects.isNull(patchRequest.email()) && !patchRequest.email().isBlank();
|
||||
boolean changingPassword =
|
||||
!Objects.isNull(patchRequest.password()) && !patchRequest.password().isBlank();
|
||||
|
||||
if (changingEmail || changingPassword) {
|
||||
if (Objects.isNull(patchRequest.currentPassword())
|
||||
|| patchRequest.currentPassword().isBlank()) {
|
||||
throw new BadPasswordException("Current password is required to change email or password");
|
||||
}
|
||||
if (!passwordEncoder.matches(patchRequest.currentPassword(), currentUser.getPassword())) {
|
||||
throw new InvalidCredentialsException();
|
||||
}
|
||||
}
|
||||
|
||||
if (!Objects.isNull(patchRequest.name()) && !patchRequest.name().isBlank()) {
|
||||
currentUser.setName(patchRequest.name().trim());
|
||||
shouldUpdate = true;
|
||||
}
|
||||
if (!Objects.isNull(patchRequest.email()) && !patchRequest.email().isBlank()) {
|
||||
if (changingEmail) {
|
||||
currentUser.setEmail(patchRequest.email().trim());
|
||||
shouldUpdate = true;
|
||||
emailChanged = true;
|
||||
@@ -344,8 +359,7 @@ public class AuthService {
|
||||
}
|
||||
|
||||
boolean updatePassword =
|
||||
!Objects.isNull(patchRequest.password())
|
||||
&& !patchRequest.password().isBlank()
|
||||
changingPassword
|
||||
&& !Objects.isNull(patchRequest.passwordAgain())
|
||||
&& !patchRequest.passwordAgain().isBlank();
|
||||
|
||||
@@ -563,11 +577,11 @@ public class AuthService {
|
||||
|
||||
// if it's more than 3 times in the last 10 minutes, raise timer of 3 hours.
|
||||
if (userPwdList.size() >= 3) {
|
||||
UserPwdLimitEntity mostRecent = userPwdList.getFirst();
|
||||
logger.warn("Oldest: {}", mostRecent.getWhenHappened());
|
||||
Duration duration = Duration.between(mostRecent.getWhenHappened(), LocalDateTime.now());
|
||||
UserPwdLimitEntity oldest = userPwdList.getLast();
|
||||
logger.warn("Oldest failed attempt: {}", oldest.getWhenHappened());
|
||||
Duration duration = Duration.between(oldest.getWhenHappened(), LocalDateTime.now());
|
||||
if (duration.toMinutes() <= 3L) {
|
||||
logger.warn("Wait more {}", 3L - duration.toMinutes());
|
||||
logger.warn("Account locked, minutes remaining: {}", 3L - duration.toMinutes());
|
||||
throw new MaxLoginLimitAttemptException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ public class TaskService {
|
||||
UserEntity user = getCurrentUser();
|
||||
logger.info("Get task ID {} to user ID {}", taskId, user.getId());
|
||||
|
||||
Optional<TaskEntity> task = taskRepository.findById(taskId);
|
||||
Optional<TaskEntity> task = taskRepository.findByIdAndUser_id(taskId, user.getId());
|
||||
if (task.isEmpty()) {
|
||||
throw new TaskNotFoundException();
|
||||
}
|
||||
@@ -152,7 +152,7 @@ public class TaskService {
|
||||
|
||||
logger.info("Patching task ID {} to user ID {}", taskId, user.getId());
|
||||
|
||||
Optional<TaskEntity> task = taskRepository.findById(taskId);
|
||||
Optional<TaskEntity> task = taskRepository.findByIdAndUser_id(taskId, user.getId());
|
||||
if (task.isEmpty()) {
|
||||
throw new TaskNotFoundException();
|
||||
}
|
||||
@@ -198,7 +198,7 @@ public class TaskService {
|
||||
|
||||
logger.info("Deleting task ID {} to user ID {}", taskId, user.getId());
|
||||
|
||||
Optional<TaskEntity> task = taskRepository.findById(taskId);
|
||||
Optional<TaskEntity> task = taskRepository.findByIdAndUser_id(taskId, user.getId());
|
||||
if (task.isEmpty()) {
|
||||
throw new TaskNotFoundException();
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ package br.com.tasknoteapp.server.service.impl;
|
||||
import br.com.tasknoteapp.server.entity.UserEntity;
|
||||
import br.com.tasknoteapp.server.service.JwtService;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.JwtException;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.MalformedJwtException;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDateTime;
|
||||
@@ -29,7 +29,7 @@ class JwtServiceImpl implements JwtService {
|
||||
private static final long MINUTE = SECOND * 60;
|
||||
private static final long HOUR = MINUTE * 60;
|
||||
private static final long DAY = HOUR * 24;
|
||||
private static final long EXPIRATION_TIME = DAY * 7;
|
||||
private static final long EXPIRATION_TIME = MINUTE * 30;
|
||||
private final SecretKey key;
|
||||
|
||||
public JwtServiceImpl(@Value("${br.com.tasknote.server.jwt-secret}") String secretKey) {
|
||||
@@ -122,7 +122,7 @@ class JwtServiceImpl implements JwtService {
|
||||
try {
|
||||
return Optional.of(
|
||||
Jwts.parser().verifyWith(key).build().parseSignedClaims(token).getPayload());
|
||||
} catch (MalformedJwtException me) {
|
||||
} catch (JwtException e) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,72 +1,17 @@
|
||||
package br.com.tasknoteapp.server.util;
|
||||
|
||||
import br.com.tasknoteapp.server.exception.BadAlgorithmException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.UUID;
|
||||
|
||||
/** This class provides method to handle UUIDs. */
|
||||
public class UuidUtil {
|
||||
private final UUID namespaceUrl = UUID.fromString("6ba7b811-9dad-11d1-80b4-00c04fd430c8");
|
||||
|
||||
/**
|
||||
* Generated a unique UUID to a given email.
|
||||
* Generates a cryptographically random UUID for use as an email confirmation token.
|
||||
*
|
||||
* @param email The email to create the UUID.
|
||||
* @return The generated UUID.
|
||||
* @param email The user email (unused; kept for API compatibility).
|
||||
* @return A random UUID.
|
||||
*/
|
||||
public UUID generateEmailUuid(String email) {
|
||||
return generateUuidFromName(namespaceUrl, email.toLowerCase().trim());
|
||||
}
|
||||
|
||||
private UUID generateUuidFromName(UUID namespace, String name) {
|
||||
// SHA-1 digest of namespace UUID + name
|
||||
byte[] namespaceBytes = toBytes(namespace);
|
||||
byte[] nameBytes = name.getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
byte[] combined = new byte[namespaceBytes.length + nameBytes.length];
|
||||
System.arraycopy(namespaceBytes, 0, combined, 0, namespaceBytes.length);
|
||||
System.arraycopy(nameBytes, 0, combined, namespaceBytes.length, nameBytes.length);
|
||||
|
||||
byte[] sha1 = sha1(combined);
|
||||
|
||||
// Manipulate bits to make it UUID v5 (version 5, SHA-1)
|
||||
sha1[6] &= 0x0f;
|
||||
sha1[6] |= 0x50;
|
||||
sha1[8] &= 0x3f;
|
||||
sha1[8] |= (byte) 0x80;
|
||||
|
||||
return bytesToUuid(sha1);
|
||||
}
|
||||
|
||||
private byte[] toBytes(UUID uuid) {
|
||||
long msb = uuid.getMostSignificantBits();
|
||||
long lsb = uuid.getLeastSignificantBits();
|
||||
byte[] bytes = new byte[16];
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
bytes[i] = (byte) ((msb >>> (8 * (7 - i))) & 0xFF);
|
||||
bytes[8 + i] = (byte) ((lsb >>> (8 * (7 - i))) & 0xFF);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
private byte[] sha1(byte[] input) {
|
||||
try {
|
||||
return java.security.MessageDigest.getInstance("SHA-1").digest(input);
|
||||
} catch (Exception e) {
|
||||
throw new BadAlgorithmException("SHA-1 algorithm not available");
|
||||
}
|
||||
}
|
||||
|
||||
private UUID bytesToUuid(byte[] hash) {
|
||||
long msb = 0;
|
||||
long lsb = 0;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
msb = (msb << 8) | (hash[i] & 0xff);
|
||||
}
|
||||
for (int i = 8; i < 16; i++) {
|
||||
lsb = (lsb << 8) | (hash[i] & 0xff);
|
||||
}
|
||||
return new UUID(msb, lsb);
|
||||
return UUID.randomUUID();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,14 @@ br:
|
||||
com:
|
||||
tasknote:
|
||||
server:
|
||||
jwt-secret: ${SECURITY_KEY:empty}
|
||||
jwt-secret: ${SECURITY_KEY}
|
||||
target-env: ${TARGET_ENV:development}
|
||||
cors:
|
||||
allowed-origins: ${CORS_ALLOWED_ORIGINS:http://localhost}
|
||||
logging:
|
||||
level:
|
||||
root: ${ROOT_LOG_LEVEL:INFO}
|
||||
br.com.tasknoteapp: TRACE
|
||||
br.com.tasknoteapp: INFO
|
||||
|
||||
mailgun:
|
||||
api-key: ${MAILGUN_APIKEY:abc123456}
|
||||
@@ -19,7 +19,7 @@ mailgun:
|
||||
server:
|
||||
port: 8585
|
||||
error:
|
||||
include-message: always
|
||||
include-message: never
|
||||
servlet:
|
||||
context-path: ${SERVER_SERVLET_CONTEXT_PATH:/server}
|
||||
spring:
|
||||
@@ -27,7 +27,7 @@ spring:
|
||||
name: tasknote-api
|
||||
datasource:
|
||||
driver-class-name: org.postgresql.Driver
|
||||
password: ${POSTGRES_PASSWORD:default}
|
||||
password: ${POSTGRES_PASSWORD}
|
||||
url: jdbc:postgresql://${POSTGRES_HOST:localhost}:${POSTGRES_PORT:5435}/${POSTGRES_DB:tasknote}
|
||||
username: ${POSTGRES_USER:tasknoteuser}
|
||||
flyway:
|
||||
|
||||
@@ -2,14 +2,14 @@ br:
|
||||
com:
|
||||
tasknote:
|
||||
server:
|
||||
jwt-secret: ${SECURITY_KEY:empty}
|
||||
jwt-secret: ${SECURITY_KEY}
|
||||
target-env: ${TARGET_ENV:development}
|
||||
cors:
|
||||
allowed-origins: ${CORS_ALLOWED_ORIGINS:http://localhost}
|
||||
logging:
|
||||
level:
|
||||
root: ${ROOT_LOG_LEVEL:INFO}
|
||||
br.com.tasknoteapp: TRACE
|
||||
br.com.tasknoteapp: INFO
|
||||
|
||||
mailgun:
|
||||
api-key: ${MAILGUN_APIKEY:abc123456}
|
||||
@@ -19,7 +19,7 @@ mailgun:
|
||||
server:
|
||||
port: 8585
|
||||
error:
|
||||
include-message: always
|
||||
include-message: never
|
||||
servlet:
|
||||
context-path: ${SERVER_SERVLET_CONTEXT_PATH:/server}
|
||||
spring:
|
||||
@@ -27,7 +27,7 @@ spring:
|
||||
name: tasknote-api
|
||||
datasource:
|
||||
driver-class-name: org.postgresql.Driver
|
||||
password: ${POSTGRES_PASSWORD:default}
|
||||
password: ${POSTGRES_PASSWORD}
|
||||
url: jdbc:postgresql://${POSTGRES_HOST:localhost}:${POSTGRES_PORT:5435}/${POSTGRES_DB:tasknote}
|
||||
username: ${POSTGRES_USER:tasknoteuser}
|
||||
flyway:
|
||||
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE tasknote.notes
|
||||
ADD CONSTRAINT chk_notes_description_max_length CHECK (length(description) <= 50000) NOT VALID;
|
||||
@@ -103,7 +103,8 @@ class UserControllerTest {
|
||||
void patchUserInfo_happyPath_shouldSucceed() throws Exception {
|
||||
UserResponse response =
|
||||
new UserResponse(1L, "John", "email@example.com", false, null, null, null, null);
|
||||
UserPatchRequest request = new UserPatchRequest("John Doe", response.email(), null, null, null);
|
||||
UserPatchRequest request =
|
||||
new UserPatchRequest("John Doe", response.email(), null, null, null, null);
|
||||
when(authService.patchUserInfo(request)).thenReturn(response);
|
||||
|
||||
String jsonString =
|
||||
|
||||
@@ -264,9 +264,11 @@ class AuthServiceTest {
|
||||
when(userRepository.findByEmail(request.email())).thenReturn(Optional.of(existing));
|
||||
|
||||
UserPwdLimitEntity limit1 = new UserPwdLimitEntity();
|
||||
limit1.setWhenHappened(LocalDateTime.now().minusMinutes(1));
|
||||
limit1.setWhenHappened(LocalDateTime.now().minusSeconds(30));
|
||||
UserPwdLimitEntity limit2 = new UserPwdLimitEntity();
|
||||
limit2.setWhenHappened(LocalDateTime.now().minusMinutes(1));
|
||||
UserPwdLimitEntity limit3 = new UserPwdLimitEntity();
|
||||
limit3.setWhenHappened(LocalDateTime.now().minusMinutes(2));
|
||||
when(userPwdLimitRepository.findTop3ByUser_idOrderByWhenHappenedDesc(existing.getId()))
|
||||
.thenReturn(List.of(limit1, limit2, limit3));
|
||||
|
||||
@@ -417,12 +419,15 @@ class AuthServiceTest {
|
||||
existing.setName(null);
|
||||
existing.setEmail(email);
|
||||
existing.setAdmin(false);
|
||||
existing.setPassword("hashedCurrentPassword");
|
||||
when(userRepository.findByEmail(email)).thenReturn(Optional.of(existing));
|
||||
|
||||
when(userRepository.save(any())).thenReturn(existing);
|
||||
|
||||
String currentPassword = "currentPw123@";
|
||||
when(passwordEncoder.matches(currentPassword, "hashedCurrentPassword")).thenReturn(true);
|
||||
|
||||
UserPatchRequest patchRequest =
|
||||
new UserPatchRequest("Kong", "newemail@domain.com", null, null, null);
|
||||
new UserPatchRequest("Kong", "newemail@domain.com", null, null, null, currentPassword);
|
||||
UserResponse response = authService.patchUserInfo(patchRequest);
|
||||
|
||||
Assertions.assertNotNull(response);
|
||||
@@ -441,13 +446,17 @@ class AuthServiceTest {
|
||||
existing.setName(null);
|
||||
existing.setEmail(email);
|
||||
existing.setAdmin(false);
|
||||
existing.setPassword("hashedCurrentPassword");
|
||||
when(userRepository.findByEmail(email)).thenReturn(Optional.of(existing));
|
||||
|
||||
when(userRepository.save(any())).thenReturn(existing);
|
||||
|
||||
String currentPassword = "currentPw123@";
|
||||
when(passwordEncoder.matches(currentPassword, "hashedCurrentPassword")).thenReturn(true);
|
||||
|
||||
String newPassword = "TestHackedPw@difficult!#:)";
|
||||
UserPatchRequest patchRequest =
|
||||
new UserPatchRequest("Kong", "newemail@domain.com", newPassword, newPassword, "en");
|
||||
new UserPatchRequest(
|
||||
"Kong", "newemail@domain.com", newPassword, newPassword, "en", currentPassword);
|
||||
|
||||
when(authUtil.validatePassword(patchRequest.password())).thenReturn(Optional.empty());
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ class TaskServiceTest {
|
||||
taskEntity.setHighPriority(true);
|
||||
taskEntity.setTags(Set.of(new TagEntity("test", userEntity)));
|
||||
taskEntity.setUser(userEntity);
|
||||
when(taskRepository.findById(taskId)).thenReturn(Optional.of(taskEntity));
|
||||
when(taskRepository.findByIdAndUser_id(taskId, USER_ID)).thenReturn(Optional.of(taskEntity));
|
||||
|
||||
TaskResponse taskResponse = taskService.getTaskById(taskId);
|
||||
|
||||
@@ -108,7 +108,7 @@ class TaskServiceTest {
|
||||
|
||||
Long taskId = 9976L;
|
||||
|
||||
when(taskRepository.findById(taskId)).thenReturn(Optional.empty());
|
||||
when(taskRepository.findByIdAndUser_id(taskId, USER_ID)).thenReturn(Optional.empty());
|
||||
|
||||
assertThrows(TaskNotFoundException.class, () -> taskService.getTaskById(taskId));
|
||||
}
|
||||
@@ -312,7 +312,7 @@ class TaskServiceTest {
|
||||
taskEntity.setHighPriority(true);
|
||||
taskEntity.setTags(Set.of(new TagEntity("test", userEntity)));
|
||||
taskEntity.setUser(userEntity);
|
||||
when(taskRepository.findById(taskId)).thenReturn(Optional.of(taskEntity));
|
||||
when(taskRepository.findByIdAndUser_id(taskId, USER_ID)).thenReturn(Optional.of(taskEntity));
|
||||
|
||||
when(taskUrlRepository.findAllById_taskId(taskId)).thenReturn(List.of());
|
||||
|
||||
@@ -335,7 +335,7 @@ class TaskServiceTest {
|
||||
|
||||
Long taskId = 2526L;
|
||||
|
||||
when(taskRepository.findById(taskId)).thenReturn(Optional.empty());
|
||||
when(taskRepository.findByIdAndUser_id(taskId, USER_ID)).thenReturn(Optional.empty());
|
||||
|
||||
assertThrows(TaskNotFoundException.class, () -> taskService.deleteTask(taskId));
|
||||
}
|
||||
@@ -359,7 +359,7 @@ class TaskServiceTest {
|
||||
taskEntity.setDone(false);
|
||||
taskEntity.setTags(Set.of(new TagEntity("test", userEntity)));
|
||||
taskEntity.setUser(userEntity);
|
||||
when(taskRepository.findById(taskId)).thenReturn(Optional.of(taskEntity));
|
||||
when(taskRepository.findByIdAndUser_id(taskId, USER_ID)).thenReturn(Optional.of(taskEntity));
|
||||
|
||||
when(taskUrlRepository.findAllById_taskId(taskId)).thenReturn(List.of());
|
||||
|
||||
@@ -410,7 +410,7 @@ class TaskServiceTest {
|
||||
taskEntity.setDone(false);
|
||||
taskEntity.setTags(Set.of(new TagEntity("test", userEntity)));
|
||||
taskEntity.setUser(userEntity);
|
||||
when(taskRepository.findById(taskId)).thenReturn(Optional.of(taskEntity));
|
||||
when(taskRepository.findByIdAndUser_id(taskId, USER_ID)).thenReturn(Optional.of(taskEntity));
|
||||
|
||||
TaskUrlEntity urlEntity = new TaskUrlEntity();
|
||||
urlEntity.setId(new TaskUrlEntityPk(taskId, "www.url.com"));
|
||||
@@ -460,7 +460,7 @@ class TaskServiceTest {
|
||||
|
||||
Long taskId = 2525L;
|
||||
|
||||
when(taskRepository.findById(taskId)).thenReturn(Optional.empty());
|
||||
when(taskRepository.findByIdAndUser_id(taskId, USER_ID)).thenReturn(Optional.empty());
|
||||
|
||||
List<String> tags = List.of("test");
|
||||
TaskPatchRequest patch =
|
||||
@@ -488,7 +488,7 @@ class TaskServiceTest {
|
||||
taskEntity.setDone(false);
|
||||
taskEntity.setTags(Set.of(new TagEntity("test", userEntity)));
|
||||
taskEntity.setUser(userEntity);
|
||||
when(taskRepository.findById(taskId)).thenReturn(Optional.of(taskEntity));
|
||||
when(taskRepository.findByIdAndUser_id(taskId, USER_ID)).thenReturn(Optional.of(taskEntity));
|
||||
|
||||
when(taskUrlRepository.findAllById_taskId(taskId)).thenReturn(List.of());
|
||||
|
||||
|
||||
@@ -3,14 +3,12 @@ package br.com.tasknoteapp.server.service.impl;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import br.com.tasknoteapp.server.entity.UserEntity;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.ExpiredJwtException;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
@@ -100,7 +98,7 @@ class JwtServiceImplTest {
|
||||
LocalDateTime expiration = jwtService.extractExpiration(token);
|
||||
|
||||
assertNotNull(expiration);
|
||||
LocalDateTime expectedExpiration = LocalDateTime.now().plusDays(7).withNano(0);
|
||||
LocalDateTime expectedExpiration = LocalDateTime.now().plusMinutes(30).withNano(0);
|
||||
assertFalse(ChronoUnit.SECONDS.between(expiration.withNano(0), expectedExpiration) > 5);
|
||||
}
|
||||
|
||||
@@ -131,7 +129,7 @@ class JwtServiceImplTest {
|
||||
.signWith(getKey())
|
||||
.compact();
|
||||
|
||||
assertThrows(ExpiredJwtException.class, () -> jwtService.isTokenExpired(expiredToken));
|
||||
assertTrue(jwtService.isTokenExpired(expiredToken));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -13,6 +13,6 @@ class UuidUtilTest {
|
||||
UUID uuid = uuidUtil.generateEmailUuid(email);
|
||||
|
||||
Assertions.assertNotNull(uuid);
|
||||
Assertions.assertEquals(uuid, uuidUtil.generateEmailUuid(email));
|
||||
Assertions.assertNotNull(uuidUtil.generateEmailUuid(email));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,12 +26,22 @@ resource "google_cloud_run_v2_service" "backend" {
|
||||
value = google_sql_database_instance.instance.private_ip_address
|
||||
}
|
||||
env {
|
||||
name = "POSTGRES_USER"
|
||||
value = var.db_user
|
||||
name = "POSTGRES_USER"
|
||||
value_source {
|
||||
secret_key_ref {
|
||||
secret = google_secret_manager_secret.db_user.secret_id
|
||||
version = google_secret_manager_secret_version.db_user_version.version
|
||||
}
|
||||
}
|
||||
}
|
||||
env {
|
||||
name = "POSTGRES_PASSWORD"
|
||||
value = var.db_password
|
||||
name = "POSTGRES_PASSWORD"
|
||||
value_source {
|
||||
secret_key_ref {
|
||||
secret = google_secret_manager_secret.db_password.secret_id
|
||||
version = google_secret_manager_secret_version.db_password_version.version
|
||||
}
|
||||
}
|
||||
}
|
||||
env {
|
||||
name = "POSTGRES_PORT"
|
||||
|
||||
@@ -3,6 +3,52 @@ resource "google_service_account" "cloudrun_sa" {
|
||||
display_name = "TaskNote Cloud Run Service Account"
|
||||
}
|
||||
|
||||
resource "google_secret_manager_secret" "db_password" {
|
||||
secret_id = "db-password"
|
||||
replication {
|
||||
user_managed {
|
||||
replicas {
|
||||
location = var.region
|
||||
}
|
||||
}
|
||||
}
|
||||
depends_on = [google_project_service.secretmanager]
|
||||
}
|
||||
|
||||
resource "google_secret_manager_secret_version" "db_password_version" {
|
||||
secret = google_secret_manager_secret.db_password.id
|
||||
secret_data = var.db_password
|
||||
}
|
||||
|
||||
resource "google_secret_manager_secret" "db_user" {
|
||||
secret_id = "db-user"
|
||||
replication {
|
||||
user_managed {
|
||||
replicas {
|
||||
location = var.region
|
||||
}
|
||||
}
|
||||
}
|
||||
depends_on = [google_project_service.secretmanager]
|
||||
}
|
||||
|
||||
resource "google_secret_manager_secret_version" "db_user_version" {
|
||||
secret = google_secret_manager_secret.db_user.id
|
||||
secret_data = var.db_user
|
||||
}
|
||||
|
||||
resource "google_secret_manager_secret_iam_member" "db_password_access" {
|
||||
secret_id = google_secret_manager_secret.db_password.id
|
||||
role = "roles/secretmanager.secretAccessor"
|
||||
member = "serviceAccount:${google_service_account.cloudrun_sa.email}"
|
||||
}
|
||||
|
||||
resource "google_secret_manager_secret_iam_member" "db_user_access" {
|
||||
secret_id = google_secret_manager_secret.db_user.id
|
||||
role = "roles/secretmanager.secretAccessor"
|
||||
member = "serviceAccount:${google_service_account.cloudrun_sa.email}"
|
||||
}
|
||||
|
||||
resource "google_secret_manager_secret" "security_key" {
|
||||
secret_id = "security-key"
|
||||
replication {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
set -euo pipefail
|
||||
|
||||
docker run --rm -i --network=host \
|
||||
-e PGPASSWORD=default \
|
||||
-e PGPASSWORD="${PGPASSWORD:?PGPASSWORD env var is required}" \
|
||||
postgres:15.8-bookworm \
|
||||
psql -h localhost -U tasknoteuser -d tasknote \
|
||||
-c "UPDATE tasknote.users SET email_confirmed_at = created_at WHERE id > 0;"
|
||||
|
||||
Reference in New Issue
Block a user