Authentication

This commit is contained in:
Michael Wain 2024-06-11 04:11:27 +03:00
parent cd63319aef
commit 4ca446621c
61 changed files with 831 additions and 108 deletions

7
README.md Normal file

@ -0,0 +1,7 @@
<h1 align="center">Hearthhack</h1>
<p align="center"><i>A hearthstone server software project</i></p>
<div align="center">
<a href="stargazers"><img src="https://img.shields.io/github/stars/" alt="Stars Badge"/></a>
<a href="blob/master/LICENSE"><img src="https://img.shields.io/github/license/?color=2b9348" alt="License Badge"/></a>
</div>
<p align="center" style="font-size:0.8em;">Loved the project?<br><a href="https://www.getmonero.org/">XMR</a>: <a href=""></a></p>

@ -1,6 +1,8 @@
package com.alterdekim.hearthhack;
import com.alterdekim.hearthhack.service.StorageService;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@ -15,4 +17,11 @@ public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
CommandLineRunner init(StorageService storageService) {
return (args) -> {
storageService.init();
};
}
}

@ -1,18 +1,14 @@
package com.alterdekim.hearthhack.component;
import com.alterdekim.hearthhack.entity.RoomPlayer;
import com.alterdekim.hearthhack.entity.User;
import com.alterdekim.hearthhack.service.RoomPlayerService;
import com.alterdekim.hearthhack.service.RoomService;
import com.alterdekim.hearthhack.service.UserService;
import com.alterdekim.hearthhack.util.Hash;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;

@ -1,6 +1,8 @@
package com.alterdekim.hearthhack.component;
import com.alterdekim.hearthhack.config.ServerConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@ -14,15 +16,19 @@ import java.util.List;
@Component
public class GameServer {
private final int port = 3724;
private int port;
private ServerSocket serverSocket;
private List<GameConnection> connections;
@Autowired
private ServerConfig serverConfig;
@Scheduled(fixedDelay = 5000)
private void start() {
try {
this.port = serverConfig.getGamePort();
this.connections = new LinkedList<>();
this.serverSocket = new ServerSocket(this.port);
while(true) {

@ -0,0 +1,42 @@
package com.alterdekim.hearthhack.component;
import com.alterdekim.hearthhack.config.DBFConfig;
import com.alterdekim.hearthhack.config.ServerConfig;
import com.alterdekim.hearthhack.parser.DBFParser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import java.nio.file.Files;
import java.nio.file.Path;
@Slf4j
@Component
public class StartupListener {
@Autowired
private DBFConfig dbfConfig;
@Autowired
private ServerConfig serverConfig;
@EventListener
public void onApplicationEvent(ContextRefreshedEvent event) {
try {
dbfConfig.setCards(DBFParser.parseCards(serverConfig.getDbfPath()));
dbfConfig.setClientConfig(
new String(
Files.readAllBytes(
Path.of(
serverConfig.getClientConfigPath()
)
)
)
);
} catch (Exception e) {
log.error(e.getMessage());
}
}
}

@ -1,6 +1,7 @@
package com.alterdekim.hearthhack.component;
import com.alterdekim.hearthhack.component.processor.*;
import com.alterdekim.hearthhack.config.DBFConfig;
import com.alterdekim.hearthhack.config.ServerConfig;
import com.alterdekim.hearthhack.service.UserService;
import com.alterdekim.hearthhack.util.BattleNetPacket;
@ -11,6 +12,7 @@ import lombok.extern.slf4j.Slf4j;
import javax.net.ssl.SSLSocket;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Map;
@ -29,13 +31,18 @@ public class TcpConnection extends Thread {
private final Map<Integer, Processor> processors;
@Getter
private final ServerConfig serverConfig;
private final DBFConfig dbfConfig;
@Getter
private final UserService userService;
public void stopListeningAndDisconnect() {
log.warn("Tried to stopListening");
try {
this.fromClient.close();
} catch (IOException e) {
log.error(e.getMessage());
}
}
public void send(BattleNetPacket bp) throws Exception {

@ -2,6 +2,7 @@ package com.alterdekim.hearthhack.component;
import com.alterdekim.hearthhack.component.processor.Processor;
import com.alterdekim.hearthhack.config.DBFConfig;
import com.alterdekim.hearthhack.config.ServerConfig;
import com.alterdekim.hearthhack.reflect.ReflectionLoader;
import com.alterdekim.hearthhack.service.UserService;
@ -22,11 +23,14 @@ import java.util.Set;
@Component
public class TcpServer extends ReflectionLoader<Processor> {
private final int socketPort = 1119;
private int socketPort;
private SSLServerSocket serverSocket;
private List<TcpConnection> connections;
@Autowired
private DBFConfig dbfConfig;
@Autowired
private ServerConfig serverConfig;
@ -36,6 +40,7 @@ public class TcpServer extends ReflectionLoader<Processor> {
@Scheduled(fixedDelay = 5000)
private void start() {
try {
this.socketPort = serverConfig.getBnetPort();
this.initReflect(Processor.class);
if( serverSocket != null && !serverSocket.isClosed() ) {
serverSocket.close();
@ -51,7 +56,7 @@ public class TcpServer extends ReflectionLoader<Processor> {
while(true) {
SSLSocket s = (SSLSocket) serverSocket.accept();
TcpConnection c = new TcpConnection(s, this.getParsers(), serverConfig, userService);
TcpConnection c = new TcpConnection(s, this.getParsers(), dbfConfig, userService);
connections.add(c);
c.start();
log.info("New Connection Established From {}", s.getInetAddress().toString());

@ -70,9 +70,11 @@ public class AuthProcessor extends Processor {
private void verifyWebCredentials(BattleNetPacket packet, TcpConnection conn) throws Exception {
Protocol.VerifyWebCredentialsRequest verifyWebCredentialsRequest
= Protocol.VerifyWebCredentialsRequest.parseFrom(packet.getBody());
log.info( new String( verifyWebCredentialsRequest.getWebCredentials().toByteArray() ) );
String token = new String( verifyWebCredentialsRequest.getWebCredentials().toByteArray() );
log.info(token);
if( !conn.getUserService().authMe(token) ) conn.stopListeningAndDisconnect();
Protocol.Header h = Processor.generateResponse(0, packet.getHeader().getToken(), 0, 0);
@ -99,9 +101,6 @@ public class AuthProcessor extends Processor {
.build();
conn.send(new BattleNetPacket(h, Util.hexStringToByteArray("0800")));
// THIRD
//Protocol.LogonResult lr = Protocol.LogonResult.parseFrom(Util.hexStringToByteArray("080012120900000000000000011193710E1A000000001A12094743545702000002116739AB040000000022002801280228032806286230023A0F517569726B794F72632332393638384202534B4A40AA15061771938C7790CDA59BD500BF2AA3205912148098F8B68ADA802F1122B2792D90FA2952A338032A03895322DD02C4E2F3CFE6CD72415466B3798C9ADB015000"));
Protocol.LogonResult logonResult = Protocol.LogonResult.newBuilder()
.setErrorCode(0)

@ -2,6 +2,7 @@ package com.alterdekim.hearthhack.component.processor.client.request.generic;
import com.alterdekim.Protocol;
import com.alterdekim.hearthhack.component.TcpConnection;
import com.alterdekim.hearthhack.config.DBFConfig;
import com.alterdekim.hearthhack.config.ServerConfig;
import com.alterdekim.hearthhack.service.IService;
import com.alterdekim.hearthhack.util.BattleNetPacket;
@ -13,7 +14,7 @@ import static com.alterdekim.hearthhack.util.GetAccountInfoRequest.ACCOUNT_LICEN
public class AccountLicensesInfo extends GenericParser {
@Override
public void setResources(IService service, ServerConfig config) {}
public void setResources(IService service, DBFConfig config) {}
@Override
public void parseGenericRequest(int token, TcpConnection conn) throws Exception {

@ -2,6 +2,7 @@ package com.alterdekim.hearthhack.component.processor.client.request.generic;
import com.alterdekim.Protocol;
import com.alterdekim.hearthhack.component.TcpConnection;
import com.alterdekim.hearthhack.config.DBFConfig;
import com.alterdekim.hearthhack.config.ServerConfig;
import com.alterdekim.hearthhack.service.IService;
import com.alterdekim.hearthhack.util.BattleNetPacket;
@ -16,7 +17,7 @@ import static com.alterdekim.hearthhack.util.GetAccountInfoRequest.FEATURES;
@Slf4j
public class AvailableFeatures extends GenericParser {
private ServerConfig config;
private DBFConfig config;
private Executor executor;
private void executeFeatures(TcpConnection conn) throws Exception {
@ -40,7 +41,7 @@ public class AvailableFeatures extends GenericParser {
}
@Override
public void setResources(IService service, ServerConfig config) {
public void setResources(IService service, DBFConfig config) {
this.config = config;
}

@ -2,6 +2,7 @@ package com.alterdekim.hearthhack.component.processor.client.request.generic;
import com.alterdekim.Protocol;
import com.alterdekim.hearthhack.component.TcpConnection;
import com.alterdekim.hearthhack.config.DBFConfig;
import com.alterdekim.hearthhack.config.ServerConfig;
import com.alterdekim.hearthhack.service.IService;
import com.alterdekim.hearthhack.service.UserService;
@ -14,11 +15,11 @@ import static com.alterdekim.hearthhack.util.GetAccountInfoRequest.BOOSTERS;
public class Boosters extends GenericParser {
private ServerConfig config;
private DBFConfig config;
private UserService userService;
@Override
public void setResources(IService service, ServerConfig config) {
public void setResources(IService service, DBFConfig config) {
this.config = config;
this.userService = (UserService) service;
}

@ -2,6 +2,7 @@ package com.alterdekim.hearthhack.component.processor.client.request.generic;
import com.alterdekim.Protocol;
import com.alterdekim.hearthhack.component.TcpConnection;
import com.alterdekim.hearthhack.config.DBFConfig;
import com.alterdekim.hearthhack.config.ServerConfig;
import com.alterdekim.hearthhack.service.IService;
import com.alterdekim.hearthhack.util.BattleNetPacket;
@ -12,10 +13,10 @@ import static com.alterdekim.hearthhack.util.GetAccountInfoRequest.CARD_BACKS;
public class CardBacks extends GenericParser {
private ServerConfig config;
private DBFConfig config;
@Override
public void setResources(IService service, ServerConfig config) {
public void setResources(IService service, DBFConfig config) {
this.config = config;
}

@ -2,6 +2,7 @@ package com.alterdekim.hearthhack.component.processor.client.request.generic;
import com.alterdekim.Protocol;
import com.alterdekim.hearthhack.component.TcpConnection;
import com.alterdekim.hearthhack.config.DBFConfig;
import com.alterdekim.hearthhack.config.ServerConfig;
import com.alterdekim.hearthhack.dbf.CardsDBF;
import com.alterdekim.hearthhack.dbf.DBFField;
@ -21,17 +22,16 @@ import static com.alterdekim.hearthhack.util.GetAccountInfoRequest.CARD_VALUES;
@Slf4j
public class CardValues extends GenericParser {
private ServerConfig config;
private DBFConfig config;
@Override
public void setResources(IService service, ServerConfig config) {
public void setResources(IService service, DBFConfig config) {
this.config = config;
}
@Override
public void parseGenericRequest(int token, TcpConnection conn) throws Exception {
CardsDBF cards = DBFParser.parseCards(config.getDbfPath());
CardsDBF cards = config.getCards();
Protocol.CardValues.Builder cardVals = Protocol.CardValues.newBuilder();
cards.getRecords().forEach(c -> {

@ -2,6 +2,7 @@ package com.alterdekim.hearthhack.component.processor.client.request.generic;
import com.alterdekim.Protocol;
import com.alterdekim.hearthhack.component.TcpConnection;
import com.alterdekim.hearthhack.config.DBFConfig;
import com.alterdekim.hearthhack.config.ServerConfig;
import com.alterdekim.hearthhack.service.IService;
import com.alterdekim.hearthhack.util.BattleNetPacket;
@ -12,10 +13,10 @@ import static com.alterdekim.hearthhack.util.GetAccountInfoRequest.CLIENT_OPTION
public class ClientOptions extends GenericParser {
private ServerConfig config;
private DBFConfig config;
@Override
public void setResources(IService service, ServerConfig config) {
public void setResources(IService service, DBFConfig config) {
this.config = config;
}

@ -2,6 +2,7 @@ package com.alterdekim.hearthhack.component.processor.client.request.generic;
import com.alterdekim.Protocol;
import com.alterdekim.hearthhack.component.TcpConnection;
import com.alterdekim.hearthhack.config.DBFConfig;
import com.alterdekim.hearthhack.config.ServerConfig;
import com.alterdekim.hearthhack.service.IService;
import com.alterdekim.hearthhack.service.UserService;
@ -13,11 +14,11 @@ import static com.alterdekim.hearthhack.util.GetAccountInfoRequest.COLLECTION;
public class Collection extends GenericParser {
private ServerConfig config;
private DBFConfig config;
private UserService userService;
@Override
public void setResources(IService service, ServerConfig config) {
public void setResources(IService service, DBFConfig config) {
this.config = config;
this.userService = (UserService) service;
}

@ -2,6 +2,7 @@ package com.alterdekim.hearthhack.component.processor.client.request.generic;
import com.alterdekim.Protocol;
import com.alterdekim.hearthhack.component.TcpConnection;
import com.alterdekim.hearthhack.config.DBFConfig;
import com.alterdekim.hearthhack.config.ServerConfig;
import com.alterdekim.hearthhack.service.IService;
import com.alterdekim.hearthhack.service.UserService;
@ -13,11 +14,11 @@ import static com.alterdekim.hearthhack.util.GetAccountInfoRequest.DECK_LIST;
public class DeckList extends GenericParser {
private ServerConfig config;
private DBFConfig config;
private UserService userService;
@Override
public void setResources(IService service, ServerConfig config) {
public void setResources(IService service, DBFConfig config) {
this.userService = (UserService) service;
this.config = config;
}

@ -2,6 +2,7 @@ package com.alterdekim.hearthhack.component.processor.client.request.generic;
import com.alterdekim.Protocol;
import com.alterdekim.hearthhack.component.TcpConnection;
import com.alterdekim.hearthhack.config.DBFConfig;
import com.alterdekim.hearthhack.config.ServerConfig;
import com.alterdekim.hearthhack.service.IService;
import com.alterdekim.hearthhack.service.UserService;
@ -13,11 +14,11 @@ import static com.alterdekim.hearthhack.util.GetAccountInfoRequest.ARCANE_DUST_B
public class DustBalance extends GenericParser {
private ServerConfig config;
private DBFConfig config;
private UserService userService;
@Override
public void setResources(IService service, ServerConfig config) {
public void setResources(IService service, DBFConfig config) {
this.userService = (UserService) service;
this.config = config;
}

@ -2,6 +2,7 @@ package com.alterdekim.hearthhack.component.processor.client.request.generic;
import com.alterdekim.Protocol;
import com.alterdekim.hearthhack.component.TcpConnection;
import com.alterdekim.hearthhack.config.DBFConfig;
import com.alterdekim.hearthhack.config.ServerConfig;
import com.alterdekim.hearthhack.service.IService;
import com.alterdekim.hearthhack.service.UserService;
@ -14,10 +15,10 @@ import static com.alterdekim.hearthhack.util.GetAccountInfoRequest.FAVORITE_HERO
public class FavoriteHeroes extends GenericParser {
private UserService userService;
private ServerConfig config;
private DBFConfig config;
@Override
public void setResources(IService service, ServerConfig config) {
public void setResources(IService service, DBFConfig config) {
this.config = config;
this.userService = (UserService) service;
}

@ -20,7 +20,7 @@ public class GeneralGenericParser extends ReflectionLoader<GenericParser> {
}
try {
GenericParser parser = this.getParsers().get(req.getValue());
parser.setResources(conn.getUserService(), conn.getServerConfig());
parser.setResources(conn.getUserService(), conn.getDbfConfig());
parser.parseGenericRequest(token, conn);
} catch (Exception e) {
log.error(e.getMessage());

@ -2,6 +2,7 @@ package com.alterdekim.hearthhack.component.processor.client.request.generic;
import com.alterdekim.Protocol;
import com.alterdekim.hearthhack.component.TcpConnection;
import com.alterdekim.hearthhack.config.DBFConfig;
import com.alterdekim.hearthhack.config.ServerConfig;
import com.alterdekim.hearthhack.reflect.AbstractParser;
import com.alterdekim.hearthhack.service.IService;
@ -16,7 +17,7 @@ import static com.alterdekim.hearthhack.util.GameUtilities.generateNotification;
@NoArgsConstructor
public abstract class GenericParser implements AbstractParser {
public abstract void setResources(IService service, ServerConfig config);
public abstract void setResources(IService service, DBFConfig config);
public abstract void parseGenericRequest(int token, TcpConnection conn) throws Exception;

@ -2,6 +2,7 @@ package com.alterdekim.hearthhack.component.processor.client.request.generic;
import com.alterdekim.Protocol;
import com.alterdekim.hearthhack.component.TcpConnection;
import com.alterdekim.hearthhack.config.DBFConfig;
import com.alterdekim.hearthhack.config.ServerConfig;
import com.alterdekim.hearthhack.service.IService;
import com.alterdekim.hearthhack.service.UserService;
@ -13,11 +14,11 @@ import static com.alterdekim.hearthhack.util.GetAccountInfoRequest.GOLD_BALANCE;
public class GoldBalance extends GenericParser {
private ServerConfig config;
private DBFConfig config;
private UserService userService;
@Override
public void setResources(IService service, ServerConfig config) {
public void setResources(IService service, DBFConfig config) {
this.config = config;
this.userService = (UserService) service;
}

@ -2,6 +2,7 @@ package com.alterdekim.hearthhack.component.processor.client.request.generic;
import com.alterdekim.Protocol;
import com.alterdekim.hearthhack.component.TcpConnection;
import com.alterdekim.hearthhack.config.DBFConfig;
import com.alterdekim.hearthhack.config.ServerConfig;
import com.alterdekim.hearthhack.service.IService;
import com.alterdekim.hearthhack.service.UserService;
@ -13,11 +14,11 @@ import static com.alterdekim.hearthhack.util.GetAccountInfoRequest.HERO_XP;
public class HeroXP extends GenericParser {
private ServerConfig config;
private DBFConfig config;
private UserService userService;
@Override
public void setResources(IService service, ServerConfig config) {
public void setResources(IService service, DBFConfig config) {
this.config = config;
this.userService = (UserService) service;
}

@ -2,6 +2,7 @@ package com.alterdekim.hearthhack.component.processor.client.request.generic;
import com.alterdekim.Protocol;
import com.alterdekim.hearthhack.component.TcpConnection;
import com.alterdekim.hearthhack.config.DBFConfig;
import com.alterdekim.hearthhack.config.ServerConfig;
import com.alterdekim.hearthhack.service.IService;
import com.alterdekim.hearthhack.service.UserService;
@ -13,11 +14,11 @@ import static com.alterdekim.hearthhack.util.GetAccountInfoRequest.MEDAL_INFO;
public class MedalInfo extends GenericParser {
private ServerConfig config;
private DBFConfig config;
private UserService userService;
@Override
public void setResources(IService service, ServerConfig config) {
public void setResources(IService service, DBFConfig config) {
this.userService = (UserService) service;
this.config = config;
}

@ -2,6 +2,7 @@ package com.alterdekim.hearthhack.component.processor.client.request.generic;
import com.alterdekim.Protocol;
import com.alterdekim.hearthhack.component.TcpConnection;
import com.alterdekim.hearthhack.config.DBFConfig;
import com.alterdekim.hearthhack.config.ServerConfig;
import com.alterdekim.hearthhack.service.IService;
import com.alterdekim.hearthhack.service.UserService;
@ -13,11 +14,11 @@ import static com.alterdekim.hearthhack.util.GetAccountInfoRequest.NOT_SO_MASSIV
public class NotSoMassiveLoginReply extends GenericParser {
private ServerConfig config;
private DBFConfig config;
private UserService userService;
@Override
public void setResources(IService service, ServerConfig config) {
public void setResources(IService service, DBFConfig config) {
this.userService = (UserService) service;
this.config = config;
}

@ -2,6 +2,7 @@ package com.alterdekim.hearthhack.component.processor.client.request.generic;
import com.alterdekim.Protocol;
import com.alterdekim.hearthhack.component.TcpConnection;
import com.alterdekim.hearthhack.config.DBFConfig;
import com.alterdekim.hearthhack.config.ServerConfig;
import com.alterdekim.hearthhack.service.IService;
import com.alterdekim.hearthhack.service.UserService;
@ -15,11 +16,11 @@ import static com.alterdekim.hearthhack.util.GetAccountInfoRequest.PVP_QUEUE;
@Slf4j
public class PlayQueue extends GenericParser {
private ServerConfig config;
private DBFConfig config;
private UserService userService;
@Override
public void setResources(IService service, ServerConfig config) {
public void setResources(IService service, DBFConfig config) {
this.config = config;
this.userService = (UserService) service;
}

@ -2,6 +2,7 @@ package com.alterdekim.hearthhack.component.processor.client.request.generic;
import com.alterdekim.Protocol;
import com.alterdekim.hearthhack.component.TcpConnection;
import com.alterdekim.hearthhack.config.DBFConfig;
import com.alterdekim.hearthhack.config.ServerConfig;
import com.alterdekim.hearthhack.service.IService;
import com.alterdekim.hearthhack.service.UserService;
@ -13,11 +14,11 @@ import static com.alterdekim.hearthhack.util.GetAccountInfoRequest.PLAYER_RECORD
public class PlayerRecords extends GenericParser {
private ServerConfig config;
private DBFConfig config;
private UserService userService;
@Override
public void setResources(IService service, ServerConfig config) {
public void setResources(IService service, DBFConfig config) {
this.config = config;
this.userService = (UserService) service;
}

@ -2,6 +2,7 @@ package com.alterdekim.hearthhack.component.processor.client.request.generic;
import com.alterdekim.Protocol;
import com.alterdekim.hearthhack.component.TcpConnection;
import com.alterdekim.hearthhack.config.DBFConfig;
import com.alterdekim.hearthhack.config.ServerConfig;
import com.alterdekim.hearthhack.service.IService;
import com.alterdekim.hearthhack.service.UserService;
@ -22,7 +23,7 @@ public class ProfileNotices extends GenericParser {
private Executor executor;
private UserService userService;
private ServerConfig config;
private DBFConfig config;
private void sendNotices(TcpConnection conn) throws Exception {
Protocol.ProfileNotices notices = Protocol.ProfileNotices.newBuilder()
@ -43,7 +44,7 @@ public class ProfileNotices extends GenericParser {
}
@Override
public void setResources(IService service, ServerConfig config) {
public void setResources(IService service, DBFConfig config) {
this.config = config;
this.userService = (UserService) service;
}

@ -2,6 +2,7 @@ package com.alterdekim.hearthhack.component.processor.client.request.generic;
import com.alterdekim.Protocol;
import com.alterdekim.hearthhack.component.TcpConnection;
import com.alterdekim.hearthhack.config.DBFConfig;
import com.alterdekim.hearthhack.config.ServerConfig;
import com.alterdekim.hearthhack.service.IService;
import com.alterdekim.hearthhack.service.UserService;
@ -14,10 +15,10 @@ import static com.alterdekim.hearthhack.util.GetAccountInfoRequest.CAMPAIGN_INFO
public class ProfileProgress extends GenericParser {
private UserService userService;
private ServerConfig config;
private DBFConfig config;
@Override
public void setResources(IService service, ServerConfig config) {
public void setResources(IService service, DBFConfig config) {
this.userService = (UserService) service;
this.config = config;
}

@ -2,6 +2,7 @@ package com.alterdekim.hearthhack.component.processor.client.request.generic;
import com.alterdekim.Protocol;
import com.alterdekim.hearthhack.component.TcpConnection;
import com.alterdekim.hearthhack.config.DBFConfig;
import com.alterdekim.hearthhack.config.ServerConfig;
import com.alterdekim.hearthhack.service.IService;
import com.alterdekim.hearthhack.service.UserService;
@ -14,10 +15,10 @@ import static com.alterdekim.hearthhack.util.GetAccountInfoRequest.REWARD_PROGRE
public class RewardProgress extends GenericParser {
private UserService userService;
private ServerConfig config;
private DBFConfig config;
@Override
public void setResources(IService service, ServerConfig config) {
public void setResources(IService service, DBFConfig config) {
this.userService = (UserService) service;
this.config = config;
}

@ -0,0 +1,12 @@
package com.alterdekim.hearthhack.config;
import com.alterdekim.hearthhack.dbf.CardsDBF;
import lombok.Data;
import org.springframework.context.annotation.Configuration;
@Data
@Configuration
public class DBFConfig {
private CardsDBF cards;
private String clientConfig;
}

@ -8,4 +8,8 @@ import org.springframework.context.annotation.Configuration;
@Configuration
public class ServerConfig {
@Value("${hearthhack.dbf_path}") private String dbfPath;
@Value("${hearthhack.client_config_template}") private String clientConfigPath;
@Value("${hearthhack.bnet_port}") private Integer bnetPort;
@Value("${hearthhack.game_port}") private Integer gamePort;
@Value("${hearthhack.host}") private String host;
}

@ -0,0 +1,12 @@
package com.alterdekim.hearthhack.config;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
@Setter
@Getter
@ConfigurationProperties("storage")
public class StorageProperties {
private String location = "static";
}

@ -0,0 +1,13 @@
package com.alterdekim.hearthhack.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.thymeleaf.extras.springsecurity6.dialect.SpringSecurityDialect;
@Configuration
public class ThymeleafConfig {
@Bean
public SpringSecurityDialect springSecurityDialect(){
return new SpringSecurityDialect();
}
}

@ -0,0 +1,35 @@
package com.alterdekim.hearthhack.controller;
import com.alterdekim.hearthhack.entity.User;
import com.alterdekim.hearthhack.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Slf4j
@Controller
public class AdminController {
@Autowired
private UserService userService;
@GetMapping("/panel")
public String adminPanel(Model model) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if( authentication.isAuthenticated() ) {
try {
User u = userService.findByUsername(((org.springframework.security.core.userdetails.User) authentication.getPrincipal()).getUsername());
if( !u.getRoles().get(0).getName().equals("ROLE_ADMIN") ) {
return "redirect:/";
}
return "panel";
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
return "redirect:/";
}
}

@ -0,0 +1,58 @@
package com.alterdekim.hearthhack.controller;
import com.alterdekim.hearthhack.dto.UserDTO;
import com.alterdekim.hearthhack.entity.User;
import com.alterdekim.hearthhack.service.UserService;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
@Controller
public class AuthController {
private final String base_title = " | Hearthhack";
@Autowired
private UserService userService;
@GetMapping("/login")
public String loginPage(Model model) {
model.addAttribute("title", "Login" + base_title);
return "login";
}
@GetMapping("/signup")
public String showRegistrationForm(Model model) {
UserDTO userDto = new UserDTO();
model.addAttribute("title", "Signup" + base_title);
model.addAttribute("user", userDto);
return "signup";
}
@PostMapping("/signup")
public String registration(
@ModelAttribute("user") @Valid UserDTO userDto,
BindingResult result,
Model model) {
User existingUser = userService.findByUsername(userDto.getUsername());
if(existingUser != null && existingUser.getUsername() != null && !existingUser.getUsername().isEmpty() ){
result.rejectValue("username", null,
"There is already an account registered with the same username");
return "redirect:/signup?error=already_exists";
}
if(result.hasErrors()) {
model.addAttribute("user", new UserDTO());
return "redirect:/signup?error=other";
}
userService.saveUser(userDto);
return "redirect:/";
}
}

@ -0,0 +1,46 @@
package com.alterdekim.hearthhack.controller;
import com.alterdekim.hearthhack.config.StorageProperties;
import com.alterdekim.hearthhack.exception.StorageFileNotFoundException;
import com.alterdekim.hearthhack.service.StorageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@EnableConfigurationProperties(StorageProperties.class)
public class FileServerController {
private final StorageService storageService;
@Autowired
public FileServerController(StorageService storageService) {
this.storageService = storageService;
}
@GetMapping("/static/{assetspath}/{filename:.+}")
@ResponseBody
public ResponseEntity<Resource> serveFile(@PathVariable String filename, @PathVariable String assetspath) {
Resource file = storageService.loadAsResource("static/" + assetspath + "/" + filename);
if( assetspath.equals("images") ) {
return ResponseEntity.ok().contentType(MediaType.IMAGE_PNG).header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + file.getFilename() + "\"").body(file);
}
return ResponseEntity.ok().contentType(new MediaType("text", assetspath)).header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + file.getFilename() + "\"").body(file);
}
@ExceptionHandler(StorageFileNotFoundException.class)
public ResponseEntity<?> handleStorageFileNotFound(StorageFileNotFoundException exc) {
return ResponseEntity.notFound().build();
}
}

@ -0,0 +1,68 @@
package com.alterdekim.hearthhack.controller;
import com.alterdekim.hearthhack.config.DBFConfig;
import com.alterdekim.hearthhack.config.ServerConfig;
import com.alterdekim.hearthhack.entity.User;
import com.alterdekim.hearthhack.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Slf4j
@Controller
public class StaticController {
private final String base_title = " | Hearthhack";
@Autowired
private DBFConfig dbfConfig;
@Autowired
private UserService userService;
@Autowired
private ServerConfig serverConfig;
@GetMapping("/rules")
public String rulesPage(Model model) {
model.addAttribute("title", "Rules" + base_title);
return "rules";
}
@GetMapping("/")
public String homePage(Model model) {
model.addAttribute("title", "Home" + base_title);
return "index";
}
@GetMapping("/access-denied")
public String accessDenied(Model model) {
model.addAttribute("title", "Access denied");
return "403";
}
@GetMapping("/config")
public ResponseEntity<String> getConfig(Model model) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if( !authentication.isAuthenticated() ) return ResponseEntity.badRequest().build();
Long uid = 0L;
try {
uid = userService.findByUsername(
((org.springframework.security.core.userdetails.User) authentication.getPrincipal()).getUsername()
).getId();
String c = dbfConfig.getClientConfig()
.replace("${token}", userService.genHash(uid))
.replace("${port}", serverConfig.getBnetPort()+"")
.replace("${host}", serverConfig.getHost());
return ResponseEntity.ok(c);
} catch (Exception e) {
log.error(e.getMessage());
}
return ResponseEntity.noContent().build();
}
}

@ -16,11 +16,6 @@ public class UserDTO {
private String username;
@NotEmpty(message = "Password should not be empty")
private String password;
@NotEmpty(message = "Invite code should not be empty")
private String invite_code;
private Boolean terms_and_conditions_accept;
private String lang;
}

@ -18,8 +18,13 @@ public class CardBack {
private Long id;
@Column(nullable=false)
private Integer backId;
private Integer backId = 0;
@Column(nullable=false)
private Long userId;
public CardBack(Integer backId, Long userId) {
this.backId = backId;
this.userId = userId;
}
}

@ -0,0 +1,13 @@
package com.alterdekim.hearthhack.exception;
public class StorageException extends RuntimeException {
public StorageException(String message) {
super(message);
}
public StorageException(String message, Throwable cause) {
super(message, cause);
}
}

@ -0,0 +1,12 @@
package com.alterdekim.hearthhack.exception;
public class StorageFileNotFoundException extends StorageException {
public StorageFileNotFoundException(String message) {
super(message);
}
public StorageFileNotFoundException(String message, Throwable cause) {
super(message, cause);
}
}

@ -0,0 +1,16 @@
package com.alterdekim.hearthhack.handler;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import java.io.IOException;
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
response.sendRedirect("/403");
}
}

@ -6,15 +6,14 @@ import lombok.extern.slf4j.Slf4j;
import org.xml.sax.helpers.DefaultHandler;
import java.io.File;
@Slf4j
public class DBFParser extends DefaultHandler {
public static CardsDBF parseCards(String path) {
try {
XmlMapper xmlMapper = new XmlMapper();
return xmlMapper.readValue(new File(path + "/CARD.xml"), CardsDBF.class);
} catch (Exception e) {
log.error(e.getMessage());
}
return new CardsDBF();
public static CardsDBF parseCards(String path) throws Exception {
return parse(path + "/CARD.xml", CardsDBF.class);
}
public static <T> T parse(String path, Class<T> cl) throws Exception {
XmlMapper xmlMapper = new XmlMapper();
return xmlMapper.readValue(new File(path), cl);
}
}

@ -2,9 +2,18 @@ package com.alterdekim.hearthhack.repository;
import com.alterdekim.hearthhack.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
User findByUsername(String username);
@Transactional
@Modifying
@Query(value = "UPDATE User u SET u.defaultCardBack = :cardBackId WHERE u.id = :userId")
void updateCardBackOfUser(@Param("userId") Long userId, @Param("cardBackId") Long cardBackId);
}

@ -1,5 +1,6 @@
package com.alterdekim.hearthhack.security;
import com.alterdekim.hearthhack.handler.CustomAccessDeniedHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -10,6 +11,7 @@ import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
@ -26,14 +28,20 @@ public class SpringSecurity {
http.csrf().disable()
.authorizeHttpRequests((authorize) ->
authorize
.requestMatchers("/profile/**").hasAnyAuthority("ROLE_USER")
.requestMatchers("/").permitAll()
.requestMatchers("/panel").hasAnyAuthority("ROLE_ADMIN")
.requestMatchers("/").hasAnyAuthority("ROLE_USER")
.requestMatchers("/config").hasAnyAuthority("ROLE_USER")
.requestMatchers("/403").permitAll()
.requestMatchers("/signup").permitAll()
.requestMatchers("/login").permitAll()
.requestMatchers("/rules").permitAll()
.requestMatchers("/static/**").permitAll()
).formLogin(
form -> form
.loginPage("/login")
.loginProcessingUrl("/login")
.failureForwardUrl("/")
.defaultSuccessUrl("/games")
.defaultSuccessUrl("/")
.permitAll()
)
.logout(
@ -44,10 +52,18 @@ public class SpringSecurity {
)
.requestCache((cache) -> cache
.requestCache(requestCache)
);
)
.exceptionHandling((exc) -> exc
.accessDeniedHandler(accessDeniedHandler())
.accessDeniedPage("/403"));
return http.build();
}
@Bean
public AccessDeniedHandler accessDeniedHandler() {
return new CustomAccessDeniedHandler();
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());

@ -0,0 +1,43 @@
package com.alterdekim.hearthhack.service;
import com.alterdekim.hearthhack.entity.Role;
import com.alterdekim.hearthhack.entity.User;
import com.alterdekim.hearthhack.repository.UserRepository;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.Collection;
import java.util.stream.Collectors;
@Service
public class CustomUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
public CustomUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
User user = userRepository.findByUsername(email);
if (user != null) {
return new org.springframework.security.core.userdetails.User(user.getUsername(),
user.getPassword(),
mapRolesToAuthorities(user.getRoles()));
}else{
throw new UsernameNotFoundException("Invalid username or password.");
}
}
private Collection< ? extends GrantedAuthority> mapRolesToAuthorities(Collection <Role> roles) {
return roles.stream()
.map(role -> new SimpleGrantedAuthority(role.getName()))
.collect(Collectors.toList());
}
}

@ -0,0 +1,110 @@
package com.alterdekim.hearthhack.service;
import com.alterdekim.hearthhack.config.StorageProperties;
import com.alterdekim.hearthhack.exception.StorageException;
import com.alterdekim.hearthhack.exception.StorageFileNotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
import org.springframework.util.FileSystemUtils;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.stream.Stream;
@Service
public class FileSystemStorageService implements StorageService {
private final Path rootLocation;
@Autowired
public FileSystemStorageService(StorageProperties properties) {
if(properties.getLocation().trim().isEmpty()){
throw new StorageException("File upload location can not be Empty.");
}
this.rootLocation = Paths.get(new ClassPathResource(properties.getLocation()).getPath());
}
@Override
public void store(MultipartFile file) {
try {
if (file.isEmpty()) {
throw new StorageException("Failed to store empty file.");
}
Path destinationFile = this.rootLocation.resolve(
Paths.get(file.getOriginalFilename()))
.normalize().toAbsolutePath();
if (!destinationFile.getParent().equals(this.rootLocation.toAbsolutePath())) {
throw new StorageException(
"Cannot store file outside current directory.");
}
try (InputStream inputStream = file.getInputStream()) {
Files.copy(inputStream, destinationFile,
StandardCopyOption.REPLACE_EXISTING);
}
}
catch (IOException e) {
throw new StorageException("Failed to store file.", e);
}
}
@Override
public Stream<Path> loadAll() {
try {
return Files.walk(this.rootLocation, 1)
.filter(path -> !path.equals(this.rootLocation))
.map(this.rootLocation::relativize);
}
catch (IOException e) {
throw new StorageException("Failed to read stored files", e);
}
}
@Override
public Path load(String filename) {
return rootLocation.resolve(filename);
}
@Override
public Resource loadAsResource(String filename) {
try {
Resource resource = new ClassPathResource(filename);
if (resource.exists() || resource.isReadable()) {
return resource;
}
else {
throw new StorageFileNotFoundException(
"Could not read file: " + filename);
}
}
catch (Exception e) {
throw new StorageFileNotFoundException("Could not read file: " + filename, e);
}
}
@Override
public void deleteAll() {
FileSystemUtils.deleteRecursively(rootLocation.toFile());
}
@Override
public void init() {
try {
Files.createDirectories(rootLocation);
}
catch (IOException e) {
throw new StorageException("Could not initialize storage", e);
}
}
}

@ -0,0 +1,24 @@
package com.alterdekim.hearthhack.service;
import org.springframework.core.io.Resource;
import org.springframework.web.multipart.MultipartFile;
import java.nio.file.Path;
import java.util.stream.Stream;
public interface StorageService {
void init();
void store(MultipartFile file);
Stream<Path> loadAll();
Path load(String filename);
Resource loadAsResource(String filename);
void deleteAll();
}

@ -1,10 +1,12 @@
package com.alterdekim.hearthhack.service;
import com.alterdekim.hearthhack.dto.UserDTO;
import com.alterdekim.hearthhack.entity.CardBack;
import com.alterdekim.hearthhack.entity.Role;
import com.alterdekim.hearthhack.entity.User;
import com.alterdekim.hearthhack.repository.*;
import lombok.RequiredArgsConstructor;
import org.checkerframework.checker.units.qual.C;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
@ -37,6 +39,9 @@ public class UserService implements IService {
}
user.setRoles(Collections.singletonList(role));
userRepository.save(user);
CardBack back = new CardBack(0, user.getId());
cardBackRepository.save(back);
userRepository.updateCardBackOfUser(user.getId(), back.getId());
}
@ -55,7 +60,12 @@ public class UserService implements IService {
String[] arr = token.split("\\-");
if( arr.length != 3 ) return false;
String hash = arr[1];
Long id = Long.parseLong(arr[2]);
long id = 0L;
try {
id = Long.parseLong(arr[2]);
} catch (Exception e) {
return false;
}
Optional<User> user = this.userRepository.findById(id);
if( user.isEmpty() ) return false;
String raw = user.get().getId() + user.get().getPassword() + user.get().getUsername();

@ -1,35 +0,0 @@
package com.alterdekim.hearthhack.util;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Random;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class Hash {
public static String sha256( byte[] b ) throws NoSuchAlgorithmException {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
return bytesToHex(digest.digest(b));
}
private static String bytesToHex(byte[] hash) {
StringBuilder hexString = new StringBuilder(2 * hash.length);
for (byte b : hash) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
return hexString.toString();
}
public static String rnd() {
return IntStream.generate(() -> new Random().nextInt(0, 62))
.limit(32)
.boxed()
.map(i -> "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".charAt(i)+"")
.collect(Collectors.joining());
}
}

@ -0,0 +1,25 @@
html,
body {
height: 100%;
}
.form-signin {
max-width: 330px;
padding: 1rem;
}
.form-signin .form-floating:focus-within {
z-index: 2;
}
.form-signin input[type="text"] {
margin-bottom: -1px;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}
.form-signin input[type="password"] {
margin-bottom: 10px;
border-top-left-radius: 0;
border-top-right-radius: 0;
}

@ -0,0 +1,13 @@
html,
body {
height: 100%;
}
.form-signup {
max-width: 330px;
padding: 1rem;
}
.form-signup .form-floating:focus-within {
z-index: 2;
}

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<th:block th:insert="~{fragments/head}"></th:block>
<link rel="stylesheet" href="/static/css/index.css">
</head>
<body>
<div class="container mt-5 min-vh-100" style="max-width: 330px;">
<h4 class="text-center">Access denied</h4>
</div>
<th:block th:insert="~{fragments/essentials}"></th:block>
</body>
</html>

@ -0,0 +1,4 @@
<th:block th:fragment="essentials">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous"></script>
</th:block>

@ -0,0 +1,8 @@
<th:block th:fragment="head">
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title th:text="${title}"></title>
<link rel="shortcut icon" type="image/x-icon" href="/static/images/favicon.ico">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
<link href="https://fonts.googleapis.com/css?family=Montserrat:400,500,800" rel="stylesheet">
</th:block>

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<th:block th:insert="~{fragments/head}"></th:block>
<link rel="stylesheet" href="/static/css/index.css">
</head>
<body class="d-flex align-items-center py-4 bg-body-tertiary">
<main class="form-signin w-100 m-auto">
<form>
<a class="btn btn-primary w-100 py-2" href="/config" download>Download client.config</a>
<p class="mt-3"><a th:href="@{/logout}" class="link-primary">Logout</a></p>
</form>
</main>
<th:block th:insert="~{fragments/essentials}"></th:block>
</body>
</html>

@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<th:block th:insert="~{fragments/head}"></th:block>
<link rel="stylesheet" href="/static/css/login.css">
</head>
<body class="d-flex align-items-center py-4 bg-body-tertiary">
<main class="form-signin w-100 m-auto">
<div th:if="${param.error}">
<div class="alert alert-danger" role="alert">Error
</div>
</div>
<div th:if="${error}">
<div class="alert alert-danger" role="alert">Error
</div>
</div>
<form method="post" th:action="@{/login}">
<h1 class="h3 mb-3 fw-normal">Login</h1>
<div class="form-floating">
<input type="text" name="username" class="form-control" id="floatingInput" placeholder="Username">
<label for="floatingInput">Nickname</label>
</div>
<div class="form-floating">
<input type="password" name="password" class="form-control" id="floatingPassword" placeholder="Password">
<label for="floatingPassword">Password</label>
</div>
<button class="btn btn-primary w-100 py-2" type="submit">Login</button>
<p class="mt-3"><a th:href="@{/signup}" class="link-primary">Signup</a></p>
</form>
</main>
<th:block th:insert="~{fragments/essentials}"></th:block>
</body>
</html>

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<th:block th:insert="~{fragments/head}"></th:block>
<link rel="stylesheet" href="/static/css/panel.css">
</head>
<body>
<th:block th:insert="~{fragments/essentials}"></th:block>
</body>
</html>

@ -0,0 +1,41 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<th:block th:insert="~{fragments/head}"></th:block>
<link rel="stylesheet" href="/static/css/signup.css">
</head>
<body class="d-flex align-items-center py-4 bg-body-tertiary">
<main class="form-signup w-100 m-auto">
<div th:if="${param.error}">
<div class="alert alert-danger" role="alert">
error
</div>
</div>
<div th:if="${error}">
<div class="alert alert-danger" role="alert">
error
</div>
</div>
<form method="post" th:action="@{/signup}" th:object="${user}">
<h1 class="h3 mb-3 fw-normal" >Sign up</h1>
<div class="form-floating">
<input type="text" name="username" class="form-control" id="floatingInput" placeholder="Username">
<label for="floatingInput">Nickname</label>
</div>
<div class="form-floating mt-1">
<input type="password" name="password" class="form-control" id="floatingPassword" placeholder="Password">
<label for="floatingPassword">Password</label>
</div>
<button class="btn btn-primary w-100 py-2 mt-3" type="submit">Sign up</button>
<p class="mt-3"><a th:href="@{/login}" class="link-primary">Login</a></p>
</form>
</main>
<th:block th:insert="~{fragments/essentials}"></th:block>
</body>
</html>