diff --git a/autograde-service/src/main/java/ch/epfl/autograde/config/SecurityConfig.java b/autograde-service/src/main/java/ch/epfl/autograde/config/SecurityConfig.java index 59c5d0753cda5567300ffe91513084d2a3f219a4..1edda7f24421b7cf343ab4669fdf5b2f508b828d 100644 --- a/autograde-service/src/main/java/ch/epfl/autograde/config/SecurityConfig.java +++ b/autograde-service/src/main/java/ch/epfl/autograde/config/SecurityConfig.java @@ -13,7 +13,6 @@ import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.ldap.core.support.BaseLdapPathContextSource; import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.ProviderManager; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; @@ -47,10 +46,10 @@ public class SecurityConfig { @Bean @Order(1) public SecurityFilterChain filterChain(HttpSecurity http, ShareSecretAuthenticationProvider provider) throws Exception { - http.apply(new SharedSecretConfigurer<>()); return http .securityMatcher("/api/**") - .authenticationManager(new ProviderManager(provider)) + .with(new SharedSecretConfigurer<>(), withDefaults()) + .authenticationProvider(provider) .csrf(AbstractHttpConfigurer::disable) .anonymous(AbstractHttpConfigurer::disable) .httpBasic(AbstractHttpConfigurer::disable) @@ -94,18 +93,23 @@ public class SecurityConfig { // Match on anything but the api endpoints (these are authentication via the shared secret scheme only) .securityMatcher(new NegatedRequestMatcher(new MvcRequestMatcher(introspector, "/api/**"))) .authenticationManager(manager) - .httpBasic(withDefaults()) + .formLogin(conf -> { + conf.loginPage("/login"); + conf.usernameParameter("username"); + conf.passwordParameter("password"); + conf.permitAll(); + }) .csrf(AbstractHttpConfigurer::disable) .anonymous(AbstractHttpConfigurer::disable) .requestCache(RequestCacheConfigurer::disable) .rememberMe(AbstractHttpConfigurer::disable) // Disable session management until we figure a way for replicated and distributed services .sessionManagement(AbstractHttpConfigurer::disable) - .formLogin(AbstractHttpConfigurer::disable) .authorizeHttpRequests(auth -> { - auth.requestMatchers("/error", "/css/error-pages.css", "/images/favicons/**").permitAll(); + auth.requestMatchers("/error", "/css/error-pages.css", "/images/favicons/**", "/images/logos/**", "/images/pictograms/**").permitAll(); auth.anyRequest().hasAuthority(AutogradeAuthorities.SYSTEM_ACCESS.getAuthority()); }) + .logout(withDefaults()) .build(); } diff --git a/autograde-service/src/main/java/ch/epfl/autograde/controller/IndexController.java b/autograde-service/src/main/java/ch/epfl/autograde/controller/IndexController.java index c5cbf22ba73dc5f69ccc477108e5d9402c86770b..a119ff4f412e06c52b728fec100a6a9962c8471e 100644 --- a/autograde-service/src/main/java/ch/epfl/autograde/controller/IndexController.java +++ b/autograde-service/src/main/java/ch/epfl/autograde/controller/IndexController.java @@ -1,7 +1,14 @@ package ch.epfl.autograde.controller; import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; @Controller public class IndexController { + + @GetMapping("/login") + private String login() { + return "login"; + } + } diff --git a/autograde-service/src/main/resources/static/images/logos/epfl-logo-black.png b/autograde-service/src/main/resources/static/images/logos/epfl-logo-black.png new file mode 100755 index 0000000000000000000000000000000000000000..1bf0b67d59a5d3fca19787b65cd28134649f5568 Binary files /dev/null and b/autograde-service/src/main/resources/static/images/logos/epfl-logo-black.png differ diff --git a/autograde-service/src/main/resources/static/images/logos/epfl-logo-red.png b/autograde-service/src/main/resources/static/images/logos/epfl-logo-red.png new file mode 100755 index 0000000000000000000000000000000000000000..f888916ddab84cb67a01d142c31c64c7d47afd6c Binary files /dev/null and b/autograde-service/src/main/resources/static/images/logos/epfl-logo-red.png differ diff --git a/autograde-service/src/main/resources/static/images/logos/epfl-logo-white.png b/autograde-service/src/main/resources/static/images/logos/epfl-logo-white.png new file mode 100755 index 0000000000000000000000000000000000000000..14c767515a1e880d51a8474229bb8d65f640b516 Binary files /dev/null and b/autograde-service/src/main/resources/static/images/logos/epfl-logo-white.png differ diff --git a/autograde-service/src/main/resources/static/images/pictograms/circle-cross.svg b/autograde-service/src/main/resources/static/images/pictograms/circle-cross.svg new file mode 100644 index 0000000000000000000000000000000000000000..9b0726f52f7350d78e561d59561a820cf1572611 --- /dev/null +++ b/autograde-service/src/main/resources/static/images/pictograms/circle-cross.svg @@ -0,0 +1,4 @@ +<svg class="h-5 w-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true"> + <circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2" fill="none" /> + <path d="M15 9L9 15M9 9l6 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" /> +</svg> \ No newline at end of file diff --git a/autograde-service/src/main/resources/static/images/pictograms/cross.svg b/autograde-service/src/main/resources/static/images/pictograms/cross.svg new file mode 100644 index 0000000000000000000000000000000000000000..98d31f9d3726f94c773b56e0455074f60eaf8821 --- /dev/null +++ b/autograde-service/src/main/resources/static/images/pictograms/cross.svg @@ -0,0 +1,3 @@ +<svg class="h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"> + <path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd" /> +</svg> \ No newline at end of file diff --git a/autograde-service/src/main/resources/templates/fragments/error-notification.html b/autograde-service/src/main/resources/templates/fragments/error-notification.html new file mode 100644 index 0000000000000000000000000000000000000000..31447fd09238e291fcd7e0049bdf5b2526bd7d07 --- /dev/null +++ b/autograde-service/src/main/resources/templates/fragments/error-notification.html @@ -0,0 +1,35 @@ +<div aria-live="assertive" class="fixed inset-0 flex items-start px-4 py-6 pointer-events-none sm:p-6 sm:items-start"> + <div class="w-full flex flex-col items-center space-y-4 sm:items-end"> + <div id="notification" class="max-w-sm w-full bg-white shadow-lg rounded-lg pointer-events-auto ring-1 ring-black ring-opacity-5 overflow-hidden transition-opacity duration-500 ease-in-out opacity-100"> + <div class="p-4"> + <div class="flex items-center justify-between"> + <div class="flex items-center"> + <div class="flex-shrink-0"> + <!-- TODO: Use the pictograms from /images/pictograms/ --> + <svg class="h-5 w-5 text-red-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true"> + <circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2" fill="none" /> + <path d="M15 9L9 15M9 9l6 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" /> + </svg> + </div> + <p class="ml-3 text-sm font-medium text-gray-900">Error</p> + </div> + <button type="button" onclick="closeAlert()" class="bg-white rounded-md inline-flex text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500"> + <span class="sr-only">Close</span> + <!-- TODO: Use the pictograms from /images/pictograms/ --> + <svg class="h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"> + <path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd" /> + </svg> + </button> + </div> + <p class="mt-1 text-sm text-gray-500">Invalid username or password.</p> + </div> + </div> + </div> +</div> +<script> + function closeAlert() { + const notification = document.getElementById('notification'); + notification.classList.replace('opacity-100', 'opacity-0'); + setTimeout(() => notification.style.display = 'none', 500); + } +</script> diff --git a/autograde-service/src/main/resources/templates/login.html b/autograde-service/src/main/resources/templates/login.html new file mode 100644 index 0000000000000000000000000000000000000000..401fb95774dc006efef0d5f4ddaeb4645a954447 --- /dev/null +++ b/autograde-service/src/main/resources/templates/login.html @@ -0,0 +1,68 @@ +<!DOCTYPE html> +<html xmlns:th="https://www.thymeleaf.org" lang="en"> + <head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Login - AUTOGRADE</title> + <script src="https://cdn.tailwindcss.com"></script> + <link rel="shortcut icon" th:href="@{/images/favicons/favicon.ico}"> + <link rel="apple-touch-icon-precomposed" th:href="@{/images/favicons/favicon-152.png}"> + <link rel="icon" th:href="@{/images/favicons/favicon-16.png}" sizes="16x16"> + <link rel="icon" th:href="@{/images/favicons/favicon-32.png}" sizes="32x32"> + <link rel="icon" th:href="@{/images/favicons/favicon-57.png}" sizes="57x57"> + <link rel="icon" th:href="@{/images/favicons/favicon-76.png}" sizes="76x76"> + <link rel="icon" th:href="@{/images/favicons/favicon-96.png}" sizes="96x96"> + <link rel="icon" th:href="@{/images/favicons/favicon-120.png}" sizes="120x120"> + <link rel="icon" th:href="@{/images/favicons/favicon-128.png}" sizes="128x128"> + <link rel="icon" th:href="@{/images/favicons/favicon-152.png}" sizes="152x152"> + <link rel="icon" th:href="@{/images/favicons/favicon-180.png}" sizes="180x180"> + <link rel="shortcut icon" th:href="@{/images/favicons/android-chrome-192x192.png}" sizes="192x192"> + <link rel="shortcut icon" th:href="@{/images/favicons/android-chrome-512x512.png}" sizes="512x512"> + <link rel="icon" th:href="@{/images/favicons/favicon-228.png}" sizes="228x228"> + <link rel="apple-touch-icon" th:href="@{/images/favicons/apple-touch-icon.png}"> + <link rel="apple-touch-icon" th:href="@{/images/favicons/favicon-152.png}" sizes="152x152"> + <link rel="apple-touch-icon" th:href="@{/images/favicons/favicon-180.png}" sizes="180x180"> + </head> + <body class="h-screen bg-gray-50"> + + <th:block th:if="${param.error}"> + <div th:replace="fragments/error-notification"></div> + </th:block> + + <div class="flex h-full items-center justify-center px-6 py-12 lg:px-8"> + <div class="max-w-lg mx-auto bg-white shadow-lg rounded-lg p-6"> + <div class="sm:mx-auto sm:w-full sm:max-w-sm"> + <img class="mx-auto w-auto" th:src="@{/images/logos/epfl-logo-red.png}" alt="EPFL Logo"> + <h2 class="text-center text-2xl/9 font-bold tracking-tight text-gray-900">Sign in to Autograde</h2> + </div> + <hr class="my-6 border-gray-200"> + <div class="mt-10 sm:mx-auto sm:w-full sm:max-w-sm"> + <form class="space-y-6" th:action="@{/login}" method="POST"> + <div> + <label for="username" class="block text-sm/6 font-medium text-gray-900">Username</label> + <div class="mt-2"> + <input type="text" name="username" id="username" autocomplete="username" required class="block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-red-600 sm:text-sm/6"> + </div> + </div> + + <div> + <div class="flex items-center justify-between"> + <label for="password" class="block text-sm/6 font-medium text-gray-900">Password</label> + <div class="text-sm"> + <a th:href="@{/forgot-password}" class="font-semibold text-red-600 hover:text-red-500">Forgot password?</a> + </div> + </div> + <div class="mt-2"> + <input type="password" name="password" id="password" autocomplete="current-password" required class="block w-full rounded-md bg-white px-3 py-1.5 text-base text-gray-900 outline outline-1 -outline-offset-1 outline-gray-300 placeholder:text-gray-400 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-red-600 sm:text-sm/6"> + </div> + </div> + + <div> + <button type="submit" class="flex w-full justify-center rounded-md bg-red-600 px-3 py-1.5 text-sm/6 font-semibold text-white shadow-sm hover:bg-red-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600">Sign in</button> + </div> + </form> + </div> + </div> + </div> + </body> +</html>