diff --git a/pom.xml b/pom.xml
index d200d37..e4af5cb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -62,13 +62,15 @@
line-awesome
2.0.0
-
org.postgresql
postgresql
runtime
-
+
+ org.springframework.boot
+ spring-boot-starter-security
+
org.springframework.boot
spring-boot-starter-data-jpa
diff --git a/src/main/java/com/application/munera/SecurityConfiguration.java b/src/main/java/com/application/munera/SecurityConfiguration.java
new file mode 100644
index 0000000..a4b406b
--- /dev/null
+++ b/src/main/java/com/application/munera/SecurityConfiguration.java
@@ -0,0 +1,72 @@
+package com.application.munera;
+
+import com.application.munera.views.LoginView;
+import com.vaadin.flow.spring.security.VaadinWebSecurity;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.builders.WebSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.provisioning.InMemoryUserDetailsManager;
+import org.springframework.security.provisioning.UserDetailsManager;
+import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
+
+@EnableWebSecurity
+@Configuration
+public class SecurityConfiguration
+ extends VaadinWebSecurity {
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ // Delegating the responsibility of general configurations
+ // of http security to the super class. It's configuring
+ // the followings: Vaadin's CSRF protection by ignoring
+ // framework's internal requests, default request cache,
+ // ignoring public views annotated with @AnonymousAllowed,
+ // restricting access to other views/endpoints, and enabling
+ // NavigationAccessControl authorization.
+ // You can add any possible extra configurations of your own
+ // here (the following is just an example):
+
+ // http.rememberMe().alwaysRemember(false);
+
+ // Configure your static resources with public access before calling
+ // super.configure(HttpSecurity) as it adds final anyRequest matcher
+ http.authorizeHttpRequests(auth -> auth.requestMatchers(new AntPathRequestMatcher("/public/**"))
+ .permitAll());
+
+ super.configure(http);
+
+ // This is important to register your login view to the
+ // navigation access control mechanism:
+ setLoginView(http, LoginView.class);
+ }
+
+ @Override
+ public void configure(WebSecurity web) throws Exception {
+ // Customize your WebSecurity configuration.
+ super.configure(web);
+ }
+
+ /**
+ * Demo UserDetailsManager which only provides two hardcoded
+ * in memory users and their roles.
+ * NOTE: This shouldn't be used in real world applications.
+ */
+ @Bean
+ public UserDetailsManager userDetailsService() {
+ UserDetails user =
+ User.withUsername("user")
+ .password("{noop}user")
+ .roles("USER")
+ .build();
+ UserDetails admin =
+ User.withUsername("admin")
+ .password("{noop}admin")
+ .roles("ADMIN")
+ .build();
+ return new InMemoryUserDetailsManager(user, admin);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/application/munera/services/SecurityService.java b/src/main/java/com/application/munera/services/SecurityService.java
new file mode 100644
index 0000000..2af72c4
--- /dev/null
+++ b/src/main/java/com/application/munera/services/SecurityService.java
@@ -0,0 +1,33 @@
+package com.application.munera.services;
+
+import com.vaadin.flow.component.UI;
+import com.vaadin.flow.server.VaadinServletRequest;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
+import org.springframework.stereotype.Component;
+
+@Component
+public class SecurityService {
+
+ private static final String LOGOUT_SUCCESS_URL = "/";
+
+ public UserDetails getAuthenticatedUser() {
+ SecurityContext context = SecurityContextHolder.getContext();
+ Object principal = context.getAuthentication().getPrincipal();
+ if (principal instanceof UserDetails) {
+ return (UserDetails) context.getAuthentication().getPrincipal();
+ }
+ // Anonymous or no authentication.
+ return null;
+ }
+
+ public void logout() {
+ UI.getCurrent().getPage().setLocation(LOGOUT_SUCCESS_URL);
+ SecurityContextLogoutHandler logoutHandler = new SecurityContextLogoutHandler();
+ logoutHandler.logout(
+ VaadinServletRequest.getCurrent().getHttpServletRequest(), null,
+ null);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/application/munera/views/LoginView.java b/src/main/java/com/application/munera/views/LoginView.java
new file mode 100644
index 0000000..eea7a22
--- /dev/null
+++ b/src/main/java/com/application/munera/views/LoginView.java
@@ -0,0 +1,43 @@
+package com.application.munera.views;
+
+import com.vaadin.flow.component.html.H1;
+import com.vaadin.flow.component.html.H2;
+import com.vaadin.flow.component.login.LoginForm;
+import com.vaadin.flow.component.orderedlayout.VerticalLayout;
+import com.vaadin.flow.router.BeforeEnterEvent;
+import com.vaadin.flow.router.BeforeEnterObserver;
+import com.vaadin.flow.router.PageTitle;
+import com.vaadin.flow.router.Route;
+import com.vaadin.flow.server.auth.AnonymousAllowed;
+
+
+
+@Route("login")
+@PageTitle("Login")
+@AnonymousAllowed
+public class LoginView extends VerticalLayout implements BeforeEnterObserver {
+
+ private LoginForm login = new LoginForm();
+
+ public LoginView() {
+ addClassName("login-view");
+ setSizeFull();
+
+ setJustifyContentMode(JustifyContentMode.CENTER);
+ setAlignItems(Alignment.CENTER);
+
+ login.setAction("login");
+
+ add(new H1("Munera"), new H2("An expense tracking application"), login);
+ }
+
+ @Override
+ public void beforeEnter(BeforeEnterEvent beforeEnterEvent) {
+ if(beforeEnterEvent.getLocation()
+ .getQueryParameters()
+ .getParameters()
+ .containsKey("error")) {
+ login.setError(true);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/application/munera/views/MainLayout.java b/src/main/java/com/application/munera/views/MainLayout.java
index a078beb..2a71d5f 100644
--- a/src/main/java/com/application/munera/views/MainLayout.java
+++ b/src/main/java/com/application/munera/views/MainLayout.java
@@ -3,14 +3,18 @@ package com.application.munera.views;
import com.application.munera.views.expenses.*;
import com.vaadin.flow.component.applayout.AppLayout;
import com.vaadin.flow.component.applayout.DrawerToggle;
+import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.html.Footer;
import com.vaadin.flow.component.html.H1;
import com.vaadin.flow.component.html.Header;
import com.vaadin.flow.component.html.Span;
+import com.vaadin.flow.component.orderedlayout.FlexComponent;
+import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.Scroller;
import com.vaadin.flow.component.sidenav.SideNav;
import com.vaadin.flow.component.sidenav.SideNavItem;
import com.vaadin.flow.router.PageTitle;
+import com.vaadin.flow.spring.security.AuthenticationContext;
import com.vaadin.flow.theme.lumo.LumoUtility;
import org.vaadin.lineawesome.LineAwesomeIcon;
@@ -20,8 +24,10 @@ import org.vaadin.lineawesome.LineAwesomeIcon;
public class MainLayout extends AppLayout {
private H1 viewTitle;
+ private final transient AuthenticationContext authContext;
- public MainLayout() {
+ public MainLayout(AuthenticationContext authContext) {
+ this.authContext = authContext;
setPrimarySection(Section.DRAWER);
addDrawerContent();
addHeaderContent();
@@ -34,7 +40,22 @@ public class MainLayout extends AppLayout {
viewTitle = new H1();
viewTitle.addClassNames(LumoUtility.FontSize.LARGE, LumoUtility.Margin.NONE);
+ // Creating the logout button
+ Button logout = new Button("Logout", click -> this.authContext.logout());
+
+ // Adding some padding to the logout button
+ logout.getStyle().set("padding", "10px");
+
+ // Creating the header and adding the logout button to the far left
+ HorizontalLayout header = new HorizontalLayout(logout);
+ header.setWidthFull(); // Make the header take the full width
+ header.setDefaultVerticalComponentAlignment(FlexComponent.Alignment.CENTER);
+ header.setJustifyContentMode(FlexComponent.JustifyContentMode.END); // Align items to the start (left)
+ header.getStyle().set("padding", "0 10px"); // Add padding around the header if needed
+
+
addToNavbar(true, toggle, viewTitle);
+ addToNavbar(header);
}
private void addDrawerContent() {
diff --git a/src/main/java/com/application/munera/views/expenses/CategoriesView.java b/src/main/java/com/application/munera/views/expenses/CategoriesView.java
index 2c4162c..a694221 100644
--- a/src/main/java/com/application/munera/views/expenses/CategoriesView.java
+++ b/src/main/java/com/application/munera/views/expenses/CategoriesView.java
@@ -22,12 +22,14 @@ import com.vaadin.flow.data.binder.BeanValidationBinder;
import com.vaadin.flow.data.binder.ValidationException;
import com.vaadin.flow.router.*;
import com.vaadin.flow.spring.data.VaadinSpringDataHelpers;
+import jakarta.annotation.security.PermitAll;
import org.springframework.data.domain.PageRequest;
import org.springframework.orm.ObjectOptimisticLockingFailureException;
import java.util.Optional;
@PageTitle("Categories")
+@PermitAll
@Route(value = "categories/:categoryID?/:action?(edit)", layout = MainLayout.class)
@Uses(Icon.class)
public class CategoriesView extends Div implements BeforeEnterObserver {
diff --git a/src/main/java/com/application/munera/views/expenses/DashboardView.java b/src/main/java/com/application/munera/views/expenses/DashboardView.java
index 8149e16..7608154 100644
--- a/src/main/java/com/application/munera/views/expenses/DashboardView.java
+++ b/src/main/java/com/application/munera/views/expenses/DashboardView.java
@@ -11,6 +11,7 @@ import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
+import jakarta.annotation.security.PermitAll;
import java.math.BigDecimal;
import java.time.Year;
@@ -20,6 +21,7 @@ import java.util.*;
import java.util.stream.Collectors;
//@HtmlImport("frontend://styles/shared-styles.html") // If you have custom styles
+@PermitAll
@PageTitle("Dashboard")
@Route(value = "dashboard", layout = MainLayout.class)
public class DashboardView extends Div {
diff --git a/src/main/java/com/application/munera/views/expenses/EventsView.java b/src/main/java/com/application/munera/views/expenses/EventsView.java
index c38c553..47aabb3 100644
--- a/src/main/java/com/application/munera/views/expenses/EventsView.java
+++ b/src/main/java/com/application/munera/views/expenses/EventsView.java
@@ -28,12 +28,14 @@ import com.vaadin.flow.router.BeforeEnterObserver;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.spring.data.VaadinSpringDataHelpers;
+import jakarta.annotation.security.PermitAll;
import org.springframework.data.domain.PageRequest;
import org.springframework.orm.ObjectOptimisticLockingFailureException;
import java.util.Optional;
@PageTitle("Events")
+@PermitAll
@Route(value = "events/:eventID?/:action?(edit)", layout = MainLayout.class)
@Uses(Icon.class)
public class EventsView extends Div implements BeforeEnterObserver {
diff --git a/src/main/java/com/application/munera/views/expenses/ExpensesView.java b/src/main/java/com/application/munera/views/expenses/ExpensesView.java
index 4ad3e44..b02b8d1 100644
--- a/src/main/java/com/application/munera/views/expenses/ExpensesView.java
+++ b/src/main/java/com/application/munera/views/expenses/ExpensesView.java
@@ -30,6 +30,7 @@ import com.vaadin.flow.data.binder.BeanValidationBinder;
import com.vaadin.flow.data.binder.ValidationException;
import com.vaadin.flow.data.renderer.ComponentRenderer;
import com.vaadin.flow.router.*;
+import jakarta.annotation.security.PermitAll;
import org.springframework.orm.ObjectOptimisticLockingFailureException;
import org.vaadin.klaudeta.PaginatedGrid;
@@ -38,6 +39,7 @@ import java.util.Objects;
import java.util.Optional;
import java.util.Set;
+@PermitAll
@PageTitle("Expenses")
@Route(value = "/:expenseID?/:action?(edit)", layout = MainLayout.class)
@RouteAlias(value = "", layout = MainLayout.class)
diff --git a/src/main/java/com/application/munera/views/expenses/PeopleView.java b/src/main/java/com/application/munera/views/expenses/PeopleView.java
index b7bf2cf..01af164 100644
--- a/src/main/java/com/application/munera/views/expenses/PeopleView.java
+++ b/src/main/java/com/application/munera/views/expenses/PeopleView.java
@@ -28,6 +28,7 @@ import com.vaadin.flow.router.BeforeEnterObserver;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.spring.data.VaadinSpringDataHelpers;
+import jakarta.annotation.security.PermitAll;
import org.springframework.data.domain.PageRequest;
import org.springframework.orm.ObjectOptimisticLockingFailureException;
@@ -35,6 +36,7 @@ import java.math.BigDecimal;
import java.util.Optional;
@PageTitle("People")
+@PermitAll
@Route(value = "people/:personID?/:action?(edit)", layout = MainLayout.class)
@Uses(Icon.class)
public class PeopleView extends Div implements BeforeEnterObserver {