Skip to content
Commits on Source (3)
...@@ -9,9 +9,9 @@ package Forms; ...@@ -9,9 +9,9 @@ package Forms;
* *
* @author stag * @author stag
*/ */
import static com.sun.tools.javac.tree.TreeInfo.name;
import fr.ldnr.beans.User; import fr.ldnr.beans.User;
import fr.ldnr.dao.DaoFactory; import fr.ldnr.dao.DaoFactory;
import helpers.PasswordAuthentification;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
public class LoginFormChecker extends FormChecker<User> { public class LoginFormChecker extends FormChecker<User> {
...@@ -30,7 +30,6 @@ public class LoginFormChecker extends FormChecker<User> { ...@@ -30,7 +30,6 @@ public class LoginFormChecker extends FormChecker<User> {
bean = new User(23, null, pseudo, pwd); bean = new User(23, null, pseudo, pwd);
if (pseudo == null || pseudo.trim().length() < 3) { if (pseudo == null || pseudo.trim().length() < 3) {
errors.put(PSEUDO_FIELD, "Doit faire au moins 3 caractères"); errors.put(PSEUDO_FIELD, "Doit faire au moins 3 caractères");
} }
...@@ -42,12 +41,19 @@ public class LoginFormChecker extends FormChecker<User> { ...@@ -42,12 +41,19 @@ public class LoginFormChecker extends FormChecker<User> {
if (errors.isEmpty()) { if (errors.isEmpty()) {
User user = DaoFactory.getUserDao().getByName(pseudo); User user = DaoFactory.getUserDao().getByName(pseudo);
// Vérifications de l'existence de l'utilisateur // Vérifications de l'existence de l'utilisateur
if (user == null || !user.getPassword().equals(pwd)) { PasswordAuthentification pa = new PasswordAuthentification();
try {
// Attention, la méthode authenticate lance des exceptions silencieuses...
if (user == null || !pa.authenticate(pwd.toCharArray(), user.getPassword())) {
errors.put(PSEUDO_FIELD, "Utilisateur ou mot de passe erroné"); errors.put(PSEUDO_FIELD, "Utilisateur ou mot de passe erroné");
} else { } else {
// L'utilisateur est le bon // L'utilisateur est le bon
bean = user; bean = user;
} }
} catch (IllegalArgumentException ex) {
errors.put(PSEUDO_FIELD, "Utilisateur ou mot de passe erroné");
}
} }
return errors.isEmpty(); return errors.isEmpty();
......
...@@ -11,6 +11,7 @@ package Forms; ...@@ -11,6 +11,7 @@ package Forms;
*/ */
import fr.ldnr.beans.User; import fr.ldnr.beans.User;
import fr.ldnr.dao.DaoFactory; import fr.ldnr.dao.DaoFactory;
import helpers.PasswordAuthentification;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
public class SigninFormChecker extends FormChecker<User> { public class SigninFormChecker extends FormChecker<User> {
...@@ -40,10 +41,13 @@ public class SigninFormChecker extends FormChecker<User> { ...@@ -40,10 +41,13 @@ public class SigninFormChecker extends FormChecker<User> {
errors.put(EMAIL_FIELD, "L'adresse email n'est pas valide."); errors.put(EMAIL_FIELD, "L'adresse email n'est pas valide.");
} }
if (password == null || password.trim().isEmpty()) { if (password == null || password.length() < 6) {
errors.put(PWD_FIELD, "Le mot de passe est obligatoire."); password = ""; // nécessaire pour simplifier la tentative de hashage
} else if (password.length() < 6) { errors.put(PWD_FIELD, "Doit contenir au moins 6 caractères");
errors.put(PWD_FIELD, "Le mot de passe doit contenir au moins 6 caractères."); } else {
if (confirmPassword == null || !password.equals(confirmPassword)) {
errors.put(CONFIRM_PWD_FIELD, "Doit être identique au mot de passe");
}
} }
if (confirmPassword == null || confirmPassword.trim().isEmpty()) { if (confirmPassword == null || confirmPassword.trim().isEmpty()) {
...@@ -58,9 +62,13 @@ public class SigninFormChecker extends FormChecker<User> { ...@@ -58,9 +62,13 @@ public class SigninFormChecker extends FormChecker<User> {
errors.put(PSEUDO_FIELD, "Le pseudo doit contenir au moins 3 caractères."); errors.put(PSEUDO_FIELD, "Le pseudo doit contenir au moins 3 caractères.");
} }
if (errors.isEmpty()) { // Si le formulaire est correct if (errors.isEmpty()) {
// Créer l'utilisateur en DB PasswordAuthentification pa = new PasswordAuthentification();
bean.setPassword(pa.hash(password.toCharArray()));
DaoFactory.getUserDao().insert(bean); DaoFactory.getUserDao().insert(bean);
if(bean.getId() == null){
errors.put(PSEUDO_FIELD, "Ce pseudo est déja utilisé !!!");
}
} }
return errors.isEmpty(); return errors.isEmpty();
......
package fr.ldnr.servelets; package fr.ldnr.servelets;
import Forms.LoginFormChecker; import Forms.LoginFormChecker;
import fr.ldnr.beans.User;
import java.io.*; import java.io.*;
import java.util.*; import java.util.*;
import javax.servlet.*; import javax.servlet.*;
...@@ -11,12 +12,8 @@ import static jdk.internal.org.jline.utils.Log.error; ...@@ -11,12 +12,8 @@ import static jdk.internal.org.jline.utils.Log.error;
@WebServlet(name = "login", urlPatterns = {"/login"}) @WebServlet(name = "login", urlPatterns = {"/login"})
public class Login extends HttpServlet { public class Login extends HttpServlet {
private static final String VIEW = "/WEB-INF/Login.jsp"; private static final String VIEW = "/WEB-INF/Login.jsp";
private static final String VIEW_ADMIN = "/WEB-INF/Admin.jsp";
@Override @Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) protected void doGet(HttpServletRequest request, HttpServletResponse response)
...@@ -24,11 +21,14 @@ public class Login extends HttpServlet { ...@@ -24,11 +21,14 @@ public class Login extends HttpServlet {
if (request.getSession().getAttribute("user") == null) { if (request.getSession().getAttribute("user") == null) {
RequestDispatcher dispatcher = request.getServletContext().getRequestDispatcher(VIEW); RequestDispatcher dispatcher = request.getServletContext().getRequestDispatcher(VIEW);
dispatcher.forward(request, response); dispatcher.forward(request, response);
} else {
User myUser = (User) request.getSession().getAttribute("user");
if (myUser.getId_user() == 1) {
request.getServletContext().getRequestDispatcher(VIEW_ADMIN).forward(request, response);
} else { } else {
response.sendRedirect(request.getContextPath() + "/home"); response.sendRedirect(request.getContextPath() + "/home");
} }
}
} }
@Override @Override
...@@ -41,10 +41,13 @@ public class Login extends HttpServlet { ...@@ -41,10 +41,13 @@ public class Login extends HttpServlet {
request.setAttribute("errors", checker.getErrors()); request.setAttribute("errors", checker.getErrors());
request.setAttribute("userData", checker.getBean()); request.setAttribute("userData", checker.getBean());
request.getServletContext() request.getServletContext()
.getRequestDispatcher(VIEW) .getRequestDispatcher(VIEW)
.forward(request, response); .forward(request, response);
} else { } else {
//je met l'utilisateur en session //je met l'utilisateur en session
request.getSession().setAttribute("user", checker.getBean()); request.getSession().setAttribute("user", checker.getBean());
......
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package helpers;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.Arrays;
import java.util.Base64;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
/**
*
* @author stag
*/
public final class PasswordAuthentification {
private static final String INIT_ID = "51";
/**
* Each token produced by this class uses this identifier as a prefix.
*/
public static final String ID = "$" + INIT_ID + "$";
/**
* The minimum recommended cost, used by default
*/
public static final int DEFAULT_COST = 16;
private static final String ALGORITHM = "PBKDF2WithHmacSHA512";
private static final int SIZE = 128;
private static final Pattern LAYOUT = Pattern.compile("\\$" + INIT_ID + "\\$(\\d\\d?)\\$(.{43})");
private final SecureRandom random;
private final int cost;
/**
* Create a password manager with a default cost
*/
public PasswordAuthentification() {
this(DEFAULT_COST);
}
/**
* Create a password manager with a specified cost
*
* @param cost the exponential computational cost of hashing a password, 0
* to 30
*/
public PasswordAuthentification(int cost) {
iterations(cost);
/* Validate cost */
this.cost = cost;
this.random = new SecureRandom();
}
private static int iterations(int cost) {
if ((cost < 0) || (cost > 30)) {
throw new IllegalArgumentException("cost: " + cost);
}
return 1 << cost; // Décalage du bit à gauche : 0001 << 3 revient à 1000
}
/**
* Hash a password for storage.
*
* @param password The password to hash
* @return a secure authentication token to be stored for later
* authentication
*/
public String hash(char[] password) {
byte[] salt = new byte[SIZE / 8];
random.nextBytes(salt);
byte[] dk = pbkdf2(password, salt, 1 << cost);
byte[] hash = new byte[salt.length + dk.length];
System.arraycopy(salt, 0, hash, 0, salt.length);
System.arraycopy(dk, 0, hash, salt.length, dk.length);
Base64.Encoder enc = Base64.getUrlEncoder().withoutPadding();
return ID + cost + '$' + enc.encodeToString(hash);
}
/**
* Authenticate with a password and a stored password token.
*
* @param password The password to compare
* @param token The token to compare the password to
* @return true if the password and token match
*/
public boolean authenticate(char[] password, String token) {
Matcher m = LAYOUT.matcher(token);
if (!m.matches()) {
throw new IllegalArgumentException("Invalid token format");
}
int iterations = iterations(Integer.parseInt(m.group(1)));
byte[] hash = Base64.getUrlDecoder().decode(m.group(2));
byte[] salt = Arrays.copyOfRange(hash, 0, SIZE / 8);
byte[] check = pbkdf2(password, salt, iterations);
int zero = 0;
for (int idx = 0; idx < check.length; ++idx) {
zero |= hash[salt.length + idx] ^ check[idx];
}
return zero == 0;
}
private static byte[] pbkdf2(char[] password, byte[] salt, int iterations) {
KeySpec spec = new PBEKeySpec(password, salt, iterations, SIZE);
try {
SecretKeyFactory f = SecretKeyFactory.getInstance(ALGORITHM);
return f.generateSecret(spec).getEncoded();
} catch (NoSuchAlgorithmException ex) {
throw new IllegalStateException("Missing algorithm: " + ALGORITHM, ex);
} catch (InvalidKeySpecException ex) {
throw new IllegalStateException("Invalid SecretKeyFactory", ex);
}
}
/**
* Hash a password in an immutable {@code String}.
*
* <p>
* Passwords should be stored in a {@code char[]} so that it can be filled
* with zeros after use instead of lingering on the heap and elsewhere.
*
* @param password The password to hash
* @return a secure authentication token to be stored for later
* authentication
* @deprecated Use {@link #hash(char[])} instead
*/
@Deprecated
public String hash(String password) {
return hash(password.toCharArray());
}
}
<%--
Document : Admin
Created on : 5 mai 2023, 13:03:36
Author : stag
--%>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Admin Page</title>
</head>
<body>
<h1>Hello World!</h1>
</body>
</html>
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
<head> <head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Article Page</title> <title>Article Page</title>
<link rel="stylesheet" href="<c:url value="/rsc/css/Style.css"/>"/> <%@include file="/WEB-INF/jspf/Style.jsp" %>
</head> </head>
<body> <body>
<%@include file="/WEB-INF/jspf/Header.jsp" %> <%@include file="/WEB-INF/jspf/Header.jsp" %>
......
...@@ -3,87 +3,8 @@ ...@@ -3,87 +3,8 @@
<html> <html>
<head> <head>
<%@page contentType="text/html" pageEncoding="UTF-8"%> <%@page contentType="text/html" pageEncoding="UTF-8"%>
<link rel="stylesheet" href="<c:url value="/rsc/css/Style.css"/>"/> <%@include file="/WEB-INF/jspf/Style.jsp" %>
<title>Blog</title> <title>Blog</title>
<style>
.article {
background-color: #f7f7f7;
border: 1px solid #ccc;
border-radius: 5px;
padding: 10px;
margin-bottom: 20px;
}
.article h3 {
font-size: 24px;
margin-top: 0;
}
.article .sub {
font-size: 14px;
margin-bottom: 10px;
}
.article p {
font-size: 16px;
line-height: 1.5;
margin-bottom: 20px;
}
.article .readMore {
text-align: right;
margin-top: 0;
}
.article .readMore a {
color: #1a1aff;
text-decoration: none;
font-size: 18px;
}
.article .readMore a:hover {
text-decoration: underline;
}
.pagination {
display: flex;
justify-content: center;
margin-top: 30px;
}
.pagination ul {
list-style: none;
margin: 0;
padding: 0;
display: flex;
}
.pagination li {
margin: 0 5px;
}
.pagination li a {
display: block;
padding: 5px 10px;
color: #fff;
background-color: #1a1aff;
text-decoration: none;
border-radius: 5px;
}
.pagination li a:hover {
background-color: #0000ff;
}
.pagination li.active a {
background-color: #0000ff;
}
.pagination li.disabled a {
background-color: #ccc;
cursor: not-allowed;
}
</style>
</head> </head>
<body> <body>
......
...@@ -4,7 +4,8 @@ ...@@ -4,7 +4,8 @@
<html> <html>
<head> <head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel="stylesheet" href="<c:url value="/rsc/css/Style.css"/>"/> <%@include file="/WEB-INF/jspf/Style.jsp" %>
<link rel="stylesheet" type="text/css" href="/rsc/css/Style.css" />
<title>Connexion</title> <title>Connexion</title>
</head> </head>
<body> <body>
...@@ -28,5 +29,6 @@ ...@@ -28,5 +29,6 @@
<input type="submit" value="Envoyer" > <input type="submit" value="Envoyer" >
<input type="reset" value="Annuler" > <input type="reset" value="Annuler" >
</form> </form>
<%@include file="./jspf/Footer.jsp" %>
</body> </body>
</html> </html>
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
<head> <head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title> <title>JSP Page</title>
<%@include file="/WEB-INF/jspf/Style.jsp" %>
<style> <style>
table { table {
border-collapse: collapse; border-collapse: collapse;
......
...@@ -5,14 +5,16 @@ ...@@ -5,14 +5,16 @@
<html> <html>
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<link rel="stylesheet" href="<c:url value="/rsc/css/Style.css"/>"/>
<%@include file="/WEB-INF/jspf/Style.jsp" %>
<link rel="stylesheet" type="text/css" href="/rsc/css/Style.css" />
<title>Inscription</title> <title>Inscription</title>
</head> </head>
<body> <body>
<%@include file="/WEB-INF/jspf/Header.jsp" %> <%@include file="/WEB-INF/jspf/Header.jsp" %>
<h1>Page d'inscription</h1> <h1>Page d'inscription</h1>
<form method="post" action="<c:url value='/sign-in' />"> <form method="post" action="<c:url value='/sign-in' />" style="margin-bottom: 50px">
<label for="email">Email</label> <label for="email">Email</label>
<input type="email" name="email" id="email" value="${requestScope.userData.email}" required><br> <input type="email" name="email" id="email" value="${requestScope.userData.email}" required><br>
<span class="error">${requestScope.errors.email}</span> <span class="error">${requestScope.errors.email}</span>
...@@ -32,6 +34,7 @@ ...@@ -32,6 +34,7 @@
<input type="submit" value="Sign In"> <input type="submit" value="Sign In">
<input type="reset" value="Annuler"> <input type="reset" value="Annuler">
</form> </form>
<%@include file="./jspf/Footer.jsp" %>
</body> </body>
</html> </html>
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
<html> <html>
<head> <head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel="stylesheet" href="<c:url value="/rsc/css/Style.css"/>"/> <%@include file="/WEB-INF/jspf/Style.jsp" %>
<title>Page de création d'article</title> <title>Page de création d'article</title>
</head> </head>
<body> <body>
...@@ -39,9 +39,6 @@ ...@@ -39,9 +39,6 @@
<input type="reset" value="Annuler"> <input type="reset" value="Annuler">
</div> </div>
</form> </form>
<%@include file="./jspf/Footer.jsp" %>
</body> </body>
</html> </html>
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
--%> --%>
<style> <style>
#MonFooter{ #MonFooter{
padding-top : 50px;
position: fixed; position: fixed;
bottom: 0; bottom: 0;
left: 0; left: 0;
......
<%--
Document : newjsp
Created on : 5 mai 2023, 14:01:13
Author : stag
--%>
<c:choose>
<c:when test="${sessionScope.user.id == 1}">
<link rel="stylesheet" href="<c:url value="/rsc/css/Admin.css"/>"/>
</c:when>
</c:choose>
<style>
.article {
background-color: #f7f7f7;
border: 1px solid #ccc;
border-radius: 5px;
padding: 10px;
margin-bottom: 20px;
}
.article h3 {
font-size: 24px;
margin-top: 0;
}
.article .sub {
font-size: 14px;
margin-bottom: 10px;
}
.article p {
font-size: 16px;
line-height: 1.5;
margin-bottom: 20px;
}
.article .readMore {
text-align: right;
margin-top: 0;
}
.article .readMore a {
color: #1a1aff;
text-decoration: none;
font-size: 18px;
}
.article .readMore a:hover {
text-decoration: underline;
}
.pagination {
display: flex;
justify-content: center;
margin-top: 30px;
}
.pagination ul {
list-style: none;
margin: 0;
padding: 0;
display: flex;
}
.pagination li {
margin: 0 5px;
}
.pagination li a {
display: block;
padding: 5px 10px;
color: #fff;
background-color: #1a1aff;
text-decoration: none;
border-radius: 5px;
}
.pagination li a:hover {
background-color: #0000ff;
}
.pagination li.active a {
background-color: #0000ff;
}
.pagination li.disabled a {
background-color: #ccc;
cursor: not-allowed;
}
form {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin: 0 auto;
width: 40%;
padding: 30px;
border-radius: 10px;
background: #2B3A42;
box-shadow: 0px 5px 20px rgba(0, 0, 0, 0.5);
}
.error {
color: red;
font-size: 1.8rem;
font-style: italic;
}
h1 {
color: #F7DC6F;
text-align: center;
font-size: 2.5rem;
}
label {
color: #F7DC6F;
font-size: 1.5rem;
font-weight: bold;
margin-bottom: 10px;
}
input[type=text],input[type=email], input[type=password] {
padding: 15px;
border: none;
border-radius: 10px;
margin-bottom: 20px;
width: 100%;
background-color: #F7DC6F;
color: #2B3A42;
font-size: 1.2rem;
}
input[type=submit], input[type=reset] {
background-color: #F7DC6F;
color: #2B3A42;
padding: 15px 30px;
border: none;
border-radius: 10px;
font-size: 1.2rem;
cursor: pointer;
}
input[type=submit]:hover , input[type=reset]:hover{
background-color: #34495E;
color: #F7DC6F;
}
.error-message {
color: #ff0000;
font-size: 18px;
font-family: 'Bebas Neue', sans-serif;
text-transform: uppercase;
background-color: #0d0d0d;
border: 2px solid #ff0000;
padding: 10px;
margin-bottom: 10px;
}
.error-message:before {
content: "!";
color: #ff0000;
font-weight: bold;
font-size: 32px;
display: inline-block;
margin-right: 10px;
transform: translateY(-10%);
}
.containerArticle {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
}
.box {
background-color: #fff;
box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.1);
padding: 20px;
}
@media (max-width: 768px) {
.container {
grid-template-columns: repeat(2, 1fr);
}
}
form {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin: 0 auto;
width: 40%;
padding: 30px;
border-radius: 10px;
background: #2B3A42;
box-shadow: 0px 5px 20px rgba(0, 0, 0, 0.5);
}
h1 {
color: #F7DC6F;
text-align: center;
font-size: 2.5rem;
}
label {
color: #F7DC6F;
font-size: 1.5rem;
font-weight: bold;
margin-bottom: 10px;
}
input[type=text],input[type=email], input[type=password] {
padding: 15px;
border: none;
border-radius: 10px;
margin-bottom: 20px;
width: 100%;
background-color: #F7DC6F;
color: #2B3A42;
font-size: 1.2rem;
}
input[type=submit] , input[type=reset]{
background-color: #F7DC6F;
color: #2B3A42;
padding: 15px 30px;
border: none;
border-radius: 10px;
font-size: 1.2rem;
cursor: pointer;
}
input[type=submit]:hover , input[type=reset]:hover{
background-color: #34495E;
color: #F7DC6F;
}
.error-message {
color: red;
font-weight: bold;
}
textarea {
padding: 15px;
border: none;
border-radius: 10px;
margin-bottom: 20px;
width: 100%;
height: 150px; /* Hauteur du textarea */
background-color: #F7DC6F;
color: #2B3A42;
font-size: 1.2rem;
resize: vertical; /* Permet l'utilisateur de redimensionner verticalement le textarea */
}
.footer {
background-color: #333;
color: #fff;
padding: 20px;
text-align: center;
}
.pagination {
display: flex;
justify-content: center;
margin-top: 20px;
padding-bottom: 40px;
}
.pagination ul {
display: flex;
list-style: none;
padding: 0;
margin: 0;
}
.pagination ul li {
margin-right: 5px;
}
.pagination ul li.disabled a {
color: #ccc;
cursor: not-allowed;
text-decoration: none;
}
.pagination ul li.active a {
color: #fff;
background-color: #007bff;
border-color: #007bff;
text-decoration: none;
}
.pagination ul li a {
display: block;
padding: 5px 10px;
color: #007bff;
background-color: #fff;
border: 1px solid #007bff;
text-decoration: none;
}
.pagination ul li a:hover {
background-color: #007bff;
color: #fff;
text-decoration: none;
}
.article-container {
text-align: center;
max-width: 800px;
margin: 0 auto;
padding: 20px;
border: 1px solid #ccc;
}
#pageArticle > h2 {
font-size: 2.5rem;
font-weight: bold;
margin-bottom: 1rem;
}
#pageArticle > .sub {
font-size: 1rem;
color: gray;
margin-bottom: 1rem;
}
#pageArticle > div {
font-size: 1.2rem;
line-height: 1.5;
margin-bottom: 2rem;
}
#pageArticle > .pagingArticle {
display: flex;
justify-content: space-between;
margin-top: 2rem;
}
#pageArticle > a {
font-size: 1.2rem;
color: black;
text-decoration: none;
border: 1px solid black;
padding: 0.5rem 1rem;
border-radius: 5px;
transition: all 0.2s ease-in-out;
}
#pageArticle > a:hover {
background-color: black;
color: white;
}
#pageArticle > .disabled {
color: gray;
border-color: gray;
pointer-events: none;
opacity: 0.6;
}
</style>
/*
To change this license header, choose License Headers in Project Properties.
To change this template file, choose Tools | Templates
and open the template in the editor.
*/
/*
Created on : 5 mai 2023, 13:04:01
Author : stag
*/
html{
border-color: red;
border-style: solid;
border-width: 3px;
}
\ No newline at end of file
.article {
background-color: #f7f7f7;
border: 1px solid #ccc;
border-radius: 5px;
padding: 10px;
margin-bottom: 20px;
}
.article h3 {
font-size: 24px;
margin-top: 0;
}
.article .sub {
font-size: 14px;
margin-bottom: 10px;
}
.article p {
font-size: 16px;
line-height: 1.5;
margin-bottom: 20px;
}
.article .readMore {
text-align: right;
margin-top: 0;
}
.article .readMore a {
color: #1a1aff;
text-decoration: none;
font-size: 18px;
}
.article .readMore a:hover {
text-decoration: underline;
}
.pagination {
display: flex;
justify-content: center;
margin-top: 30px;
}
.pagination ul {
list-style: none;
margin: 0;
padding: 0;
display: flex;
}
.pagination li {
margin: 0 5px;
}
.pagination li a {
display: block;
padding: 5px 10px;
color: #fff;
background-color: #1a1aff;
text-decoration: none;
border-radius: 5px;
}
.pagination li a:hover {
background-color: #0000ff;
}
.pagination li.active a {
background-color: #0000ff;
}
.pagination li.disabled a {
background-color: #ccc;
cursor: not-allowed;
}
\ No newline at end of file
...@@ -10,6 +10,7 @@ form { ...@@ -10,6 +10,7 @@ form {
background: #2B3A42; background: #2B3A42;
box-shadow: 0px 5px 20px rgba(0, 0, 0, 0.5); box-shadow: 0px 5px 20px rgba(0, 0, 0, 0.5);
} }
.error { .error {
color: red; color: red;
font-size: 1.8rem; font-size: 1.8rem;
......