diff --git a/pom.xml b/pom.xml
index 7cd7ea1..a38c4dd 100644
--- a/pom.xml
+++ b/pom.xml
@@ -10,6 +10,7 @@
jar
+
ro.pippo
pippo
@@ -37,8 +38,24 @@
snakeyaml
1.21
-
+
+ commons-codec
+ commons-codec
+ 1.14
+
+
+
+ at.favre.lib
+ bcrypt
+ 0.9.0
+
+
+ com.auth0
+ java-jwt
+ 3.10.3
+
+
diff --git a/src/main/java/net/mindoverflow/webmarker/WebMarker.java b/src/main/java/net/mindoverflow/webmarker/WebMarker.java
index be10a16..e1985cb 100644
--- a/src/main/java/net/mindoverflow/webmarker/WebMarker.java
+++ b/src/main/java/net/mindoverflow/webmarker/WebMarker.java
@@ -1,18 +1,13 @@
package net.mindoverflow.webmarker;
-import net.mindoverflow.webmarker.runnables.StatsRunnable;
import net.mindoverflow.webmarker.utils.Cached;
-import net.mindoverflow.webmarker.utils.sql.SQLiteManager;
-import net.mindoverflow.webmarker.webserver.WebApplication;
import net.mindoverflow.webmarker.utils.config.ConfigEntries;
import net.mindoverflow.webmarker.utils.config.ConfigManager;
import net.mindoverflow.webmarker.utils.messaging.Messenger;
+import net.mindoverflow.webmarker.utils.sql.SQLiteManager;
+import net.mindoverflow.webmarker.webserver.WebApplication;
import ro.pippo.core.Pippo;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-
public class WebMarker {
private static final Messenger msg = new Messenger();
@@ -33,7 +28,9 @@ public class WebMarker {
pippo.start(port);
msg.info("Started webserver.");
+ /* todo: enable to track ram usage
ScheduledExecutorService exec = Executors.newSingleThreadScheduledExecutor();
exec.scheduleAtFixedRate(new StatsRunnable(), 0, 5, TimeUnit.SECONDS);
+ */
}
}
diff --git a/src/main/java/net/mindoverflow/webmarker/utils/FileUtils.java b/src/main/java/net/mindoverflow/webmarker/utils/FileUtils.java
new file mode 100644
index 0000000..76f25c4
--- /dev/null
+++ b/src/main/java/net/mindoverflow/webmarker/utils/FileUtils.java
@@ -0,0 +1,14 @@
+package net.mindoverflow.webmarker.utils;
+
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+
+public class FileUtils {
+
+
+ public static JsonObject stringToJson(String body)
+ {
+ JsonParser jsonParser = new JsonParser();
+ return (JsonObject) jsonParser.parse(body);
+ }
+}
diff --git a/src/main/java/net/mindoverflow/webmarker/utils/URLMap.java b/src/main/java/net/mindoverflow/webmarker/utils/URLMap.java
deleted file mode 100644
index 45e5b70..0000000
--- a/src/main/java/net/mindoverflow/webmarker/utils/URLMap.java
+++ /dev/null
@@ -1,55 +0,0 @@
-package net.mindoverflow.webmarker.utils;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class URLMap {
-
- private static List ids = new ArrayList<>();
- private static List urls = new ArrayList<>();
-
- public static void saveUrl(int userId, String url)
- {
- ids.add(userId);
- urls.add(url);
- System.out.println();
- System.out.println("Saved ID: " + userId + "; URL: " + url);
- System.out.println("table is now:");
- for(int pos = 0; pos < ids.size(); pos++)
- {
- System.out.println("ID = " + ids.get(pos) + "; URL = " + urls.get(pos));
- }
- System.out.println();
- }
-
- public static List getUserUrls(int userId)
- {
- ListthisUserUrls = new ArrayList<>();
- for(int pos = 0; pos < ids.size(); pos++)
- {
- if(userId == ids.get(pos))
- {
- thisUserUrls.add(urls.get(pos));
- }
- }
-
- return thisUserUrls;
- }
-
- public static void dropUser(int userId)
- {
- List newIds = new ArrayList<>();
- List newUrls = new ArrayList<>();
- for(int pos = 0; pos < ids.size(); pos++)
- {
- if(ids.get(pos) != userId)
- {
- newIds.add(ids.get(pos));
- newUrls.add(urls.get(pos));
- }
- }
-
- ids = newIds;
- urls = newUrls;
- }
-}
diff --git a/src/main/java/net/mindoverflow/webmarker/utils/config/ConfigEntries.java b/src/main/java/net/mindoverflow/webmarker/utils/config/ConfigEntries.java
index 7a7346f..10444f6 100644
--- a/src/main/java/net/mindoverflow/webmarker/utils/config/ConfigEntries.java
+++ b/src/main/java/net/mindoverflow/webmarker/utils/config/ConfigEntries.java
@@ -2,7 +2,10 @@ package net.mindoverflow.webmarker.utils.config;
public enum ConfigEntries
{
- WEBSERVER_PORT("port", 7344);
+ WEBSERVER_PORT("port", 7344),
+ JWT_SECRET("secret", "changeMe"),
+
+ ;
private final String path;
private Object value;
diff --git a/src/main/java/net/mindoverflow/webmarker/utils/config/ConfigManager.java b/src/main/java/net/mindoverflow/webmarker/utils/config/ConfigManager.java
index 2850968..861acff 100644
--- a/src/main/java/net/mindoverflow/webmarker/utils/config/ConfigManager.java
+++ b/src/main/java/net/mindoverflow/webmarker/utils/config/ConfigManager.java
@@ -64,7 +64,10 @@ public class ConfigManager
msg.sendLine(MessageLevel.NONE, "OK");
for(ConfigEntries entry : ConfigEntries.values())
- { msg.info(entry.name() + ": " + entry.getValue()); }
+ {
+ if(entry == ConfigEntries.JWT_SECRET) continue; // we don't want to log encryption secret key
+ msg.info(entry.name() + ": " + entry.getValue());
+ }
}
public static String getJarAbsolutePath()
diff --git a/src/main/java/net/mindoverflow/webmarker/utils/security/EncryptionUtils.java b/src/main/java/net/mindoverflow/webmarker/utils/security/EncryptionUtils.java
new file mode 100644
index 0000000..d43f70c
--- /dev/null
+++ b/src/main/java/net/mindoverflow/webmarker/utils/security/EncryptionUtils.java
@@ -0,0 +1,65 @@
+package net.mindoverflow.webmarker.utils.security;
+
+import at.favre.lib.crypto.bcrypt.BCrypt;
+import org.apache.commons.codec.binary.Hex;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import java.io.UnsupportedEncodingException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Base64;
+
+public class EncryptionUtils {
+ public static String hmacSha256(String key, String value)
+ {
+ byte[] keyBytes = key.getBytes();
+ SecretKeySpec signingKey = new SecretKeySpec(keyBytes, "HmacSHA256");
+
+ try {
+ Mac mac = Mac.getInstance("HmacSHA256");
+ mac.init(signingKey);
+
+ byte[] rawHmac = mac.doFinal(value.getBytes());
+
+ byte[] hexBytes = new Hex().encode(rawHmac);
+
+ return new String(hexBytes, "UTF-8");
+ } catch (NoSuchAlgorithmException | InvalidKeyException | UnsupportedEncodingException e) {
+ e.printStackTrace();
+ }
+
+ return null;
+ }
+
+ public static String bcrypt(String value)
+ {
+ return BCrypt.withDefaults().hashToString(12, value.toCharArray()); // todo: custom salt
+ }
+
+ public static boolean bcryptMatches(String storedValue, String unencrypted)
+ {
+ BCrypt.Result result = BCrypt.verifyer().verify(unencrypted.toCharArray(), storedValue);
+ return result.verified;
+ }
+
+ public static String handleEncoding(String encoding, String encodedPassword)
+ {
+ String password;
+
+ switch (encoding.toLowerCase())
+ {
+ case "plaintext":
+ password = encodedPassword;
+ break;
+ case "base64":
+ password = new String(Base64.getDecoder().decode(encodedPassword));
+ break;
+ default:
+ password = "";
+ break;
+ }
+
+ return password;
+ }
+}
diff --git a/src/main/java/net/mindoverflow/webmarker/utils/security/SafetyCheck.java b/src/main/java/net/mindoverflow/webmarker/utils/security/SafetyCheck.java
new file mode 100644
index 0000000..58ce3df
--- /dev/null
+++ b/src/main/java/net/mindoverflow/webmarker/utils/security/SafetyCheck.java
@@ -0,0 +1,33 @@
+package net.mindoverflow.webmarker.utils.security;
+
+public class SafetyCheck {
+
+ public static boolean isSafeUsername(String username)
+ {
+ // todo: allow configuration
+ if(!username.matches("[a-zA-Z0-9]*")) return false;
+ if(username.length() > 15) return false;
+ if(username.length() < 3) return false;
+ if(username.equalsIgnoreCase("null")) return false;
+
+ return true;
+ }
+
+ public static boolean isSafePassword(String password)
+ {
+ if(password.length() < 6) return false;
+ if(password.getBytes().length > 71) return false; // see https://github.com/patrickfav/bcrypt#handling-for-overlong-passwords
+
+ // todo: more password security
+
+ return true;
+ }
+
+ public static boolean isValidEncoding(String encoding)
+ {
+ if(encoding.equalsIgnoreCase("base64")) return true;
+ if(encoding.equalsIgnoreCase("plaintext")) return true;
+
+ return false;
+ }
+}
diff --git a/src/main/java/net/mindoverflow/webmarker/utils/sql/FDatabaseTable.java b/src/main/java/net/mindoverflow/webmarker/utils/sql/FDatabaseTable.java
deleted file mode 100644
index 3d5b182..0000000
--- a/src/main/java/net/mindoverflow/webmarker/utils/sql/FDatabaseTable.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package net.mindoverflow.webmarker.utils.sql;
-
-
-import net.mindoverflow.webmarker.utils.sql.primitives.SQLTable;
-
-import java.util.ArrayList;
-
-public enum FDatabaseTable
-{
- USERS(new SQLTable("users", // table name
- new ArrayList<>(){{ // columns
- add(FDatabaseColumn.USERID);
- add(FDatabaseColumn.USERNAME);
- add(FDatabaseColumn.PASSWORD);
- }})),
-
- ;
-
- private final SQLTable table;
-
- FDatabaseTable(SQLTable table)
- { this.table = table; }
-
- public SQLTable getTable()
- { return table; }
-}
diff --git a/src/main/java/net/mindoverflow/webmarker/utils/sql/FDatabaseColumn.java b/src/main/java/net/mindoverflow/webmarker/utils/sql/MDatabaseColumn.java
similarity index 74%
rename from src/main/java/net/mindoverflow/webmarker/utils/sql/FDatabaseColumn.java
rename to src/main/java/net/mindoverflow/webmarker/utils/sql/MDatabaseColumn.java
index b0fedee..a5780cf 100644
--- a/src/main/java/net/mindoverflow/webmarker/utils/sql/FDatabaseColumn.java
+++ b/src/main/java/net/mindoverflow/webmarker/utils/sql/MDatabaseColumn.java
@@ -3,14 +3,14 @@ package net.mindoverflow.webmarker.utils.sql;
import net.mindoverflow.webmarker.utils.sql.primitives.SQLColumn;
import net.mindoverflow.webmarker.utils.sql.primitives.SQLDataType;
-public enum FDatabaseColumn
+public enum MDatabaseColumn
{
ALL(new SQLColumn("*"), null),
USERNAME(new SQLColumn("username"), SQLDataType.VARCHAR_128),
PASSWORD(new SQLColumn("password"), SQLDataType.VARCHAR_128),
- USERID(new SQLColumn("userid"), SQLDataType.VARCHAR_128),
-
+ USER_UUID(new SQLColumn("userid"), SQLDataType.VARCHAR_128),
+ WEB_DOMAIN(new SQLColumn("domain"), SQLDataType.VARCHAR_128),
;
@@ -18,7 +18,7 @@ public enum FDatabaseColumn
private final SQLColumn column;
private final SQLDataType type;
- FDatabaseColumn(SQLColumn column, SQLDataType type)
+ MDatabaseColumn(SQLColumn column, SQLDataType type)
{
this.column = column;
this.type = type;
diff --git a/src/main/java/net/mindoverflow/webmarker/utils/sql/MDatabaseTable.java b/src/main/java/net/mindoverflow/webmarker/utils/sql/MDatabaseTable.java
new file mode 100644
index 0000000..3b3b85e
--- /dev/null
+++ b/src/main/java/net/mindoverflow/webmarker/utils/sql/MDatabaseTable.java
@@ -0,0 +1,33 @@
+package net.mindoverflow.webmarker.utils.sql;
+
+
+import net.mindoverflow.webmarker.utils.sql.primitives.SQLTable;
+
+import java.util.ArrayList;
+
+public enum MDatabaseTable
+{
+ USERS(new SQLTable("users", // table name
+ new ArrayList<>(){{ // columns
+ add(MDatabaseColumn.USER_UUID);
+ add(MDatabaseColumn.USERNAME);
+ add(MDatabaseColumn.PASSWORD);
+ }})),
+
+ HISTORY(new SQLTable("history",
+ new ArrayList<>(){{
+ add(MDatabaseColumn.USER_UUID);
+ add(MDatabaseColumn.WEB_DOMAIN);
+
+ }}))
+
+ ;
+
+ private final SQLTable table;
+
+ MDatabaseTable(SQLTable table)
+ { this.table = table; }
+
+ public SQLTable getTable()
+ { return table; }
+}
diff --git a/src/main/java/net/mindoverflow/webmarker/utils/sql/MarkerSQLUtils.java b/src/main/java/net/mindoverflow/webmarker/utils/sql/MarkerSQLUtils.java
new file mode 100644
index 0000000..0e7b6bb
--- /dev/null
+++ b/src/main/java/net/mindoverflow/webmarker/utils/sql/MarkerSQLUtils.java
@@ -0,0 +1,82 @@
+package net.mindoverflow.webmarker.utils.sql;
+
+import net.mindoverflow.webmarker.utils.Cached;
+
+import java.util.List;
+import java.util.UUID;
+
+public class MarkerSQLUtils {
+
+ public static boolean addUser(UUID randomId, String name, String password)
+ {
+
+ String query = "INSERT INTO " + MDatabaseTable.USERS.getTable().getTableSQLName() + " (" +
+ MDatabaseColumn.USER_UUID.getColumn().getColumnSQLName() + ", " +
+ MDatabaseColumn.USERNAME.getColumn().getColumnSQLName() + ", " +
+ MDatabaseColumn.PASSWORD.getColumn().getColumnSQLName() + ") VALUES (?, ?, ?);";
+
+ return Cached.sqlManager.executeUpdate(query, randomId.toString(), name, password);
+ }
+
+ public static boolean userExists(String name)
+ {
+ String query = "SELECT " + MDatabaseColumn.USERNAME.getColumn().getColumnSQLName() +
+ " FROM " + MDatabaseTable.USERS.getTable().getTableSQLName() +
+ " WHERE " + MDatabaseColumn.USERNAME.getColumn().getColumnSQLName() +
+ " = ? ;";
+
+ List result = Cached.sqlManager.executeStatement(query, MDatabaseColumn.USERNAME, name);
+ return result.size() > 0;
+ }
+
+ public static boolean uuidTaken(UUID randomId)
+ {
+ String query = "SELECT " + MDatabaseColumn.USER_UUID.getColumn().getColumnSQLName() +
+ " FROM " + MDatabaseTable.USERS.getTable().getTableSQLName() +
+ " WHERE " + MDatabaseColumn.USER_UUID.getColumn().getColumnSQLName() +
+ " = ? ;";
+
+ List result = Cached.sqlManager.executeStatement(query, MDatabaseColumn.USER_UUID, randomId.toString());
+ if(result.size() > 0) return true;
+
+ return false;
+ }
+
+ public static UUID getUserUUID(String username)
+ {
+ String query = "SELECT " + MDatabaseColumn.USER_UUID.getColumn().getColumnSQLName() +
+ " FROM " + MDatabaseTable.USERS.getTable().getTableSQLName() +
+ " WHERE " + MDatabaseColumn.USERNAME.getColumn().getColumnSQLName() +
+ " = ? ;";
+
+ List result = Cached.sqlManager.executeStatement(query, MDatabaseColumn.USER_UUID, username);
+ if(result.size() != 1) return null; //todo: error!
+
+ return UUID.fromString(result.get(0));
+ }
+
+ // todo: use UUID?
+ public static String getUserBcryptedPassword(String username)
+ {
+ String query = "SELECT " + MDatabaseColumn.PASSWORD.getColumn().getColumnSQLName() +
+ " FROM " + MDatabaseTable.USERS.getTable().getTableSQLName() +
+ " WHERE " + MDatabaseColumn.USERNAME.getColumn().getColumnSQLName() +
+ " = ? ;";
+
+ List result = Cached.sqlManager.executeStatement(query, MDatabaseColumn.PASSWORD, username);
+ if(result.size() != 1) return null; // todo: error!
+
+ return result.get(0);
+ }
+
+ public static boolean addHistoryRecord(UUID uuid, String url)
+ {
+ String query = "INSERT INTO " + MDatabaseTable.HISTORY.getTable().getTableSQLName() + " (" +
+ MDatabaseColumn.USER_UUID.getColumn().getColumnSQLName() + ", " +
+ MDatabaseColumn.WEB_DOMAIN.getColumn().getColumnSQLName() + ") VALUES (?, ?);";
+
+ return Cached.sqlManager.executeUpdate(query, uuid.toString(), url);
+
+
+ }
+}
diff --git a/src/main/java/net/mindoverflow/webmarker/utils/sql/SQLiteManager.java b/src/main/java/net/mindoverflow/webmarker/utils/sql/SQLiteManager.java
index 8dac86e..4eda119 100644
--- a/src/main/java/net/mindoverflow/webmarker/utils/sql/SQLiteManager.java
+++ b/src/main/java/net/mindoverflow/webmarker/utils/sql/SQLiteManager.java
@@ -7,6 +7,7 @@ import net.mindoverflow.webmarker.utils.sql.primitives.SQLDataType;
import net.mindoverflow.webmarker.utils.sql.primitives.SQLTable;
import java.sql.*;
+import java.util.ArrayList;
import java.util.List;
public class SQLiteManager {
@@ -37,17 +38,17 @@ public class SQLiteManager {
private void doInitialSetup()
{
- for(FDatabaseTable currentTable : FDatabaseTable.values())
+ for(MDatabaseTable currentTable : MDatabaseTable.values())
{
if(!tableExists(currentTable))
{
- msg.info("Creating SQLite table `" + currentTable.getTable().getTableSQLName() + "`");
+ msg.info("Creating SQLite table '" + currentTable.getTable().getTableSQLName() + "'");
createTable(currentTable);
}
}
}
- private boolean tableExists(FDatabaseTable tableEnum)
+ private boolean tableExists(MDatabaseTable tableEnum)
{
String name = tableEnum.getTable().getTableSQLName();
@@ -70,16 +71,16 @@ public class SQLiteManager {
return false;
}
- private void createTable(FDatabaseTable tableEnum)
+ private void createTable(MDatabaseTable tableEnum)
{
SQLTable table = tableEnum.getTable();
- List columns = table.getColumns();
+ List columns = table.getColumns();
List dataTypes = table.getDataTypes();
StringBuilder query = new StringBuilder("CREATE TABLE IF NOT EXISTS ").append(table.getTableSQLName()).append(" (");
int pos = 0;
- for(FDatabaseColumn column : columns)
+ for(MDatabaseColumn column : columns)
{
query.append(column.getColumn().getColumnSQLName()).append(" ").append(dataTypes.get(pos).getSQLName());
pos++;
@@ -91,7 +92,8 @@ public class SQLiteManager {
executeUpdate(query.toString());
}
- private void executeUpdate(String query)
+ @Deprecated
+ boolean executeUpdate(String query)
{
try
{
@@ -99,15 +101,85 @@ public class SQLiteManager {
{
msg.critical("Lost connection to SQLite database!");
System.exit(1);
+ return false;
}
Statement statement = connection.createStatement();
statement.executeUpdate(query);
+ return true;
} catch (SQLException e)
{
e.printStackTrace();
msg.critical("Error executing SQLite update!");
System.exit(1);
+ return false;
}
}
+
+ public boolean executeUpdate(String query, String... strings)
+ {
+ try {
+
+ if(connection == null || connection.isClosed())
+ {
+ msg.critical("Lost connection to SQLite database!");
+ System.exit(1);
+ return false;
+ }
+
+ PreparedStatement statement = connection.prepareStatement(query);
+
+ int pos = 1;
+ for(String s : strings)
+ {
+ statement.setString(pos, s);
+ pos++;
+ }
+ statement.executeUpdate();
+ return true;
+
+
+ } catch (SQLException throwables) {
+ throwables.printStackTrace();
+ // todo: error
+ }
+
+ return false;
+ }
+
+ public List executeStatement(String query, MDatabaseColumn column, String... strings)
+ {
+ try {
+ if(connection == null || connection.isClosed())
+ {
+ msg.critical("Lost connection to SQLite database!");
+ System.exit(1);
+ return null;
+ }
+
+ PreparedStatement statement = connection.prepareStatement(query);
+
+ String columnSqlName = column.getColumn().getColumnSQLName();
+
+ int pos = 1;
+ for(String s : strings)
+ {
+ statement.setString(pos, s);
+ pos++;
+ }
+
+ ResultSet resultSet = statement.executeQuery();
+ List values = new ArrayList<>();
+ while(resultSet.next())
+ {
+ values.add(resultSet.getString(columnSqlName));
+ }
+ return values;
+
+ } catch (SQLException e) {
+ e.printStackTrace(); //todo: error
+ }
+
+ return null;
+ }
}
diff --git a/src/main/java/net/mindoverflow/webmarker/utils/sql/primitives/SQLTable.java b/src/main/java/net/mindoverflow/webmarker/utils/sql/primitives/SQLTable.java
index aa856d7..9d3b524 100644
--- a/src/main/java/net/mindoverflow/webmarker/utils/sql/primitives/SQLTable.java
+++ b/src/main/java/net/mindoverflow/webmarker/utils/sql/primitives/SQLTable.java
@@ -1,6 +1,6 @@
package net.mindoverflow.webmarker.utils.sql.primitives;
-import net.mindoverflow.webmarker.utils.sql.FDatabaseColumn;
+import net.mindoverflow.webmarker.utils.sql.MDatabaseColumn;
import java.util.ArrayList;
import java.util.List;
@@ -8,22 +8,22 @@ import java.util.List;
public class SQLTable
{
private final String tableName;
- private final List columns;
+ private final List columns;
private final List columnTypes = new ArrayList<>();
- public SQLTable(String tableName, List columns)
+ public SQLTable(String tableName, List columns)
{
this.tableName = tableName;
this.columns = columns;
- for(FDatabaseColumn column : columns)
+ for(MDatabaseColumn column : columns)
{ columnTypes.add(column.getDataType()); }
}
public String getTableSQLName()
{ return tableName; }
- public List getColumns()
+ public List getColumns()
{ return columns; }
public List getDataTypes()
diff --git a/src/main/java/net/mindoverflow/webmarker/webserver/WebApplication.java b/src/main/java/net/mindoverflow/webmarker/webserver/WebApplication.java
index 188975a..578a500 100644
--- a/src/main/java/net/mindoverflow/webmarker/webserver/WebApplication.java
+++ b/src/main/java/net/mindoverflow/webmarker/webserver/WebApplication.java
@@ -1,57 +1,19 @@
package net.mindoverflow.webmarker.webserver;
-import net.mindoverflow.webmarker.utils.URLMap;
+import net.mindoverflow.webmarker.webserver.controllers.LoginController;
+import net.mindoverflow.webmarker.webserver.controllers.RegisterController;
+import net.mindoverflow.webmarker.webserver.controllers.StorageController;
import ro.pippo.controller.ControllerApplication;
import ro.pippo.core.route.TrailingSlashHandler;
-import java.util.List;
-
public class WebApplication extends ControllerApplication {
@Override
- protected void onInit() {
-
- GET("/save", routeContext -> routeContext.send("Please append an user ID"));
- GET("/save/{id}", routeContext -> routeContext.send("Please append an URL"));
- GET("/save/{id}/{url}", routeContext ->
- {
- int userId = routeContext.getParameter("id").toInt();
- String saveUrl = routeContext.getParameter("url").toString();
-
- URLMap.saveUrl(userId, saveUrl);
- routeContext.send("Saved!");
- });
-
- GET("/get", routeContext -> routeContext.send("Please append an user ID"));
- GET("/get/{id}", routeContext ->
- {
- int userId = routeContext.getParameter("id").toInt();
-
- List urls = URLMap.getUserUrls(userId);
- StringBuilder urlsSB = new StringBuilder(" | ");
- for(String url : urls)
- {
- urlsSB.append(url).append(" | ");
- }
- routeContext.send(urlsSB.toString());
-
- });
-
- GET("/drop", routeContext -> routeContext.send("Please append an user ID"));
- GET("/drop/{id}", routeContext ->
- {
- int userId = routeContext.getParameter("id").toInt();
- URLMap.dropUser(userId);
- routeContext.send("Dropped!");
-
- });
-
- POST("/post", routeContext -> {
- int userId = routeContext.getParameter("id").toInt();
- String url = routeContext.getParameter("url").toString();
-
- routeContext.send("AAAAAAAAAAAAA " + url);
- });
+ protected void onInit()
+ {
+ addControllers(new LoginController());
+ addControllers(new RegisterController());
+ addControllers(new StorageController());
ANY("/.*", new TrailingSlashHandler(false)); // remove trailing slash
}
diff --git a/src/main/java/net/mindoverflow/webmarker/webserver/controllers/LoginController.java b/src/main/java/net/mindoverflow/webmarker/webserver/controllers/LoginController.java
new file mode 100644
index 0000000..280cba3
--- /dev/null
+++ b/src/main/java/net/mindoverflow/webmarker/webserver/controllers/LoginController.java
@@ -0,0 +1,82 @@
+package net.mindoverflow.webmarker.webserver.controllers;
+
+import com.auth0.jwt.JWT;
+import com.auth0.jwt.algorithms.Algorithm;
+import com.google.gson.JsonObject;
+import net.mindoverflow.webmarker.utils.FileUtils;
+import net.mindoverflow.webmarker.utils.config.ConfigEntries;
+import net.mindoverflow.webmarker.utils.messaging.Messenger;
+import net.mindoverflow.webmarker.utils.security.EncryptionUtils;
+import net.mindoverflow.webmarker.utils.security.SafetyCheck;
+import net.mindoverflow.webmarker.utils.sql.MarkerSQLUtils;
+import ro.pippo.controller.Controller;
+import ro.pippo.controller.POST;
+import ro.pippo.controller.Path;
+import ro.pippo.core.route.RouteContext;
+
+import java.time.ZonedDateTime;
+import java.util.Date;
+
+@Path("/api/v1/login")
+public class LoginController extends Controller
+{
+ private final Messenger msg = new Messenger();
+
+ @POST
+ public void login()
+ {
+ RouteContext routeContext = getRouteContext();
+
+
+ String body = routeContext.getRequest().getBody();
+ JsonObject jsonObject = FileUtils.stringToJson(body);
+
+ String username = jsonObject.get("username").getAsString();
+ String encodedPassword = jsonObject.get("password").getAsString();
+ String encoding = jsonObject.get("encoding").getAsString();
+
+ if(!SafetyCheck.isValidEncoding(encoding))
+ {
+ routeContext.send("Invalid encoding: '" + encoding + "'!");
+ return;
+ }
+
+ String password = EncryptionUtils.handleEncoding(encoding, encodedPassword);
+
+ if(!SafetyCheck.isSafeUsername(username))
+ {
+ routeContext.send("Invalid username!");
+ return;
+ }
+
+ if(!SafetyCheck.isSafePassword(password))
+ {
+ routeContext.send("Invalid password!");
+ return;
+ }
+
+ if(!MarkerSQLUtils.userExists(username))
+ {
+ routeContext.send("User does not exist!");
+ return;
+ }
+
+ String bcryptedStoredPassword = MarkerSQLUtils.getUserBcryptedPassword(username);
+
+ if(!EncryptionUtils.bcryptMatches(bcryptedStoredPassword, password))
+ {
+ routeContext.send("Wrong password!");
+ return;
+ }
+
+ // JWT
+ Algorithm algorithm = Algorithm.HMAC256((String) ConfigEntries.JWT_SECRET.getValue());
+ String token = JWT.create()
+ .withClaim("username", username)
+ .withExpiresAt(Date.from(ZonedDateTime.now().plusMinutes(60).toInstant()))
+ .sign(algorithm);
+
+ routeContext.send(token);
+ msg.info("User " + username + " logged in!");
+ }
+}
diff --git a/src/main/java/net/mindoverflow/webmarker/webserver/controllers/RegisterController.java b/src/main/java/net/mindoverflow/webmarker/webserver/controllers/RegisterController.java
new file mode 100644
index 0000000..f3d417c
--- /dev/null
+++ b/src/main/java/net/mindoverflow/webmarker/webserver/controllers/RegisterController.java
@@ -0,0 +1,68 @@
+package net.mindoverflow.webmarker.webserver.controllers;
+
+import com.google.gson.JsonObject;
+import net.mindoverflow.webmarker.utils.FileUtils;
+import net.mindoverflow.webmarker.utils.security.EncryptionUtils;
+import net.mindoverflow.webmarker.utils.security.SafetyCheck;
+import net.mindoverflow.webmarker.utils.sql.MarkerSQLUtils;
+import ro.pippo.controller.Controller;
+import ro.pippo.controller.POST;
+import ro.pippo.controller.Path;
+import ro.pippo.core.route.RouteContext;
+
+import java.util.UUID;
+
+@Path("/api/v1/register")
+public class RegisterController extends Controller
+{
+ @POST
+ public void register()
+ {
+ RouteContext routeContext = getRouteContext();
+
+ String body = routeContext.getRequest().getBody();
+ JsonObject jsonObject = FileUtils.stringToJson(body);
+
+ String username = jsonObject.get("username").getAsString();
+ String encodedPassword = jsonObject.get("password").getAsString();
+ String encoding = jsonObject.get("encoding").getAsString();
+
+ if(!SafetyCheck.isValidEncoding(encoding))
+ {
+ routeContext.send("Invalid encoding: '" + encoding + "'!");
+ return;
+ }
+
+ String password = EncryptionUtils.handleEncoding(encoding, encodedPassword);
+
+ if(!SafetyCheck.isSafeUsername(username))
+ {
+ routeContext.send("Invalid username!");
+ return;
+ }
+
+ if(!SafetyCheck.isSafePassword(password))
+ {
+ routeContext.send("Invalid password!");
+ return;
+ }
+
+ if(MarkerSQLUtils.userExists(username))
+ {
+ routeContext.send("User exists!");
+ return;
+ }
+
+ // generate a random UUID, to identify same user in different tables
+ UUID randomId = UUID.randomUUID();
+
+ // check if the UUID is already taken by another user
+ while(MarkerSQLUtils.uuidTaken(randomId))
+ {
+ randomId = UUID.randomUUID();
+ }
+
+ if(MarkerSQLUtils.addUser(randomId, username, EncryptionUtils.bcrypt(password))) routeContext.send("Added user!");
+ }
+
+}
diff --git a/src/main/java/net/mindoverflow/webmarker/webserver/controllers/StorageController.java b/src/main/java/net/mindoverflow/webmarker/webserver/controllers/StorageController.java
new file mode 100644
index 0000000..7f48c4b
--- /dev/null
+++ b/src/main/java/net/mindoverflow/webmarker/webserver/controllers/StorageController.java
@@ -0,0 +1,162 @@
+package net.mindoverflow.webmarker.webserver.controllers;
+
+import com.auth0.jwt.JWT;
+import com.auth0.jwt.JWTVerifier;
+import com.auth0.jwt.algorithms.Algorithm;
+import com.auth0.jwt.exceptions.JWTVerificationException;
+import com.auth0.jwt.interfaces.Claim;
+import com.auth0.jwt.interfaces.DecodedJWT;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import net.mindoverflow.webmarker.utils.FileUtils;
+import net.mindoverflow.webmarker.utils.config.ConfigEntries;
+import net.mindoverflow.webmarker.utils.security.SafetyCheck;
+import net.mindoverflow.webmarker.utils.sql.MarkerSQLUtils;
+import ro.pippo.controller.Controller;
+import ro.pippo.controller.POST;
+import ro.pippo.controller.Path;
+import ro.pippo.core.route.RouteContext;
+
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.UUID;
+
+@Path("/api/v1/store")
+public class StorageController extends Controller
+{
+
+ private RouteContext routeContext;
+
+ @POST
+ public void store()
+ {
+ // Load RouteContext
+ routeContext = getRouteContext();
+
+ // Load JSON object from body
+ String body = routeContext.getRequest().getBody();
+ JsonObject jsonObject = FileUtils.stringToJson(body);
+
+ // Load JWT element
+ JsonElement jwtElement = getJwtFromJson(jsonObject);
+ if(jwtElement == null) return;
+
+ // Decrypt/Verify JWT
+ DecodedJWT jwt = decryptAndVerifyJwt(jwtElement);
+ if(jwt == null) return;
+
+ // Load username from JWT
+ String username = getFromJwt(jwt, "username");
+ if(username == null) return;
+
+ // Check some stuff about the user
+ if(!userCheckPassed(username)) return;
+
+ // Load UUID from username
+ UUID uuid = MarkerSQLUtils.getUserUUID(username);
+ if(uuid == null)
+ {
+ routeContext.send("Server error: missing UUID!");
+ return;
+ }
+
+ // Load url from Json body
+ String url = getFromJson(jsonObject, "url");
+ if(url == null) return;
+
+ // Verify url validity
+ URI confirmedUrl = verifyUrl(url);
+ if(confirmedUrl == null) return;
+
+ MarkerSQLUtils.addHistoryRecord(uuid, confirmedUrl.toString());
+ routeContext.send("OK!");
+
+ }
+
+ private JsonElement getJwtFromJson(JsonObject jsonObject)
+ {
+ JsonElement jwtElement = jsonObject.get("jwt");
+ if(jwtElement == null || jwtElement.isJsonNull())
+ {
+ routeContext.send("Invalid JWT!"); //todo: throw exception instead?
+ return null;
+ }
+ return jwtElement;
+ }
+
+ private DecodedJWT decryptAndVerifyJwt(JsonElement jwtElement)
+ {
+ String token = jwtElement.getAsString();
+
+ try {
+ Algorithm algorithm = Algorithm.HMAC256((String) ConfigEntries.JWT_SECRET.getValue());
+ JWTVerifier jwtVerifier = JWT.require(algorithm)
+ .build();
+
+ return jwtVerifier.verify(token);
+ }
+ catch (JWTVerificationException e)
+ {
+ routeContext.send("Invalid JWT!");
+ return null;
+ }
+ }
+
+ private String getFromJwt(DecodedJWT jwt, String claimName)
+ {
+ Claim claim = jwt.getClaim(claimName);
+ if(claim == null || claim.isNull())
+ {
+ routeContext.send("JWT missing '" + claimName + "' claim!");
+ return null;
+ }
+
+ return claim.asString();
+ }
+
+ private boolean userCheckPassed(String username)
+ {
+ if(!SafetyCheck.isSafeUsername(username))
+ {
+ routeContext.send("Invalid username!");
+ return false;
+ }
+
+ if(!MarkerSQLUtils.userExists(username))
+ {
+ routeContext.send("User does not exist!");
+ return false;
+ }
+
+ return true;
+ }
+
+ private String getFromJson(JsonObject jsonObject, String name)
+ {
+
+ JsonElement jsonElement = jsonObject.get(name);
+ if(jsonElement == null || jsonElement.isJsonNull())
+ {
+ routeContext.send("JSON body missing '" + name + "' entry!");
+ return null;
+ }
+
+ return jsonElement.getAsString();
+ }
+
+ private URI verifyUrl(String url)
+ {
+ try {
+ return new URL(url).toURI();
+ } catch (URISyntaxException e) {
+ routeContext.send("Invalid URI!");
+ }
+ catch (MalformedURLException e) {
+ routeContext.send("Invalid URL!");
+ }
+
+ return null;
+ }
+}
diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml
index 20a8781..62d7364 100644
--- a/src/main/resources/config.yml
+++ b/src/main/resources/config.yml
@@ -1 +1,2 @@
-port: 7344
\ No newline at end of file
+port: 7344
+secret: '398JC3lDk1Ckaock3dcnc938COAk9d'
\ No newline at end of file