feat: add authorization

This commit is contained in:
Sergey Krylov 2026-02-23 16:14:34 +03:00
parent 03fedef553
commit 5f25234962
17 changed files with 274 additions and 26 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@ -1,9 +1,37 @@
<%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<div>
<h3>Charm <3</h3>
<table>
<tr class="hiddenRow">
<td>
<a href="/"><img src="content/app/img/heart.png" width="75"></a>
</td>
<td>
<form method="post" action="/lang">
<button type="submit" name="lang" value="ru">ru</button>
<button type="submit" name="lang" value="en">en</button>
<button type="submit" name="lang" value="ru" class="langButton">
<img src="content/app/img/ru.png" class="langImg">
</button>
</form>
</td>
<td>
<form method="post" action="/lang">
<button type="submit" name="lang" value="en" class="langButton">
<img src="content/app/img/en.png" class="langImg">
</button>
</form>
</td>
<td>
<c:if test="${sessionScope.userDetails == null}">
<a href="/login"><img src="content/app/img/key.png" width="75"></a>
</c:if>
<c:if test="${sessionScope.userDetails != null}">
<form method="post" action="/logout">
<input type="image" src="content/app/img/key.png" width="75" alt="submit" class="icon"
onclick="return confirm()"/>
</form>
</c:if>
</td>
</tr>
</table>
<hr>
</div>

View File

@ -0,0 +1,43 @@
<%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html lang="en">
<head>
<title>Charm Login</title>
<%@ include file="style.html" %>
</head>
<body>
<%@ include file="header.jsp" %>
<div>
<form method="post" action="/login">
<table>
<tr>
<td><h3>${wordBundle.getWord("email")}</h3></td>
<td><input type="email" name="email" placeholder="user@charm.ru"></td>
</tr>
<tr>
<td><h3>${wordBundle.getWord("password")}</h3></td>
<td><input type="password" name="password"></td>
</tr>
</table>
<table>
<tr class="hiddenRow">
<td>
<a href="/registration"><img src="content/app/img/pencil.png" width="75"></a>
</td>
<td>
<input type="image" src="content/app/img/arrow-right.png" width="75" alt="submit"
class="icon"/>
</td>
</tr>
</table>
</form>
<div style="color: red">
<c:forEach var="error" items="${errors}">
<span>${wordBundle.getWord(error)}</span>
<br>
</c:forEach>
</div>
</div>
<%@ include file="footer.jsp" %>
</body>
</html>

View File

@ -21,10 +21,11 @@ public class ContentController extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
String contentPath = req.getRequestURI().replace("/content/", "");
String contentPath = req.getRequestURI().replace("/content", "");
log.info("Content download request: {} {}", req.getRequestURI(), contentPath);
resp.setContentType("application/octet-stream");
service.download(contentPath, resp.getOutputStream());
log.info("Content successfully downloaded: {}", contentPath);
log.info("Content successfully downloaded: {}", req);
} catch (IOException e) {
log.error("Content download failed: {}", e.getMessage());
resp.sendError(SC_NOT_FOUND);

View File

@ -7,27 +7,21 @@ import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ru.charm.back.controller.filter.ErrorFilter;
import ru.charm.back.dto.ProfileGetDto;
import ru.charm.back.dto.ProfileUpdateDto;
import ru.charm.back.mapper.RequestToProfileUpdateDtoMapper;
import ru.charm.back.model.Gender;
import ru.charm.back.model.Profile;
import ru.charm.back.model.exception.DuplicateEmailException;
import ru.charm.back.service.ProfileService;
import java.io.IOException;
import java.time.LocalDate;
import java.util.Optional;
import static jakarta.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
@WebServlet("/email")
@WebServlet("/credentials")
@Slf4j
@MultipartConfig
public class EmailController extends HttpServlet {
public class CredentialsController extends HttpServlet {
private final ProfileService service = ProfileService.getInstance();
private final RequestToProfileUpdateDtoMapper requestToProfileUpdateDtoMapper = RequestToProfileUpdateDtoMapper.getInstance();

View File

@ -0,0 +1,46 @@
package ru.charm.back.controller;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import ru.charm.back.dto.LoginDto;
import ru.charm.back.dto.ProfileGetDto;
import ru.charm.back.dto.RegistrationDto;
import ru.charm.back.mapper.RequestToLoginDtoMapper;
import ru.charm.back.mapper.RequestToRegistrationDtoMapper;
import ru.charm.back.service.ProfileService;
import ru.charm.back.validator.RegistrationValidator;
import ru.charm.back.validator.ValidationResult;
import java.io.IOException;
import java.util.Optional;
@WebServlet("/login")
@Slf4j
public class LoginController extends HttpServlet {
private final ProfileService service = ProfileService.getInstance();
private final RequestToLoginDtoMapper requestTologinDtoMapper = RequestToLoginDtoMapper.getInstance();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
LoginDto dto = requestTologinDtoMapper.map(req);
Optional<ProfileGetDto> userDetailOpt = service.login(dto);
if (userDetailOpt.isPresent()) {
req.getSession().setAttribute("userDetails", userDetailOpt.get());
resp.sendRedirect(String.format("/profile?id=%s", userDetailOpt.get().getId()));
return;
}
resp.sendRedirect("/login");
}
}

View File

@ -0,0 +1,24 @@
package ru.charm.back.controller;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import ru.charm.back.dto.LoginDto;
import ru.charm.back.dto.ProfileGetDto;
import ru.charm.back.mapper.RequestToLoginDtoMapper;
import ru.charm.back.service.ProfileService;
import java.io.IOException;
import java.util.Optional;
@WebServlet("/logout")
public class LogoutController extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
req.getSession().invalidate();
resp.sendRedirect("/login");
}
}

View File

@ -0,0 +1,43 @@
package ru.charm.back.controller.filter;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import ru.charm.back.dto.ProfileGetDto;
import ru.charm.back.model.Role;
import ru.charm.back.service.WordBundle;
import java.io.IOException;
import java.util.Arrays;
import java.util.Set;
@WebFilter("/*")
@Slf4j
public class AuthFilter implements Filter {
private final Set<String> PRIVATE_PATHS = Set.of("/profile", "/credentials");
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) servletRequest;
HttpServletResponse res = (HttpServletResponse) servletResponse;
if (PRIVATE_PATHS.contains(req.getRequestURI())) {
ProfileGetDto userDetails = (ProfileGetDto) req.getSession().getAttribute("userDetails");
log.info("User {} is trying to access {}", userDetails, req.getRequestURI());
if (userDetails == null) {
res.sendError(HttpServletResponse.SC_UNAUTHORIZED);
} else if (userDetails.getRole() == Role.ADMIN || userDetails.getId().toString().equals( req.getParameter("id"))) {
filterChain.doFilter(req, res);
} else {
res.sendError(HttpServletResponse.SC_FORBIDDEN);
}
} else {
filterChain.doFilter(req, res);
}
}
}

View File

@ -2,6 +2,7 @@ package ru.charm.back.dao;
import ru.charm.back.model.Gender;
import ru.charm.back.model.Profile;
import ru.charm.back.model.Role;
import ru.charm.back.model.Status;
import java.time.LocalDate;
@ -32,6 +33,7 @@ public class ProfileDao {
profile.setAbout("I am QA");
profile.setGender(Gender.MALE);
profile.setStatus(Status.ACTIVE);
profile.setRole(Role.ADMIN);
this.storage.put(1L, profile);
Profile profile1 = new Profile();
profile1.setId(2L);
@ -43,6 +45,7 @@ public class ProfileDao {
profile1.setAbout("I am Java Dev");
profile1.setGender(Gender.FEMALE);
profile1.setStatus(Status.INACTIVE);
profile1.setRole(Role.USER);
this.storage.put(2L, profile1);
this.idStorage = new AtomicLong(3L);
}
@ -80,4 +83,10 @@ public class ProfileDao {
public Set<String> getAllEmails() {
return storage.values().stream().map(Profile::getEmail).collect(Collectors.toSet());
}
public Optional<Profile> findByEmail(String email) {
if (email == null) return Optional.empty();
return storage.values().stream().filter(profile -> profile.getEmail().equals(email)).findFirst();
}
}

View File

@ -0,0 +1,9 @@
package ru.charm.back.dto;
import lombok.Data;
@Data
public class LoginDto {
private String email;
private String password;
}

View File

@ -1,21 +1,26 @@
package ru.charm.back.dto;
import lombok.AccessLevel;
import lombok.Data;
import lombok.experimental.FieldDefaults;
import ru.charm.back.model.Gender;
import ru.charm.back.model.Role;
import ru.charm.back.model.Status;
import java.time.LocalDate;
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
public class ProfileGetDto {
private Long id;
private String email;
private String name;
private String surname;
private LocalDate birthDate;
private Integer age;
private String about;
private Gender gender;
private Status status;
private String photo;
Long id;
String email;
String name;
String surname;
LocalDate birthDate;
Integer age;
String about;
Gender gender;
Status status;
String photo;
Role role;
}

View File

@ -37,6 +37,7 @@ public class ProfileToProfileGetDtoMapper implements Mapper<Profile, ProfileGetD
dto.setGender(profile.getGender());
dto.setStatus(profile.getStatus());
dto.setPhoto(profile.getPhoto());
dto.setRole(profile.getRole());
return dto;
}
}

View File

@ -2,6 +2,7 @@ package ru.charm.back.mapper;
import ru.charm.back.dto.RegistrationDto;
import ru.charm.back.model.Profile;
import ru.charm.back.model.Role;
import ru.charm.back.model.Status;
public class RegistrationDtoToProfileMapper implements Mapper<RegistrationDto, Profile> {
@ -25,6 +26,7 @@ public class RegistrationDtoToProfileMapper implements Mapper<RegistrationDto, P
profile.setEmail(dto.getEmail());
profile.setPassword(dto.getPassword());
profile.setStatus(Status.INACTIVE);
profile.setRole(Role.USER);
return profile;
}
}

View File

@ -0,0 +1,29 @@
package ru.charm.back.mapper;
import jakarta.servlet.http.HttpServletRequest;
import ru.charm.back.dto.LoginDto;
import ru.charm.back.dto.RegistrationDto;
public class RequestToLoginDtoMapper implements Mapper<HttpServletRequest, LoginDto> {
private static final RequestToLoginDtoMapper INSTANCE = new RequestToLoginDtoMapper();
private RequestToLoginDtoMapper() {
}
public static RequestToLoginDtoMapper getInstance() {
return INSTANCE;
}
@Override
public LoginDto map(HttpServletRequest req) {
return map(req, new LoginDto());
}
@Override
public LoginDto map(HttpServletRequest req, LoginDto dto) {
dto.setEmail(req.getParameter("email"));
dto.setPassword(req.getParameter("password"));
return dto;
}
}

View File

@ -17,4 +17,5 @@ public class Profile {
private Gender gender;
private Status status;
private String photo;
private Role role;
}

View File

@ -0,0 +1,6 @@
package ru.charm.back.model;
public enum Role {
ADMIN,
USER
}

View File

@ -4,6 +4,7 @@ import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.SneakyThrows;
import ru.charm.back.dao.ProfileDao;
import ru.charm.back.dto.LoginDto;
import ru.charm.back.dto.ProfileGetDto;
import ru.charm.back.dto.ProfileUpdateDto;
import ru.charm.back.dto.RegistrationDto;
@ -66,4 +67,10 @@ public class ProfileService {
public boolean delete(Long id) {
return dao.delete(id);
}
public Optional<ProfileGetDto> login(LoginDto dto) {
return dao.findByEmail(dto.getEmail())
.filter(profile -> profile.getPassword().equals(dto.getPassword()))
.map(profileToProfileGetDtoMapper::map);
}
}