Public Access
feat(registration): registration waits for the admin, no invite code
This commit is contained in:
1 parent
1008034e82
commit
39a4d5ae49
26 files changed
+261
-147
No files matched your search
@@ -192,20 +192,22 @@ class DeletedAccountFlowTest extends AbstractIntegrationTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void registration_CountsInThePublicStatsAtOnce() throws Exception {
|
||||
void registration_WaitsAndDoesNotCountUntilTheAccept() throws Exception {
|
||||
String newEmail = "delete-flow-registered@example.com";
|
||||
jdbc.update("DELETE FROM users WHERE email = ?", newEmail);
|
||||
long before = totalUsers();
|
||||
|
||||
try {
|
||||
mockMvc.perform(post("/api/registration/new")
|
||||
.header(ClientHeaderFilter.CLIENT_HEADER, "web")
|
||||
.header(ClientHeaderFilter.CLIENT_HEADER, "web").header("X-Forwarded-For", freshClientIp())
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"email\":\"" + newEmail + "\",\"password\":\"" + PASSWORD
|
||||
+ "\",\"inviteCode\":\"test-invite-code\"}"))
|
||||
+ "\"}"))
|
||||
.andExpect(status().is2xxSuccessful());
|
||||
|
||||
assertThat(totalUsers()).isEqualTo(before + 1);
|
||||
assertThat(jdbc.queryForObject("SELECT status FROM users WHERE email = ?", String.class, newEmail))
|
||||
.isEqualTo("PENDING");
|
||||
assertThat(totalUsers()).isEqualTo(before);
|
||||
} finally {
|
||||
jdbc.update("DELETE FROM users WHERE email = ?", newEmail);
|
||||
}
|
||||
@@ -216,10 +218,10 @@ class DeletedAccountFlowTest extends AbstractIntegrationTest {
|
||||
jdbc.update("UPDATE users SET deleted_at = NOW() WHERE email = ?", EMAIL);
|
||||
|
||||
mockMvc.perform(post("/api/registration/new")
|
||||
.header(ClientHeaderFilter.CLIENT_HEADER, "web")
|
||||
.header(ClientHeaderFilter.CLIENT_HEADER, "web").header("X-Forwarded-For", freshClientIp())
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"email\":\"" + EMAIL + "\",\"password\":\"" + PASSWORD
|
||||
+ "\",\"inviteCode\":\"test-invite-code\"}"))
|
||||
+ "\"}"))
|
||||
.andExpect(jsonPath("$.code").value("EMAIL_ALREADY_IN_USE"));
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ class MailTemplateTest extends AbstractUnitTest {
|
||||
@EnumSource(MailTemplate.class)
|
||||
void render_FillsEveryPlaceholderInBothLanguages(MailTemplate template) {
|
||||
for (String language : new String[]{"de", "en"}) {
|
||||
Mail mail = template.render(language, "Alice", "alice@example.com");
|
||||
Mail mail = template.render(language, "Alice", "alice@example.com", "https://tbp.example");
|
||||
|
||||
assertThat(mail.subject()).isNotBlank().doesNotContain("%");
|
||||
assertThat(mail.body()).contains("Alice").doesNotContain("%s");
|
||||
|
||||
@@ -71,10 +71,10 @@ class MailFlowTest extends AbstractIntegrationTest {
|
||||
}
|
||||
|
||||
private void register() throws Exception {
|
||||
mockMvc.perform(post("/api/registration/new").header(ClientHeaderFilter.CLIENT_HEADER, "web")
|
||||
mockMvc.perform(post("/api/registration/new").header(ClientHeaderFilter.CLIENT_HEADER, "web").header("X-Forwarded-For", freshClientIp())
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"email\":\"" + NEW_USER + "\",\"password\":\"" + PASSWORD
|
||||
+ "\",\"inviteCode\":\"test-invite-code\",\"displayName\":\"MailFlow\",\"language\":\"de\"}"))
|
||||
+ "\",\"displayName\":\"MailFlow\",\"language\":\"de\"}"))
|
||||
.andExpect(status().isOk());
|
||||
}
|
||||
|
||||
@@ -87,9 +87,9 @@ class MailFlowTest extends AbstractIntegrationTest {
|
||||
register();
|
||||
|
||||
assertThat(rows()).extracting(row -> row.get("kind") + " " + row.get("to_address") + " " + row.get("status"))
|
||||
.containsExactly("NEW_USER_REGISTERED admin@test.local PENDING",
|
||||
"ACCOUNT_ACTIVE " + NEW_USER + " PENDING");
|
||||
assertThat(rows().get(1).get("subject")).isEqualTo("Dein ToBePlayed-Konto ist aktiv");
|
||||
.containsExactly("REGISTRATION_REQUESTED admin@test.local PENDING",
|
||||
"REGISTRATION_RECEIVED " + NEW_USER + " PENDING");
|
||||
assertThat(rows().get(1).get("subject")).isEqualTo("Deine ToBePlayed-Registrierung ist angekommen");
|
||||
|
||||
mailOutboxService.sendDue();
|
||||
|
||||
@@ -99,6 +99,25 @@ class MailFlowTest extends AbstractIntegrationTest {
|
||||
assertThat(rows()).extracting(row -> row.get("status")).containsOnly("SENT");
|
||||
}
|
||||
|
||||
@Test
|
||||
void acceptAndDeny_MailTheUser() throws Exception {
|
||||
insertUser(ADMIN, "PLATFORM_ADMIN");
|
||||
long accepted = insertUser(PLAYER, "USER");
|
||||
long denied = insertUser(NEW_USER, "USER");
|
||||
jdbc.update("UPDATE users SET status = 'PENDING' WHERE id IN (?, ?)", accepted, denied);
|
||||
String admin = accessToken(ADMIN);
|
||||
|
||||
for (long userId : new long[]{accepted, denied}) {
|
||||
mockMvc.perform(put("/api/admin/users/" + userId + "/decision").header(ClientHeaderFilter.CLIENT_HEADER, "web")
|
||||
.cookie(new Cookie(AuthCookies.ACCESS_COOKIE, admin))
|
||||
.contentType(MediaType.APPLICATION_JSON).content("{\"accepted\":" + (userId == accepted) + "}"))
|
||||
.andExpect(status().isNoContent());
|
||||
}
|
||||
|
||||
assertThat(rows()).extracting(row -> row.get("kind") + " " + row.get("to_address"))
|
||||
.containsExactly("ACCOUNT_ACTIVE " + PLAYER, "REGISTRATION_DENIED " + NEW_USER);
|
||||
}
|
||||
|
||||
@Test
|
||||
void aFailedSend_IsTriedAgainAndThenGivenUp() throws Exception {
|
||||
register();
|
||||
@@ -121,10 +140,10 @@ class MailFlowTest extends AbstractIntegrationTest {
|
||||
void aFailedRegistration_WritesNoMail() throws Exception {
|
||||
insertUser(NEW_USER, "USER");
|
||||
|
||||
mockMvc.perform(post("/api/registration/new").header(ClientHeaderFilter.CLIENT_HEADER, "web")
|
||||
mockMvc.perform(post("/api/registration/new").header(ClientHeaderFilter.CLIENT_HEADER, "web").header("X-Forwarded-For", freshClientIp())
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"email\":\"" + NEW_USER + "\",\"password\":\"" + PASSWORD
|
||||
+ "\",\"inviteCode\":\"test-invite-code\"}"))
|
||||
+ "\"}"))
|
||||
.andExpect(status().isConflict());
|
||||
|
||||
assertThat(rows()).isEmpty();
|
||||
|
||||
@@ -44,35 +44,35 @@ class MailServiceTest extends AbstractUnitTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void userRegistered_TellsTheAdminInGermanAndTheUserInTheirLanguage() {
|
||||
void registrationRequested_TellsTheAdminInGermanWithTheLinkAndTheUserInTheirLanguage() {
|
||||
user("Alice", "en");
|
||||
|
||||
service(ADMIN).userRegistered(USER);
|
||||
service(ADMIN).registrationRequested(USER);
|
||||
|
||||
assertThat(inserted(MailTemplate.NEW_USER_REGISTERED, ADMIN).body())
|
||||
.startsWith("Ein neuer Nutzer").contains("Alice", "alice@example.com");
|
||||
assertThat(inserted(MailTemplate.ACCOUNT_ACTIVE, "alice@example.com").body())
|
||||
.startsWith("Hello Alice,").contains("https://tbp.example");
|
||||
assertThat(inserted(MailTemplate.REGISTRATION_REQUESTED, ADMIN).body())
|
||||
.startsWith("Eine neue Registrierung").contains("Alice", "alice@example.com", "https://tbp.example/admin/users");
|
||||
assertThat(inserted(MailTemplate.REGISTRATION_RECEIVED, "alice@example.com").body()).startsWith("Hello Alice,");
|
||||
}
|
||||
|
||||
@Test
|
||||
void userRegistered_GreetsByEmailWithoutADisplayName() {
|
||||
void registrationRequested_GreetsByEmailWithoutADisplayName() {
|
||||
user(null, "de");
|
||||
|
||||
service(ADMIN).userRegistered(USER);
|
||||
service(ADMIN).registrationRequested(USER);
|
||||
|
||||
assertThat(inserted(MailTemplate.ACCOUNT_ACTIVE, "alice@example.com").body()).startsWith("Hallo alice@example.com,");
|
||||
assertThat(inserted(MailTemplate.NEW_USER_REGISTERED, ADMIN).body()).contains("Name: -");
|
||||
assertThat(inserted(MailTemplate.REGISTRATION_RECEIVED, "alice@example.com").body())
|
||||
.startsWith("Hallo alice@example.com,");
|
||||
assertThat(inserted(MailTemplate.REGISTRATION_REQUESTED, ADMIN).body()).contains("Name: -");
|
||||
}
|
||||
|
||||
@Test
|
||||
void userRegistered_WritesNoAdminRowWithoutAnAdminAddress() {
|
||||
void registrationRequested_WritesNoAdminRowWithoutAnAdminAddress() {
|
||||
user("Alice", "en");
|
||||
|
||||
service("").userRegistered(USER);
|
||||
service("").registrationRequested(USER);
|
||||
|
||||
verify(repository, never()).insert(eq(MailTemplate.NEW_USER_REGISTERED), anyLong(), anyString(), any());
|
||||
inserted(MailTemplate.ACCOUNT_ACTIVE, "alice@example.com");
|
||||
verify(repository, never()).insert(eq(MailTemplate.REGISTRATION_REQUESTED), anyLong(), anyString(), any());
|
||||
inserted(MailTemplate.REGISTRATION_RECEIVED, "alice@example.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+31
-24
@@ -10,6 +10,7 @@ import org.hibernate.exception.ConstraintViolationException;
|
||||
import auth_service.configuration.security.JwtService;
|
||||
import auth_service.modules.registration.api.dto.request.RegistrationRequest;
|
||||
import auth_service.modules.registration.exception.EmailAlreadyInUseException;
|
||||
import auth_service.modules.registration.exception.PendingLimitReachedException;
|
||||
import auth_service.modules.registration.exception.TooManyRegistrationAttemptsException;
|
||||
import auth_service.modules.registration.model.Registration;
|
||||
import auth_service.modules.registration.service.RegistrationService;
|
||||
@@ -83,7 +84,6 @@ class RegistrationControllerTest extends AbstractWebMvcTest {
|
||||
var request = RegistrationRequest.builder()
|
||||
.email("test@example.com")
|
||||
.password("SuperSecret123!")
|
||||
.inviteCode("test-invite-code")
|
||||
.build();
|
||||
|
||||
long expectedUserId = 42L;
|
||||
@@ -105,7 +105,6 @@ class RegistrationControllerTest extends AbstractWebMvcTest {
|
||||
var request = RegistrationRequest.builder()
|
||||
.email("test@example.com")
|
||||
.password("SuperSecret123!")
|
||||
.inviteCode("test-invite-code")
|
||||
.build();
|
||||
|
||||
when(registrationService.register(any(Registration.class), any()))
|
||||
@@ -119,7 +118,7 @@ class RegistrationControllerTest extends AbstractWebMvcTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void register_OldClientSendingASteamId_IsAcceptedAndTheIdIgnored() throws Exception {
|
||||
void register_OldClientSendingASteamIdAndAnInviteCode_IsAcceptedAndBothIgnored() throws Exception {
|
||||
when(registrationService.register(any(Registration.class), any())).thenReturn(42L);
|
||||
|
||||
mockMvc.perform(post("/api/registration/new")
|
||||
@@ -134,7 +133,6 @@ class RegistrationControllerTest extends AbstractWebMvcTest {
|
||||
var request = RegistrationRequest.builder()
|
||||
.email("test@example.com")
|
||||
.password("SuperSecret123!")
|
||||
.inviteCode("code")
|
||||
.build();
|
||||
when(registrationService.register(any(Registration.class), any()))
|
||||
.thenThrow(duplicate("users_email_lower"));
|
||||
@@ -154,7 +152,7 @@ class RegistrationControllerTest extends AbstractWebMvcTest {
|
||||
mockMvc.perform(post("/api/registration/new")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("""
|
||||
{"email":"test@example.com","password":"SuperSecret123!","inviteCode":"code","displayName":"Lobo"}"""))
|
||||
{"email":"test@example.com","password":"SuperSecret123!","displayName":"Lobo"}"""))
|
||||
.andExpect(status().isConflict())
|
||||
.andExpect(jsonPath("$.code").value("DISPLAY_NAME_ALREADY_IN_USE"));
|
||||
}
|
||||
@@ -166,7 +164,7 @@ class RegistrationControllerTest extends AbstractWebMvcTest {
|
||||
mockMvc.perform(post("/api/registration/new")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("""
|
||||
{"email":"test@example.com","password":"SuperSecret123!","inviteCode":"code","language":"de"}"""))
|
||||
{"email":"test@example.com","password":"SuperSecret123!","language":"de"}"""))
|
||||
.andExpect(status().isOk());
|
||||
|
||||
ArgumentCaptor<Registration> registration = ArgumentCaptor.forClass(Registration.class);
|
||||
@@ -179,7 +177,7 @@ class RegistrationControllerTest extends AbstractWebMvcTest {
|
||||
mockMvc.perform(post("/api/registration/new")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("""
|
||||
{"email":"test@example.com","password":"SuperSecret123!","inviteCode":"code","language":"fr"}"""))
|
||||
{"email":"test@example.com","password":"SuperSecret123!","language":"fr"}"""))
|
||||
.andExpect(status().isBadRequest())
|
||||
.andExpect(jsonPath("$.code").value("VALIDATION_FAILED"));
|
||||
}
|
||||
@@ -198,7 +196,7 @@ class RegistrationControllerTest extends AbstractWebMvcTest {
|
||||
mockMvc.perform(post("/api/registration/new")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("""
|
||||
{"email":"test@example.com","password":"SuperSecret123!","inviteCode":"code"}"""))
|
||||
{"email":"test@example.com","password":"SuperSecret123!"}"""))
|
||||
.andExpect(status().isInternalServerError())
|
||||
.andExpect(jsonPath("$.code").value("INTERNAL_ERROR"))
|
||||
.andExpect(content().string(not(containsString("secret detail"))));
|
||||
@@ -209,7 +207,7 @@ class RegistrationControllerTest extends AbstractWebMvcTest {
|
||||
mockMvc.perform(post("/api/registration/new")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("""
|
||||
{"email":"test@example.com","password":"SuperSecret123!","inviteCode":"code","displayName":"x"}"""))
|
||||
{"email":"test@example.com","password":"SuperSecret123!","displayName":"x"}"""))
|
||||
.andExpect(status().isBadRequest())
|
||||
.andExpect(jsonPath("$.code").value("VALIDATION_FAILED"))
|
||||
.andExpect(jsonPath("$.fields[0].field").value("displayName"));
|
||||
@@ -226,7 +224,7 @@ class RegistrationControllerTest extends AbstractWebMvcTest {
|
||||
mockMvc.perform(post("/api/registration/new")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("""
|
||||
{"email":"test@example.com","password":"SuperSecret123!","inviteCode":"code","displayName":"Lobo"}"""))
|
||||
{"email":"test@example.com","password":"SuperSecret123!","displayName":"Lobo"}"""))
|
||||
.andExpect(status().isOk());
|
||||
|
||||
// Assert
|
||||
@@ -245,7 +243,7 @@ class RegistrationControllerTest extends AbstractWebMvcTest {
|
||||
.header("X-Forwarded-For", "10.0.0.1, 203.0.113.7")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("""
|
||||
{"email":"test@example.com","password":"SuperSecret123!","inviteCode":"code"}"""))
|
||||
{"email":"test@example.com","password":"SuperSecret123!"}"""))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.userId").value(42L));
|
||||
}
|
||||
@@ -260,22 +258,33 @@ class RegistrationControllerTest extends AbstractWebMvcTest {
|
||||
mockMvc.perform(post("/api/registration/new")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("""
|
||||
{"email":"test@example.com","password":"SuperSecret123!","inviteCode":"code"}"""))
|
||||
{"email":"test@example.com","password":"SuperSecret123!"}"""))
|
||||
.andExpect(status().isTooManyRequests())
|
||||
.andExpect(header().string("Retry-After", "900"))
|
||||
.andExpect(jsonPath("$.code").value("TOO_MANY_ATTEMPTS"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void register_FullQueue_Is409PendingLimitReached() throws Exception {
|
||||
when(registrationService.register(any(Registration.class), any())).thenThrow(new PendingLimitReachedException());
|
||||
|
||||
mockMvc.perform(post("/api/registration/new")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("""
|
||||
{"email":"test@example.com","password":"SuperSecret123!"}"""))
|
||||
.andExpect(status().isConflict())
|
||||
.andExpect(jsonPath("$.code").value("PENDING_LIMIT_REACHED"));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@CsvSource(delimiter = '|', value = {
|
||||
" |SuperSecret123!|code",
|
||||
"no-at-sign|SuperSecret123!|code",
|
||||
"test@example.com| |code",
|
||||
"test@example.com|short|code",
|
||||
"test@example.com|SuperSecret123!| ",
|
||||
" |SuperSecret123!",
|
||||
"no-at-sign|SuperSecret123!",
|
||||
"test@example.com| ",
|
||||
"test@example.com|short",
|
||||
})
|
||||
void register_InvalidInput_Is400WithoutRegistering(String email, String password, String inviteCode) throws Exception {
|
||||
var request = RegistrationRequest.builder().email(email).password(password).inviteCode(inviteCode).build();
|
||||
void register_InvalidInput_Is400WithoutRegistering(String email, String password) throws Exception {
|
||||
var request = RegistrationRequest.builder().email(email).password(password).build();
|
||||
|
||||
mockMvc.perform(post("/api/registration/new")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
@@ -288,8 +297,7 @@ class RegistrationControllerTest extends AbstractWebMvcTest {
|
||||
// BCrypt uses only 72 bytes: a longer password would mostly be ignored, or refused with a 500.
|
||||
@Test
|
||||
void register_PasswordOver72Bytes_Is400() throws Exception {
|
||||
var request = RegistrationRequest.builder().email("test@example.com").password("é".repeat(37))
|
||||
.inviteCode("code").build();
|
||||
var request = RegistrationRequest.builder().email("test@example.com").password("é".repeat(37)).build();
|
||||
|
||||
mockMvc.perform(post("/api/registration/new")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
@@ -301,8 +309,7 @@ class RegistrationControllerTest extends AbstractWebMvcTest {
|
||||
|
||||
@Test
|
||||
void register_Email_IsTrimmedAndLowerCase() throws Exception {
|
||||
var request = RegistrationRequest.builder().email(" Bob@Example.COM ").password("SuperSecret123!")
|
||||
.inviteCode("code").build();
|
||||
var request = RegistrationRequest.builder().email(" Bob@Example.COM ").password("SuperSecret123!").build();
|
||||
when(registrationService.register(any(Registration.class), any())).thenReturn(1L);
|
||||
|
||||
mockMvc.perform(post("/api/registration/new")
|
||||
@@ -324,7 +331,7 @@ class RegistrationControllerTest extends AbstractWebMvcTest {
|
||||
mockMvc.perform(post("/api/registration/new")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("""
|
||||
{"email":"test@example.com","password":"SuperSecret123!","inviteCode":"code"}"""))
|
||||
{"email":"test@example.com","password":"SuperSecret123!"}"""))
|
||||
.andExpect(status().isInternalServerError())
|
||||
.andExpect(jsonPath("$.code").value("INTERNAL_ERROR"));
|
||||
|
||||
|
||||
+10
@@ -64,6 +64,16 @@ class RegisteredUserRepositoryTest extends AbstractPersistenceTest {
|
||||
assertEquals(false, settings.get("public_profile"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void save_StoresAPendingRequestAndCountsIt() {
|
||||
long before = registeredUserRepository.countPending();
|
||||
|
||||
long id = registeredUserRepository.save(registration("pending@example.com", null));
|
||||
|
||||
assertEquals("PENDING", jdbc.queryForObject("SELECT status FROM users WHERE id = ?", String.class, id));
|
||||
assertEquals(before + 1, registeredUserRepository.countPending());
|
||||
}
|
||||
|
||||
@Test
|
||||
void save_StoresTheLanguageOfTheRegistrationPage() {
|
||||
Registration registration = registration("lang@example.com", null);
|
||||
|
||||
+11
@@ -40,6 +40,17 @@ class RegistrationAttemptServiceTest extends AbstractUnitTest {
|
||||
assertDoesNotThrow(() -> attempts.acquire("5.6.7.8"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void takesAtMostThreeRequestsPerIp() {
|
||||
for (int i = 0; i < RegistrationAttemptService.MAX_REQUESTS_PER_IP; i++) {
|
||||
attempts.countRequest("1.2.3.4");
|
||||
}
|
||||
|
||||
assertThrows(TooManyRegistrationAttemptsException.class, () -> attempts.countRequest("1.2.3.4"));
|
||||
assertDoesNotThrow(() -> attempts.countRequest("5.6.7.8"));
|
||||
assertDoesNotThrow(() -> attempts.acquire("1.2.3.4"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void successDoesNotUseUpAnAttempt() {
|
||||
for (int i = 0; i < RegistrationAttemptService.MAX_FAILURES_PER_IP * 2; i++) {
|
||||
|
||||
+26
-15
@@ -1,8 +1,8 @@
|
||||
package auth_service.modules.registration.service;
|
||||
|
||||
import auth_service.modules.registration.exception.EmailAlreadyInUseException;
|
||||
import auth_service.modules.registration.exception.InvalidInvitationCodeException;
|
||||
import auth_service.modules.registration.exception.DisplayNameAlreadyRegisteredException;
|
||||
import auth_service.modules.registration.exception.PendingLimitReachedException;
|
||||
import auth_service.modules.registration.exception.TooManyRegistrationAttemptsException;
|
||||
import auth_service.modules.registration.model.Registration;
|
||||
import auth_service.modules.registration.persistence.RegisteredUserRepository;
|
||||
@@ -26,9 +26,6 @@ class RegistrationServiceTest extends AbstractUnitTest {
|
||||
|
||||
private static final String IP = "203.0.113.7";
|
||||
|
||||
@Mock
|
||||
private CodeValidationService codeValidationService;
|
||||
|
||||
@Mock
|
||||
private RegisteredUserRepository registeredUserRepository;
|
||||
|
||||
@@ -51,7 +48,6 @@ class RegistrationServiceTest extends AbstractUnitTest {
|
||||
validRegistration = Registration.builder()
|
||||
.email("test@example.com")
|
||||
.rawPassword("SuperSecret123!")
|
||||
.inviteCode("test-invite-code")
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -59,7 +55,6 @@ class RegistrationServiceTest extends AbstractUnitTest {
|
||||
void register_Success() {
|
||||
// Arrange
|
||||
long expectedUserId = 42L;
|
||||
when(codeValidationService.isCodeValid(validRegistration.getInviteCode())).thenReturn(true);
|
||||
when(registeredUserRepository.existsByEmail(validRegistration.getEmail())).thenReturn(false);
|
||||
when(passwordEncoder.encode(validRegistration.getRawPassword())).thenReturn("hashedPasswordXYZ");
|
||||
when(registeredUserRepository.save(any(Registration.class))).thenReturn(expectedUserId);
|
||||
@@ -74,16 +69,15 @@ class RegistrationServiceTest extends AbstractUnitTest {
|
||||
assertEquals("hashedPasswordXYZ", validRegistration.getEncryptedPassword());
|
||||
verify(registrationAttemptService).acquire(IP);
|
||||
verify(registrationAttemptService).release(IP);
|
||||
verify(accountMailer).userRegistered(expectedUserId);
|
||||
verify(registrationAttemptService).countRequest(IP);
|
||||
verify(accountMailer).registrationRequested(expectedUserId);
|
||||
}
|
||||
|
||||
@Test
|
||||
void register_InvalidInviteCode() {
|
||||
// Arrange
|
||||
when(codeValidationService.isCodeValid(validRegistration.getInviteCode())).thenReturn(false);
|
||||
void register_AFullQueue_RefusesAndCountsAsAFailedAttempt() {
|
||||
when(registeredUserRepository.countPending()).thenReturn((long) RegistrationService.MAX_PENDING);
|
||||
|
||||
// Act & Assert
|
||||
assertThrows(InvalidInvitationCodeException.class, () -> registrationService.register(validRegistration, IP));
|
||||
assertThrows(PendingLimitReachedException.class, () -> registrationService.register(validRegistration, IP));
|
||||
|
||||
verify(registeredUserRepository, never()).existsByEmail(anyString());
|
||||
verify(registeredUserRepository, never()).save(any());
|
||||
@@ -91,10 +85,27 @@ class RegistrationServiceTest extends AbstractUnitTest {
|
||||
verifyNoInteractions(accountMailer);
|
||||
}
|
||||
|
||||
@Test
|
||||
void register_OneBelowTheLimit_IsTaken() {
|
||||
when(registeredUserRepository.countPending()).thenReturn((long) RegistrationService.MAX_PENDING - 1);
|
||||
when(registeredUserRepository.save(any(Registration.class))).thenReturn(43L);
|
||||
|
||||
assertEquals(43L, registrationService.register(validRegistration, IP));
|
||||
}
|
||||
|
||||
@Test
|
||||
void register_TooManyRequestsFromOneIp_SavesNothing() {
|
||||
doThrow(new TooManyRegistrationAttemptsException(900)).when(registrationAttemptService).countRequest(IP);
|
||||
|
||||
assertThrows(TooManyRegistrationAttemptsException.class, () -> registrationService.register(validRegistration, IP));
|
||||
|
||||
verify(registeredUserRepository, never()).save(any());
|
||||
verifyNoInteractions(accountMailer);
|
||||
}
|
||||
|
||||
@Test
|
||||
void register_EmailAlreadyInUse() {
|
||||
// Arrange
|
||||
when(codeValidationService.isCodeValid(validRegistration.getInviteCode())).thenReturn(true);
|
||||
when(registeredUserRepository.existsByEmail(validRegistration.getEmail())).thenReturn(true);
|
||||
|
||||
// Act & Assert
|
||||
@@ -102,13 +113,13 @@ class RegistrationServiceTest extends AbstractUnitTest {
|
||||
|
||||
verify(registeredUserRepository, never()).save(any());
|
||||
verify(registrationAttemptService, never()).release(anyString());
|
||||
verify(registrationAttemptService, never()).countRequest(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void register_DisplayNameTaken() {
|
||||
// Arrange
|
||||
validRegistration.setDisplayName("Lobo");
|
||||
when(codeValidationService.isCodeValid(validRegistration.getInviteCode())).thenReturn(true);
|
||||
when(registeredUserRepository.existsByEmail(validRegistration.getEmail())).thenReturn(false);
|
||||
when(registeredUserRepository.existsByDisplayName("Lobo")).thenReturn(true);
|
||||
|
||||
@@ -125,7 +136,7 @@ class RegistrationServiceTest extends AbstractUnitTest {
|
||||
|
||||
// Act & Assert
|
||||
assertThrows(TooManyRegistrationAttemptsException.class, () -> registrationService.register(validRegistration, IP));
|
||||
verifyNoInteractions(codeValidationService, registeredUserRepository);
|
||||
verifyNoInteractions(registeredUserRepository);
|
||||
verify(registrationAttemptService, never()).release(anyString());
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import java.security.KeyPairGenerator;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Base64;
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* Base class for integration tests.
|
||||
@@ -43,6 +44,16 @@ public abstract class AbstractIntegrationTest {
|
||||
@Autowired
|
||||
private JdbcTemplate baseJdbc;
|
||||
|
||||
private static final AtomicInteger NEXT_CLIENT = new AtomicInteger();
|
||||
|
||||
/**
|
||||
* For X-Forwarded-For: a new client address per registration, because Auth takes at most 3 per address.
|
||||
*/
|
||||
protected static String freshClientIp() {
|
||||
int client = NEXT_CLIENT.incrementAndGet();
|
||||
return "198.18." + client / 250 + "." + (client % 250 + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* For users a test inserts by SQL. Registration inserts the row itself.
|
||||
*/
|
||||
@@ -58,7 +69,6 @@ public abstract class AbstractIntegrationTest {
|
||||
static void secrets(DynamicPropertyRegistry registry) {
|
||||
registry.add("security.jwt.private-key", () -> Base64.getEncoder().encodeToString(KEYS.getPrivate().getEncoded()));
|
||||
registry.add("security.jwt.public-key", () -> Base64.getEncoder().encodeToString(KEYS.getPublic().getEncoded()));
|
||||
registry.add("auth.registration.invitation-code", () -> "test-invite-code");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in new issue
Block a user