Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update header for georchestra and update login page #149

Merged
merged 4 commits into from
Nov 28, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
package org.georchestra.gateway.app;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Pair;
import org.georchestra.gateway.security.GeorchestraGatewaySecurityConfigProperties;
import org.georchestra.gateway.security.GeorchestraGatewaySecurityConfigProperties.Server;
import org.georchestra.gateway.security.GeorchestraUserMapper;
Expand All @@ -37,6 +38,7 @@
import org.springframework.context.event.EventListener;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
Expand All @@ -50,6 +52,7 @@
import javax.annotation.PostConstruct;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
Expand All @@ -70,7 +73,13 @@ public class GeorchestraGatewayApplication {

private @Autowired(required = false) OAuth2ClientProperties oauth2ClientConfig;
private @Value("${georchestra.gateway.headerEnabled:true}") boolean headerEnabled;

// defined in georchestra datadir's default.properties
private @Value("${georchestraStylesheet:}") String georchestraStylesheet;
private @Value("${useLegacyHeader:false}") boolean useLegacyHeader;
private @Value("${headerUrl:/header/}") String headerUrl;
private @Value("${headerHeight:90}") int headerHeight;
private @Value("${logoUrl:}") String logoUrl;
private @Value("${headerScript:https://cdn.jsdelivr.net/gh/georchestra/header@dist/header.js}") String headerScript;
private @Value("${spring.messages.basename:}") String messagesBasename;

Expand Down Expand Up @@ -108,26 +117,29 @@ public Mono<Map<String, Object>> whoami(Authentication principal, ServerWebExcha

@GetMapping(path = "/logout")
public String logout(Model mdl) {
mdl.addAttribute("header_enabled", headerEnabled);
setHeaderAttributes(mdl);
return "logout";
}

@GetMapping(path = "/login")
public String loginPage(@RequestParam Map<String, String> allRequestParams, Model mdl) {
Map<String, String> oauth2LoginLinks = new HashMap<>();
Map<String, Pair<String, String>> oauth2LoginLinks = new HashMap<>();
if (oauth2ClientConfig != null) {
oauth2ClientConfig.getRegistration().forEach((k, v) -> {
String clientName = Optional.ofNullable(v.getClientName()).orElse(k);
oauth2LoginLinks.put("/oauth2/authorization/" + k, clientName);

String providerPath = Paths.get("login/img/", k + ".png").toString();
String logo = new ClassPathResource("static/" + providerPath).exists() ? providerPath
: "login/img/default.png";
oauth2LoginLinks.put("/oauth2/authorization/" + k, Pair.of(clientName, logo));
});
}

if (oauth2LoginLinks.size() == 1 && !ldapEnabled ) {
return "redirect:"+oauth2LoginLinks.keySet().stream().findFirst().get();
if (oauth2LoginLinks.size() == 1 && !ldapEnabled) {
return "redirect:" + oauth2LoginLinks.keySet().stream().findFirst().get();
}

mdl.addAttribute("header_enabled", headerEnabled);
mdl.addAttribute("header_script", headerScript);

setHeaderAttributes(mdl);
mdl.addAttribute("ldapEnabled", ldapEnabled);
mdl.addAttribute("oauth2LoginLinks", oauth2LoginLinks);
boolean expired = "expired_password".equals(allRequestParams.get("error"));
Expand Down Expand Up @@ -178,4 +190,14 @@ public MessageSource messageSource() {
messageSource.setDefaultEncoding(StandardCharsets.UTF_8.name());
return messageSource;
}

private void setHeaderAttributes(Model mdl) {
mdl.addAttribute("georchestraStylesheet", georchestraStylesheet);
mdl.addAttribute("useLegacyHeader", useLegacyHeader);
mdl.addAttribute("headerUrl", headerUrl);
mdl.addAttribute("headerHeight", headerHeight);
mdl.addAttribute("logoUrl", logoUrl);
mdl.addAttribute("headerEnabled", headerEnabled);
mdl.addAttribute("headerScript", headerScript);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http,
// custom handling for forbidden error
http.exceptionHandling().accessDeniedHandler(new CustomAccessDeniedHandler());

http.formLogin()
.authenticationFailureHandler(new ExtendedRedirectServerAuthenticationFailureHandler("login?error"))
.loginPage("/login");

sortedCustomizers(customizers).forEach(customizer -> {
log.debug("Applying security customizer {}", customizer.getName());
customizer.customize(http);
Expand Down
4 changes: 4 additions & 0 deletions gateway/src/main/resources/messages/login.properties
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,7 @@ expired_password = Your password has been expired
expired_password_link = and should be changed
invalid_credentials = Invalid username or password
duplicate_account = An account already exists using this email address
separator_login=Or, Login with
register_no_account=Don't have an account?
login_message_subtitle=Discover all the data of your platform
f-necas marked this conversation as resolved.
Show resolved Hide resolved
back_home=Back home
4 changes: 4 additions & 0 deletions gateway/src/main/resources/messages/login_de.properties
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,7 @@ expired_password = Ihr Passwort ist abgelaufen
expired_password_link = und sollte geändert werden
invalid_credentials = Ungültiger Benutzername oder Passwort
duplicate_account = Es existiert bereits ein Konto mit dieser E-Mail-Adresse
separator_login=Oder, Anmelden mit
register_no_account=Sie haben noch kein Konto?
login_message_subtitle=Entdecken Sie alle Daten Ihrer Plattform
f-necas marked this conversation as resolved.
Show resolved Hide resolved
back_home=Zurück zur Startseite
4 changes: 4 additions & 0 deletions gateway/src/main/resources/messages/login_en.properties
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,7 @@ expired_password = Your password has been expired
expired_password_link = and should be changed
invalid_credentials = Invalid username or password
duplicate_account = An account already exists using this email address
separator_login=Or, Login with
register_no_account=Don't have an account?
login_message_subtitle=Discover all the data of your platform
f-necas marked this conversation as resolved.
Show resolved Hide resolved
back_home=Back home
4 changes: 4 additions & 0 deletions gateway/src/main/resources/messages/login_es.properties
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,7 @@ expired_password = Su contraseña ha caducado
expired_password_link = y debería ser cambiado
invalid_credentials = Nombre de usuario o contraseña invalido
duplicate_account = Ya existe una cuenta usando esta dirección de correo electrónico
separator_login=O, Iniciar sesión con
register_no_account=No tienes una cuenta?
login_message_subtitle=Descubre todos los datos de tu plataforma
back_home=Volver a la página de inicio
4 changes: 4 additions & 0 deletions gateway/src/main/resources/messages/login_fr.properties
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,7 @@ expired_password = Votre mot de passe a expiré
expired_password_link = et doit être changé
invalid_credentials = Nom d'utilisateur ou mot de passe non valide
duplicate_account = Il existe déjà un compte utilisant cette adresse e-mail
separator_login=Ou, Connectez-vous avec
register_no_account=Vous n'avez pas de compte?
login_message_subtitle=Découvrez toutes les données de votre plateforme
f-necas marked this conversation as resolved.
Show resolved Hide resolved
back_home=Retour
4 changes: 4 additions & 0 deletions gateway/src/main/resources/messages/login_nl.properties
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,7 @@ expired_password = Uw wachtwoord is verlopen
expired_password_link = en moet worden veranderd
invalid_credentials = ongeldige gebruikersnaam of wachtwoord
duplicate_account = Er bestaat al een account met dit e-mailadres
separator_login=Of, Aanmelden met
register_no_account=Heeft u nog geen account?
login_message_subtitle=Ontdek alle gegevens van uw platform
back_home=Terug naar huis
4 changes: 4 additions & 0 deletions gateway/src/main/resources/messages/login_ru.properties
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,7 @@ expired_password = Срок действия вашего пароля исте
expired_password_link = и следует изменить
invalid_credentials = неправильное имя пользователя или пароль
duplicate_account = Учетная запись уже существует с использованием этого адреса электронной почты
separator_login=Или, Войти с
register_no_account=Нет у вас аккаунта?
login_message_subtitle=Откройте все данные вашей платформы
back_home=Назад
7 changes: 0 additions & 7 deletions gateway/src/main/resources/static/login/bootstrap.min.css

This file was deleted.

6 changes: 6 additions & 0 deletions gateway/src/main/resources/static/login/bootstrap5.min.css

Large diffs are not rendered by default.

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
46 changes: 44 additions & 2 deletions gateway/src/main/resources/static/login/signin.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
html,
body {
height: 100%;
height: 100%;
font-family: Inter, sans-serif, Georgia, Cambria, "Times New Roman", Times, serif;
}
:root {
--georchestra: #85127e;
Expand Down Expand Up @@ -44,5 +45,46 @@ body {
background-color: #fff;
border-color: var(--georchestra);
outline: 0;
box-shadow: 0 0 0 0.2rem rgba(133, 18, 125,.25);
box-shadow: 0 0 0 0.2rem color-mix(in srgb,var(--georchestra),#fff 80%);
}
#login-left-col {
border: 20px solid white;
border-radius: 30px;
border-end-end-radius: 0;
border-start-end-radius: 0;
}
a {
color: var(--georchestra);
text-decoration: none;
}
a:hover {
/*text-decoration: underline;*/
color: color-mix(in srgb,var(--georchestra),#000 50%);
}
.fs-7 {
font-size: 0.75rem;
}
.separator {
display: flex;
align-items: center;
text-align: center;
color: #aaa;
font-size: 0.75rem;
margin-bottom: 1rem;
margin-top: 1rem;
}

.separator::before,
.separator::after {
content: '';
flex: 1;
border-bottom: 1px solid #aaa;
}

.separator:not(:empty)::before {
margin-right: .25em;
}

.separator:not(:empty)::after {
margin-left: .25em;
}
130 changes: 87 additions & 43 deletions gateway/src/main/resources/templates/login.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,50 +6,94 @@
<meta name="description" content="">
<meta name="author" content="">
<title>Please sign in</title>
<script th:src="${header_script}"></script>
<link href="login/bootstrap.min.css" rel="stylesheet">
<script th:src="${headerScript}"></script>
<link href="login/bootstrap5.min.css?v5.3.3" rel="stylesheet">
<link href="login/signin.css" rel="stylesheet"/>
<link th:href="${georchestraStylesheet}" rel="stylesheet"/>
</head>
<body>
<header th:if="${header_enabled}">
<geor-header></geor-header>
</header>
<div class="container">
<form class="form-signin" method="post" action="/login" th:if="${ldapEnabled}">
<h2 class="form-signin-heading"><span th:text="#{login_message_title}"></span></h2>
<p>
<label for="username" class="sr-only"><span th:text="#{username}"></span></label>
<input type="text" id="username" name="username" class="form-control" th:placeholder="#{username}" required autofocus>
</p>
<p>
<label for="password" class="sr-only"><span th:text="#{password}"></span></label>
<input type="password" id="password" name="password" class="form-control" th:placeholder="#{password}" required>
</p>
<button class="btn btn-lg btn-primary btn-block mb-2" type="submit"><span th:text="#{login}"></span></button>
<div class="d-flex flex-row flex-wrap justify-content-around">
<a href="/console/account/new">
<span th:text="#{register}"></span>
</a>
<a href="/console/account/passwordRecovery">
<span th:text="#{forget_password}"></span>
</a>
</div>
<div style="margin-top: 60px;">
<div style="text-align: center; font-size: 18px; color: #ff0033;" th:if="${invalidCredentials}"> <span th:text="#{invalid_credentials}"></span> </div>
<div style="text-align: center; font-size: 18px; color: #ff0033;" th:if="${passwordExpired}"> <span th:text="#{expired_password}" ></span>
<a href="/console/account/passwordRecovery" > <span th:text="#{expired_password_link}" ></span> </a>
</div>
<div style="text-align: center; font-size: 18px; color: #ff0033;" th:if="${duplicateAccount}"> <span th:text="#{duplicate_account}"></span> </div>
</div>
</form>
<div th:if="${oauth2LoginLinks.size() != 0}" class="container"><h2 class="form-signin-heading">Login with OAuth 2.0</h2>
<table class="table table-striped">
<tr th:each="oauth2Client : ${oauth2LoginLinks}">
<td>
<a th:href="${oauth2Client.key}" th:text="${oauth2Client.value}"></a>
</td>
</tr>
</table></div>
</div>
<body class="d-flex flex-column" id="georchestra-login-page">
<header th:if="${headerEnabled}">
<geor-header th:stylesheet="${georchestraStylesheet}" th:legacy-header="${useLegacyHeader}"
th:style="'height:' + ${headerHeight} + 'px'" th:logo-url="${logoUrl}"
th:legacy-url="${headerUrl}"></geor-header>
</header>
<div class="container h-100 py-4 px-3">
<div class="d-flex justify-content-center align-items-center rounded-5 h-100">
<div class="row h-100 rounded-lg w-100">
<div class="col p-0 d-none d-md-block" id="login-left-col">
<div class="bg-transparent w-100 h-100 flex align-items-center justify-content-center">
<div class="h-100 w-100 text-white rounded-5" style="background: url('login/img/login.webp')">
</div>
</div>
</div>
<div class="col d-flex flex-column align-items-center justify-content-between bg-white py-5">
<a href="/" class="d-flex align-items-center justify-content-center" th:if="${not headerEnabled}">
<img src="https://www.georchestra.org/public/georchestra-logo.svg" alt="" width="200" height="72">
</a>
<form class="form-signin" method="post" action="/login" th:if="${ldapEnabled}">
<h2 class="form-signin-heading"><span th:text="#{login_message_title}"/></h2>
<h4 class="fs-6 fw-light"><span th:text="#{login_message_subtitle}"/></h4>
<div class="my-4 text-danger text-center"
th:if="${invalidCredentials or passwordExpired or duplicateAccount} ">
<div th:if="${invalidCredentials}"><span th:text="#{invalid_credentials}"></span></div>
<div th:if="${passwordExpired}"><span th:text="#{expired_password}"></span>
<a href="/console/account/passwordRecovery"> <span
th:text="#{expired_password_link}"></span> </a>
</div>
<div th:if="${duplicateAccount}"><span th:text="#{duplicate_account}"></span></div>
</div>
<p class="my-4">
<label for="username" class="sr-only fs-7"><span th:text="#{username}"></span></label>
<input type="text" id="username" name="username" class="form-control"
th:placeholder="#{username}" required autofocus>
</p>
<p class="my-4">
<label for="password" class="sr-only fs-7"><span th:text="#{password}"></span></label>
<input type="password" id="password" name="password" class="form-control"
th:placeholder="#{password}" required>
</p>
<div class="d-flex justify-content-end my-4 fs-7">
<a href="/console/account/passwordRecovery">
<span th:text="#{forget_password}"></span>
</a>
</div>

<button class="btn btn-primary btn-block mb-2 w-100" type="submit"><span th:text="#{login}"></span>
</button>
<div th:if="${oauth2LoginLinks.size() != 0}" class="container">
<div class="separator" th:text="#{separator_login}"></div>
<div class="d-flex align-items-center justify-content-evenly my-3">
<div class="bg-light border p-2 rounded" th:each="oauth2Client : ${oauth2LoginLinks}">
<a th:href="${oauth2Client.key}" th:title="${oauth2Client.value.left}">
<img th:src="${oauth2Client.value.right}" th:alt="${oauth2Client.value.left}" width="24" height="24">
</a>
</div>
</div>
</div>
<div class="d-flex flex-row flex-wrap justify-content-center">
<span th:text="#{register_no_account}"></span>
<span>&nbsp;</span>
<a href="/console/account/new">
<span th:text="#{register}"></span>
</a>

</div>
</form>

<div class="h-auto d-flex justify-content-start w-100">
<a href="/">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" style="height: 24px">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5 8.25 12l7.5-7.5"/>
</svg>
<span th:text="#{back_home}"></span>
</a>
</div>
</div>
</div>
</div>


</div>
</body>
</html>
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
</modules>
<properties>
<revision>1.1.1-SNAPSHOT</revision>
<georchestra.version>24.0.1-SNAPSHOT</georchestra.version>
<georchestra.version>24.0.4-SNAPSHOT</georchestra.version>
f-necas marked this conversation as resolved.
Show resolved Hide resolved
<spring-cloud.version>2021.0.9</spring-cloud.version>
<formatter-maven-plugin.version>2.10.0</formatter-maven-plugin.version>
<fmt.skip>false</fmt.skip>
Expand Down
Loading