Skip to content

Commit e727b13

Browse files
committed
Modelate backoffice authentication
1 parent 77717f4 commit e727b13

File tree

20 files changed

+382
-16
lines changed

20 files changed

+382
-16
lines changed

apps/main/tv/codely/apps/backoffice/backend/config/BackofficeBackendServerConfiguration.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,22 @@
33
import org.springframework.boot.web.servlet.FilterRegistrationBean;
44
import org.springframework.context.annotation.Bean;
55
import org.springframework.context.annotation.Configuration;
6-
import tv.codely.shared.infrastructure.spring.BasicHttpAuthMiddleware;
6+
import tv.codely.apps.backoffice.backend.middleware.BasicHttpAuthMiddleware;
7+
import tv.codely.shared.domain.bus.command.CommandBus;
78

89
@Configuration
910
public class BackofficeBackendServerConfiguration {
11+
private final CommandBus bus;
12+
13+
public BackofficeBackendServerConfiguration(CommandBus bus) {
14+
this.bus = bus;
15+
}
16+
1017
@Bean
1118
public FilterRegistrationBean<BasicHttpAuthMiddleware> basicHttpAuthMiddleware() {
1219
FilterRegistrationBean<BasicHttpAuthMiddleware> registrationBean = new FilterRegistrationBean<>();
1320

14-
registrationBean.setFilter(new BasicHttpAuthMiddleware());
21+
registrationBean.setFilter(new BasicHttpAuthMiddleware(bus));
1522
registrationBean.addUrlPatterns("/health-check");
1623

1724
return registrationBean;

src/shared/main/tv/codely/shared/infrastructure/spring/BasicHttpAuthMiddleware.java renamed to apps/main/tv/codely/apps/backoffice/backend/middleware/BasicHttpAuthMiddleware.java

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,24 @@
1-
package tv.codely.shared.infrastructure.spring;
1+
package tv.codely.apps.backoffice.backend.middleware;
2+
3+
import tv.codely.backoffice.auth.application.authenticate.AuthenticateUserCommand;
4+
import tv.codely.backoffice.auth.domain.InvalidAuthCredentials;
5+
import tv.codely.backoffice.auth.domain.InvalidAuthUsername;
6+
import tv.codely.shared.domain.bus.command.CommandBus;
7+
import tv.codely.shared.domain.bus.command.CommandHandlerExecutionError;
8+
import tv.codely.shared.domain.bus.command.CommandNotRegisteredError;
29

310
import javax.servlet.*;
411
import javax.servlet.http.HttpServletRequest;
512
import javax.servlet.http.HttpServletResponse;
613
import java.io.IOException;
714
import java.util.Base64;
8-
import java.util.HashMap;
915

1016
public final class BasicHttpAuthMiddleware implements Filter {
11-
private final HashMap<String, String> validUsers = new HashMap<String, String>() {{
12-
put("javi", "barbitas");
13-
put("rafa", "pelazo");
14-
}};
17+
private final CommandBus bus;
18+
19+
public BasicHttpAuthMiddleware(CommandBus bus) {
20+
this.bus = bus;
21+
}
1522

1623
@Override
1724
public void doFilter(
@@ -42,18 +49,17 @@ private void authenticate(
4249
String user = auth[0];
4350
String pass = auth[1];
4451

45-
if (isValid(user, pass)) {
52+
try {
53+
bus.dispatch(new AuthenticateUserCommand(user, pass));
54+
4655
request.setAttribute("authentication_username", user);
56+
4757
chain.doFilter(request, response);
48-
} else {
58+
} catch (InvalidAuthUsername | InvalidAuthCredentials | CommandHandlerExecutionError | CommandNotRegisteredError error) {
4959
setInvalidCredentials(response);
5060
}
5161
}
5262

53-
private boolean isValid(String user, String pass) {
54-
return validUsers.containsKey(user) && validUsers.get(user).equals(pass);
55-
}
56-
5763
private String[] decodeAuth(String authString) {
5864
return new String(Base64.getDecoder().decode(authString.split("\\s+")[1])).split(":");
5965
}

apps/test/tv/codely/apps/ApplicationTestCase.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import org.springframework.beans.factory.annotation.Autowired;
55
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
66
import org.springframework.boot.test.context.SpringBootTest;
7+
import org.springframework.http.HttpHeaders;
78
import org.springframework.http.HttpMethod;
89
import org.springframework.test.context.junit4.SpringRunner;
910
import org.springframework.test.web.servlet.MockMvc;
@@ -43,6 +44,22 @@ protected void assertResponse(
4344
.andExpect(response);
4445
}
4546

47+
protected void assertResponse(
48+
String endpoint,
49+
Integer expectedStatusCode,
50+
String expectedResponse,
51+
HttpHeaders headers
52+
) throws Exception {
53+
ResultMatcher response = expectedResponse.isEmpty()
54+
? content().string("")
55+
: content().json(expectedResponse);
56+
57+
mockMvc
58+
.perform(get(endpoint).headers(headers))
59+
.andExpect(status().is(expectedStatusCode))
60+
.andExpect(response);
61+
}
62+
4663
protected void assertRequestWithBody(
4764
String method,
4865
String endpoint,
Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,36 @@
11
package tv.codely.apps.backoffice.backend.controller.health_check;
22

33
import org.junit.jupiter.api.Test;
4+
import org.springframework.http.HttpHeaders;
45
import tv.codely.apps.ApplicationTestCase;
56

67
final class HealthCheckGetControllerShould extends ApplicationTestCase {
78
@Test
8-
void check_the_app_is_working_ok() throws Exception {
9-
assertResponse("/health-check", 200, "{'application':'backoffice_backend','status':'ok'}");
9+
void check_the_app_is_working_ok_with_valid_credentials() throws Exception {
10+
HttpHeaders headers = new HttpHeaders();
11+
headers.setBasicAuth("javi", "barbitas");
12+
13+
assertResponse("/health-check", 200, "{'application':'backoffice_backend','status':'ok'}", headers);
14+
}
15+
16+
@Test
17+
void not_check_the_app_is_working_ok_with_invalid_credentials() throws Exception {
18+
HttpHeaders headers = new HttpHeaders();
19+
headers.setBasicAuth("tipo_de_incognito", "homer.sampson");
20+
21+
assertResponse("/health-check", 403, "", headers);
22+
}
23+
24+
@Test
25+
void not_check_the_app_is_working_ok_with_invalid_credentials_of_an_existing_user() throws Exception {
26+
HttpHeaders headers = new HttpHeaders();
27+
headers.setBasicAuth("rafa", "incorrect.password");
28+
29+
assertResponse("/health-check", 403, "", headers);
30+
}
31+
32+
@Test
33+
void not_check_the_app_is_working_ok_with_no_credentials() throws Exception {
34+
assertResponse("/health-check", 401, "");
1035
}
1136
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package tv.codely.backoffice.auth.application.authenticate;
2+
3+
import tv.codely.shared.domain.bus.command.Command;
4+
5+
public final class AuthenticateUserCommand implements Command {
6+
private final String username;
7+
private final String password;
8+
9+
public AuthenticateUserCommand(String username, String password) {
10+
this.username = username;
11+
this.password = password;
12+
}
13+
14+
public String username() {
15+
return username;
16+
}
17+
18+
public String password() {
19+
return password;
20+
}
21+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package tv.codely.backoffice.auth.application.authenticate;
2+
3+
import tv.codely.backoffice.auth.domain.AuthPassword;
4+
import tv.codely.backoffice.auth.domain.AuthUsername;
5+
import tv.codely.shared.domain.Service;
6+
import tv.codely.shared.domain.bus.command.CommandHandler;
7+
8+
@Service
9+
public final class AuthenticateUserCommandHandler implements CommandHandler<AuthenticateUserCommand> {
10+
private final UserAuthenticator authenticator;
11+
12+
public AuthenticateUserCommandHandler(UserAuthenticator authenticator) {
13+
this.authenticator = authenticator;
14+
}
15+
16+
@Override
17+
public void handle(AuthenticateUserCommand command) {
18+
AuthUsername username = new AuthUsername(command.username());
19+
AuthPassword password = new AuthPassword(command.password());
20+
21+
authenticator.authenticate(username, password);
22+
}
23+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package tv.codely.backoffice.auth.application.authenticate;
2+
3+
import tv.codely.backoffice.auth.domain.*;
4+
import tv.codely.shared.domain.Service;
5+
6+
import java.util.Optional;
7+
8+
@Service
9+
public final class UserAuthenticator {
10+
private final AuthRepository repository;
11+
12+
public UserAuthenticator(AuthRepository repository) {
13+
this.repository = repository;
14+
}
15+
16+
public void authenticate(AuthUsername username, AuthPassword password) {
17+
Optional<AuthUser> auth = repository.search(username);
18+
19+
ensureUserExist(auth, username);
20+
ensureCredentialsAreValid(auth.get(), password);
21+
}
22+
23+
private void ensureUserExist(Optional<AuthUser> auth, AuthUsername username) {
24+
if (!auth.isPresent()) {
25+
throw new InvalidAuthUsername(username);
26+
}
27+
}
28+
29+
private void ensureCredentialsAreValid(AuthUser auth, AuthPassword password) {
30+
if (!auth.passwordMatches(password)) {
31+
throw new InvalidAuthCredentials(auth.username());
32+
}
33+
}
34+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package tv.codely.backoffice.auth.domain;
2+
3+
import tv.codely.shared.domain.StringValueObject;
4+
5+
public final class AuthPassword extends StringValueObject {
6+
public AuthPassword(String value) {
7+
super(value);
8+
}
9+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package tv.codely.backoffice.auth.domain;
2+
3+
import java.util.Optional;
4+
5+
public interface AuthRepository {
6+
Optional<AuthUser> search(AuthUsername username);
7+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package tv.codely.backoffice.auth.domain;
2+
3+
public final class AuthUser {
4+
private final AuthUsername username;
5+
private final AuthPassword password;
6+
7+
public AuthUser(AuthUsername username, AuthPassword password) {
8+
this.username = username;
9+
this.password = password;
10+
}
11+
12+
public AuthUsername username() {
13+
return username;
14+
}
15+
16+
public boolean passwordMatches(AuthPassword password) {
17+
return this.password.equals(password);
18+
}
19+
}

0 commit comments

Comments
 (0)