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

Merge dev #10

Merged
merged 5 commits into from
Feb 6, 2025
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -55,9 +55,7 @@ public List<MessageDTO> getMessages(@RequestAttribute(value = AttributesConstant
@Operation(summary = "Get message")
@GetMapping("/channel/{id}/{messageId}")
public MessageDTO getMessage(@RequestAttribute(value = AttributesConstants.CHANNEL) Channel channel, @PathVariable long id, @PathVariable long messageId) throws MessageNotFoundException {
Message message = messagesService.getMessage(messageId, channel);

return new MessageDTO(message);
return messagesService.getMessage(messageId, channel);
}

@Operation(summary = "Create message")
Expand All @@ -69,7 +67,7 @@ public MessageDTO createMessage(@RequestAttribute(value = AttributesConstants.US

Message message = messagesService.addMessage(channel, user, body);

return new MessageDTO(message);
return new MessageDTO(message, null);
}

@Operation(summary = "Delete message")
Expand Down
6 changes: 3 additions & 3 deletions foxogram-common/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ dependencies {
implementation 'org.mindrot:jbcrypt:0.4'
implementation 'org.slf4j:slf4j-api:2.0.16'
implementation 'ch.qos.logback:logback-classic:1.5.15'
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
implementation 'io.jsonwebtoken:jjwt-api:0.12.6'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.6'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.6'
implementation 'org.apache.commons:commons-imaging:1.0.0-alpha5'
implementation 'io.minio:minio:8.5.14'
implementation 'org.springframework.boot:spring-boot-starter-amqp'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public ChannelDTO(Channel channel, Message lastMessage) {
this.memberCount = channel.getMembers().size();
}
if (lastMessage != null) {
this.lastMessage = new MessageDTO(lastMessage);
this.lastMessage = new MessageDTO(lastMessage, null);
}
this.owner = new UserDTO(channel.getOwner(), null, false, false);
this.createdAt = channel.getCreatedAt();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import su.foxogram.models.Attachment;
import su.foxogram.models.Message;

import java.util.List;
Expand All @@ -20,16 +21,17 @@ public class MessageDTO {

private ChannelDTO channel;

private List<String> attachments;
private List<?> attachments;

private long createdAt;

public MessageDTO(Message message) {
public MessageDTO(Message message, List<Attachment> attachments) {
this.id = message.getId();
this.content = message.getContent();
this.author = new MemberDTO(message.getAuthor(), false);
this.channel = new ChannelDTO(message.getChannel(), null);
this.attachments = message.getAttachments();
if (attachments != null) this.attachments = attachments;
else this.attachments = message.getAttachments();
this.createdAt = message.getTimestamp();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public class MessagesDTO {

public MessagesDTO(List<Message> messages) {
for (Message message : messages) {
this.messages.add(new MessageDTO(message));
this.messages.add(new MessageDTO(message, null));
}
}
}
36 changes: 36 additions & 0 deletions foxogram-common/src/main/java/su/foxogram/models/Attachment.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package su.foxogram.models;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;

@Entity
@Getter
@Setter
@Table(name = "attachments", indexes = {
@Index(name = "idx_attachment_id", columnList = "id", unique = true)
})
public class Attachment {

@Id
public String id;

@Column()
public String filename;

@Column()
public String contentType;

@Column()
public long flags;

public Attachment() {
}

public Attachment(String id, String filename, String contentType, long flags) {
this.id = id;
this.filename = filename;
this.contentType = contentType;
this.flags = flags;
}
}
2 changes: 0 additions & 2 deletions foxogram-common/src/main/java/su/foxogram/models/Message.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ public class Message {
@Column()
public long timestamp;

@ElementCollection(targetClass = String.class, fetch = FetchType.EAGER)
@CollectionTable(name = "attachments", joinColumns = @JoinColumn(name = "message_id"))
@Column()
public List<String> attachments;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package su.foxogram.repositories;

import jakarta.validation.constraints.NotNull;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import su.foxogram.models.Attachment;

@Repository
public interface AttachmentRepository extends CrudRepository<Attachment, Long> {

Attachment findById(String id);

@Override
void delete(@NotNull Attachment attachment);
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
import su.foxogram.repositories.CodeRepository;
import su.foxogram.repositories.UserRepository;
import su.foxogram.util.CodeGenerator;
import su.foxogram.util.Encryptor;
import su.foxogram.util.PasswordHasher;

@Slf4j
@Service
Expand Down Expand Up @@ -54,24 +54,30 @@ public AuthenticationService(UserRepository userRepository, CodeRepository codeR

public User getUser(String header, boolean ignoreEmailVerification, boolean ignoreBearer) throws UserUnauthorizedException, UserEmailNotVerifiedException {
long userId;
String passwordHash;

try {
String claims = header.substring(7);
String token = header.substring(7);

if (ignoreBearer) claims = header;
if (ignoreBearer) token = header;

Jws<Claims> claimsJws = Jwts.parserBuilder()
.setSigningKey(jwtService.getSigningKey())
Jws<Claims> claimsJws = Jwts.parser()
.verifyWith(jwtService.getSigningKey())
.build()
.parseClaimsJws(claims);
.parseSignedClaims(token);

userId = Long.parseLong(claimsJws.getBody().getId());
userId = Long.parseLong(claimsJws.getPayload().getId());
passwordHash = claimsJws.getPayload().getSubject();
} catch (Exception e) {
throw new UserUnauthorizedException();
}

User user = userRepository.findById(userId).orElseThrow(UserUnauthorizedException::new);

if (!user.getPassword().equals(passwordHash)) {
throw new UserUnauthorizedException();
}

if (!ignoreEmailVerification && user.hasFlag(UserConstants.Flags.EMAIL_VERIFIED))
throw new UserEmailNotVerifiedException();

Expand All @@ -92,23 +98,23 @@ public String userRegister(String username, String email, String password) throw

log.info("User ({}, {}) email verification message sent successfully", user.getUsername(), user.getEmail());

return jwtService.generate(user.getId());
return jwtService.generate(user.getId(), user.getPassword());
}

private User createUser(String username, String email, String password) {
long deletion = 0;
long flags = UserConstants.Flags.AWAITING_CONFIRMATION.getBit();
int type = UserConstants.Type.USER.getType();

return new User(0, null, null, username, email, Encryptor.hashPassword(password), flags, type, deletion, null);
return new User(0, null, null, username, email, PasswordHasher.hashPassword(password), flags, type, deletion, null);
}

private void sendConfirmationEmail(User user) {
String emailType = EmailConstants.Type.EMAIL_VERIFY.getValue();
String digitCode = CodeGenerator.generateDigitCode();
long issuedAt = System.currentTimeMillis();
long expiresAt = issuedAt + CodesConstants.Lifetime.BASE.getValue();
String accessToken = jwtService.generate(user.getId());
String accessToken = jwtService.generate(user.getId(), user.getPassword());

emailService.sendEmail(user.getEmail(), user.getId(), emailType, user.getUsername(), digitCode, issuedAt, expiresAt, accessToken);
}
Expand All @@ -118,15 +124,15 @@ public String loginUser(String email, String password) throws UserCredentialsIsI
validatePassword(user, password);

log.info("User ({}, {}) login successfully", user.getUsername(), user.getEmail());
return jwtService.generate(user.getId());
return jwtService.generate(user.getId(), user.getPassword());
}

public User findUserByEmail(String email) throws UserCredentialsIsInvalidException {
return userRepository.findByEmail(email).orElseThrow(UserCredentialsIsInvalidException::new);
}

private void validatePassword(User user, String password) throws UserCredentialsIsInvalidException {
if (!Encryptor.verifyPassword(password, user.getPassword()))
if (!PasswordHasher.verifyPassword(password, user.getPassword()))
throw new UserCredentialsIsInvalidException();
}

Expand Down Expand Up @@ -176,7 +182,7 @@ public void confirmResetPassword(UserResetPasswordConfirmDTO body) throws CodeEx
User user = userRepository.findByEmail(body.getEmail()).orElseThrow(UserCredentialsIsInvalidException::new);
Code code = codeService.validateCode(body.getCode());

user.setPassword(Encryptor.hashPassword(body.getNewPassword()));
user.setPassword(PasswordHasher.hashPassword(body.getNewPassword()));
user.removeFlag(UserConstants.Flags.AWAITING_CONFIRMATION);

codeService.deleteCode(code);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ private void changeIcon(Channel channel, MultipartFile icon) throws UploadFailed
String hash;

try {
hash = storageService.uploadToMinio(icon, StorageConstants.AVATARS_BUCKET);
hash = storageService.uploadIdentityImage(icon, StorageConstants.AVATARS_BUCKET);
} catch (Exception e) {
throw new UploadFailedException();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import su.foxogram.configs.APIConfig;
import su.foxogram.configs.EmailConfig;
import su.foxogram.constants.EmailConstants;
import su.foxogram.util.Algorithm;
import su.foxogram.util.StringUtils;

import java.io.IOException;
import java.io.InputStream;
Expand Down Expand Up @@ -92,7 +92,7 @@ private String readHTML() throws IOException {
}

try (InputStream inputStream = resource.getInputStream()) {
return Algorithm.inputStreamToString(inputStream, StandardCharsets.UTF_8);
return StringUtils.inputStreamToString(inputStream, StandardCharsets.UTF_8);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import su.foxogram.configs.JwtConfig;
import su.foxogram.constants.TokenConstants;

import java.security.Key;
import javax.crypto.SecretKey;
import java.util.Date;

@Service
Expand All @@ -20,19 +20,19 @@ public JwtService(JwtConfig jwtConfig) {
this.jwtConfig = jwtConfig;
}

public String generate(long id) {
public String generate(long id, String passwordHash) {
long now = System.currentTimeMillis();
Date expirationDate = new Date(now + TokenConstants.LIFETIME);

return Jwts.builder()
.setId(String.valueOf(id))
.setExpiration(expirationDate)
.id(String.valueOf(id))
.subject(passwordHash)
.expiration(expirationDate)
.signWith(getSigningKey())
.compact();
}

public Key getSigningKey() {
byte[] keyBytes = Decoders.BASE64.decode(jwtConfig.getSecret());
return Keys.hmacShaKeyFor(keyBytes);
public SecretKey getSigningKey() {
return Keys.hmacShaKeyFor(Decoders.BASE64.decode(jwtConfig.getSecret()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,8 @@
import su.foxogram.exceptions.cdn.UploadFailedException;
import su.foxogram.exceptions.member.MissingPermissionsException;
import su.foxogram.exceptions.message.MessageNotFoundException;
import su.foxogram.models.Channel;
import su.foxogram.models.Member;
import su.foxogram.models.Message;
import su.foxogram.models.User;
import su.foxogram.models.*;
import su.foxogram.repositories.AttachmentRepository;
import su.foxogram.repositories.ChannelRepository;
import su.foxogram.repositories.MemberRepository;
import su.foxogram.repositories.MessageRepository;
Expand All @@ -40,13 +38,16 @@ public class MessagesService {

private final MemberRepository memberRepository;

private final AttachmentRepository attachmentRepository;

@Autowired
public MessagesService(MessageRepository messageRepository, StorageService storageService, RabbitService rabbitService, ChannelRepository channelRepository, MemberRepository memberRepository) {
public MessagesService(MessageRepository messageRepository, StorageService storageService, RabbitService rabbitService, ChannelRepository channelRepository, MemberRepository memberRepository, AttachmentRepository attachmentRepository) {
this.messageRepository = messageRepository;
this.storageService = storageService;
this.rabbitService = rabbitService;
this.channelRepository = channelRepository;
this.memberRepository = memberRepository;
this.attachmentRepository = attachmentRepository;
}

public List<MessageDTO> getMessages(long before, int limit, Channel channel) {
Expand All @@ -55,18 +56,27 @@ public List<MessageDTO> getMessages(long before, int limit, Channel channel) {
log.info("Messages ({}, {}) in channel ({}) found successfully", limit, before, channel.getId());

return messagesArray.reversed().stream()
.map(MessageDTO::new)
.map(message -> {
List<Attachment> attachments = new ArrayList<>();
if (message.getAttachments() != null) {
message.getAttachments().forEach(attachment -> attachments.add(attachmentRepository.findById(attachment)));
}
return new MessageDTO(message, attachments);
})
.collect(Collectors.toList());
}

public Message getMessage(long id, Channel channel) throws MessageNotFoundException {
public MessageDTO getMessage(long id, Channel channel) throws MessageNotFoundException {
Message message = messageRepository.findByChannelAndId(channel, id);

if (message == null) throw new MessageNotFoundException();

List<Attachment> attachments = new ArrayList<>();
message.getAttachments().forEach(attachment -> attachments.add(attachmentRepository.findById(attachment)));

log.info("Message ({}) in channel ({}) found successfully", id, channel.getId());

return message;
return new MessageDTO(message, attachments);
}

public Message addMessage(Channel channel, User user, MessageCreateDTO body) throws UploadFailedException, JsonProcessingException {
Expand All @@ -78,7 +88,7 @@ public Message addMessage(Channel channel, User user, MessageCreateDTO body) thr
uploadedAttachments = body.getAttachments().stream()
.map(attachment -> {
try {
return uploadAttachment(attachment);
return uploadAttachment(attachment).getId();
} catch (UploadFailedException e) {
throw new RuntimeException(e);
}
Expand All @@ -92,7 +102,7 @@ public Message addMessage(Channel channel, User user, MessageCreateDTO body) thr
Message message = new Message(channel, body.getContent(), member, uploadedAttachments);
messageRepository.save(message);

rabbitService.send(getRecipients(channel), new MessageDTO(message), GatewayConstants.Event.MESSAGE_CREATE.getValue());
rabbitService.send(getRecipients(channel), new MessageDTO(message, null), GatewayConstants.Event.MESSAGE_CREATE.getValue());
log.info("Message ({}) to channel ({}) created successfully", message.getId(), channel.getId());

return message;
Expand Down Expand Up @@ -120,13 +130,13 @@ public Message editMessage(long id, Channel channel, Member member, MessageCreat
message.setContent(content);
messageRepository.save(message);

rabbitService.send(getRecipients(channel), new MessageDTO(message), GatewayConstants.Event.MESSAGE_UPDATE.getValue());
rabbitService.send(getRecipients(channel), new MessageDTO(message, null), GatewayConstants.Event.MESSAGE_UPDATE.getValue());
log.info("Message ({}) in channel ({}) edited successfully", id, channel.getId());

return message;
}

private String uploadAttachment(MultipartFile attachment) throws UploadFailedException {
private Attachment uploadAttachment(MultipartFile attachment) throws UploadFailedException {
try {
return storageService.uploadToMinio(attachment, StorageConstants.ATTACHMENTS_BUCKET);
} catch (Exception e) {
Expand Down
Loading