diff --git a/src/main/java/com/ericbouchut/learndev/common/config/SecurityConfig.java b/src/main/java/com/ericbouchut/learndev/common/config/SecurityConfig.java index 46efbe9..4320970 100644 --- a/src/main/java/com/ericbouchut/learndev/common/config/SecurityConfig.java +++ b/src/main/java/com/ericbouchut/learndev/common/config/SecurityConfig.java @@ -1,7 +1,9 @@ package com.ericbouchut.learndev.common.config; +import jakarta.servlet.DispatcherType; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; @@ -10,6 +12,8 @@ @Configuration @EnableWebSecurity +// Enables @PreAuthorize for service-level rules (e.g. course ownership). +@EnableMethodSecurity public class SecurityConfig { @Bean @@ -21,7 +25,13 @@ public PasswordEncoder passwordEncoder() { public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth + // The ERROR dispatch renders templates/error/*.html for a + // response already authorized (or denied) on its way in. + .dispatcherTypeMatchers(DispatcherType.ERROR).permitAll() .requestMatchers("/", "/privacy", "/auth/**", "/css/**", "/js/**", "/fonts/**").permitAll() + .requestMatchers("/instructor/**").hasRole("INSTRUCTOR") + .requestMatchers("/admin/**").hasRole("ADMIN") + .requestMatchers("/courses/**").authenticated() .anyRequest().authenticated()) .formLogin(form -> form .loginPage("/auth/login") // GET: show the login form diff --git a/src/main/resources/templates/error/403.html b/src/main/resources/templates/error/403.html new file mode 100644 index 0000000..c3f70ba --- /dev/null +++ b/src/main/resources/templates/error/403.html @@ -0,0 +1,17 @@ + + + + + + +
+

Access denied

+

You do not have permission to view this page.

+

+ Back to your dashboard +

+
+ + + + diff --git a/src/main/resources/templates/error/404.html b/src/main/resources/templates/error/404.html new file mode 100644 index 0000000..a040255 --- /dev/null +++ b/src/main/resources/templates/error/404.html @@ -0,0 +1,17 @@ + + + + + + +
+

Page not found

+

The page you are looking for does not exist or has moved.

+

+ Back to the home page +

+
+ + + + diff --git a/src/main/resources/templates/error/500.html b/src/main/resources/templates/error/500.html new file mode 100644 index 0000000..15b14f9 --- /dev/null +++ b/src/main/resources/templates/error/500.html @@ -0,0 +1,17 @@ + + + + + + +
+

Something went wrong

+

An unexpected error occurred on our side. Please try again in a moment.

+

+ Back to the home page +

+
+ + + + diff --git a/src/main/resources/templates/fragments/layout.html b/src/main/resources/templates/fragments/layout.html index 3a97133..3dceece 100644 --- a/src/main/resources/templates/fragments/layout.html +++ b/src/main/resources/templates/fragments/layout.html @@ -4,7 +4,8 @@ - head(title): document head with the design stylesheets - header(current): skip link + banner + auth-aware navigation; `current` names the nav entry of the calling page ('home', 'login', - 'register', 'dashboard' or null) and drives aria-current="page" + 'register', 'dashboard', 'instructor', 'admin' or null) and drives + aria-current="page" - footer: site footer Each page keeps its own (privacy.html is lang="fr"). --> @@ -38,6 +39,12 @@