Implement RESTful API, JWT auth, SQLite storage
This update brings a huge change to the whole system's structure.
A new RESTful API has been implemented, which allows users to register, login
and store data.
The API only supports HTTP POST, and can be accessed via /api/v1/. Requests must
contain a JSON body with the necessary entries, which are:
/api/v1/register AND /api/v1/login:
{
"username": "username",
"password": "password",
"encoding": "plaintext/base64"
}
(Note: passwords can be encoded via "base64" or "plaintext".)
/api/v1/store:
{
"jwt": "encrypted_key_here",
"url": "https://google.com/"
}
The flow is:
- register via /api/v1/register;
- login via /api/v1/login, listen for JWT token in response;
- store via /api/v1/store, by sending JWT and URL to store.
The SQLite database now has 2 tables, "users" and "history".
The "users" table is used to store user data:
- username;
- password, secured via bcrypt;
- random user UUID.
The "history" table is used to store browsing history:
- user UUID, to identify the user;
- browsed url.
The secret used to sign JWTs is stored in the config.yml file.
Other new features include SQL-injection protection,
multiple validity/security checks on usernames and passwords, etc.
Signed-off-by: Lorenzo Dellacà <lorenzo.dellaca@mind-overflow.net>
2020-08-22 12:51:33 +02:00
|
|
|
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;
|
2020-08-22 18:32:46 +02:00
|
|
|
import java.time.*;
|
|
|
|
import java.time.format.DateTimeFormatter;
|
|
|
|
import java.time.temporal.ChronoUnit;
|
Implement RESTful API, JWT auth, SQLite storage
This update brings a huge change to the whole system's structure.
A new RESTful API has been implemented, which allows users to register, login
and store data.
The API only supports HTTP POST, and can be accessed via /api/v1/. Requests must
contain a JSON body with the necessary entries, which are:
/api/v1/register AND /api/v1/login:
{
"username": "username",
"password": "password",
"encoding": "plaintext/base64"
}
(Note: passwords can be encoded via "base64" or "plaintext".)
/api/v1/store:
{
"jwt": "encrypted_key_here",
"url": "https://google.com/"
}
The flow is:
- register via /api/v1/register;
- login via /api/v1/login, listen for JWT token in response;
- store via /api/v1/store, by sending JWT and URL to store.
The SQLite database now has 2 tables, "users" and "history".
The "users" table is used to store user data:
- username;
- password, secured via bcrypt;
- random user UUID.
The "history" table is used to store browsing history:
- user UUID, to identify the user;
- browsed url.
The secret used to sign JWTs is stored in the config.yml file.
Other new features include SQL-injection protection,
multiple validity/security checks on usernames and passwords, etc.
Signed-off-by: Lorenzo Dellacà <lorenzo.dellaca@mind-overflow.net>
2020-08-22 12:51:33 +02:00
|
|
|
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
|
2020-08-22 18:32:46 +02:00
|
|
|
if(!usernameCheckPassed(username)) return;
|
Implement RESTful API, JWT auth, SQLite storage
This update brings a huge change to the whole system's structure.
A new RESTful API has been implemented, which allows users to register, login
and store data.
The API only supports HTTP POST, and can be accessed via /api/v1/. Requests must
contain a JSON body with the necessary entries, which are:
/api/v1/register AND /api/v1/login:
{
"username": "username",
"password": "password",
"encoding": "plaintext/base64"
}
(Note: passwords can be encoded via "base64" or "plaintext".)
/api/v1/store:
{
"jwt": "encrypted_key_here",
"url": "https://google.com/"
}
The flow is:
- register via /api/v1/register;
- login via /api/v1/login, listen for JWT token in response;
- store via /api/v1/store, by sending JWT and URL to store.
The SQLite database now has 2 tables, "users" and "history".
The "users" table is used to store user data:
- username;
- password, secured via bcrypt;
- random user UUID.
The "history" table is used to store browsing history:
- user UUID, to identify the user;
- browsed url.
The secret used to sign JWTs is stored in the config.yml file.
Other new features include SQL-injection protection,
multiple validity/security checks on usernames and passwords, etc.
Signed-off-by: Lorenzo Dellacà <lorenzo.dellaca@mind-overflow.net>
2020-08-22 12:51:33 +02:00
|
|
|
|
|
|
|
// 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
|
2020-08-22 18:32:46 +02:00
|
|
|
URI confirmedUrl = stringToURI(url);
|
Implement RESTful API, JWT auth, SQLite storage
This update brings a huge change to the whole system's structure.
A new RESTful API has been implemented, which allows users to register, login
and store data.
The API only supports HTTP POST, and can be accessed via /api/v1/. Requests must
contain a JSON body with the necessary entries, which are:
/api/v1/register AND /api/v1/login:
{
"username": "username",
"password": "password",
"encoding": "plaintext/base64"
}
(Note: passwords can be encoded via "base64" or "plaintext".)
/api/v1/store:
{
"jwt": "encrypted_key_here",
"url": "https://google.com/"
}
The flow is:
- register via /api/v1/register;
- login via /api/v1/login, listen for JWT token in response;
- store via /api/v1/store, by sending JWT and URL to store.
The SQLite database now has 2 tables, "users" and "history".
The "users" table is used to store user data:
- username;
- password, secured via bcrypt;
- random user UUID.
The "history" table is used to store browsing history:
- user UUID, to identify the user;
- browsed url.
The secret used to sign JWTs is stored in the config.yml file.
Other new features include SQL-injection protection,
multiple validity/security checks on usernames and passwords, etc.
Signed-off-by: Lorenzo Dellacà <lorenzo.dellaca@mind-overflow.net>
2020-08-22 12:51:33 +02:00
|
|
|
if(confirmedUrl == null) return;
|
|
|
|
|
2020-08-22 18:32:46 +02:00
|
|
|
// Load ZonedDateTime from Json body
|
|
|
|
String zonedDateTimeStr = getFromJson(jsonObject, "timestamp");
|
|
|
|
if(zonedDateTimeStr == null) return;
|
|
|
|
|
|
|
|
// Transform String to ZonedDateTime
|
|
|
|
ZonedDateTime timestamp = stringToZDT(zonedDateTimeStr);
|
|
|
|
if(timestamp == null) return;
|
|
|
|
System.out.println("Zoned Timestamp: " + timestamp.toString());
|
|
|
|
|
|
|
|
// Check if the timestamp is too old to be true
|
|
|
|
ZonedDateTime now = ZonedDateTime.now();
|
|
|
|
System.out.println("NOW Timestamp: " + now.toString());
|
|
|
|
if(!dateCheckPassed(timestamp, now)) return;
|
|
|
|
|
|
|
|
// Transform whatever GMT it's on to GMT+0
|
|
|
|
LocalDateTime timestampUTC = LocalDateTime.ofInstant(timestamp.toInstant(), ZoneOffset.UTC);
|
|
|
|
System.out.println("UTC Timestamp: " + timestampUTC.toString());
|
|
|
|
|
|
|
|
MarkerSQLUtils.addHistoryRecord(uuid, confirmedUrl.toString(), timestampUTC);
|
Implement RESTful API, JWT auth, SQLite storage
This update brings a huge change to the whole system's structure.
A new RESTful API has been implemented, which allows users to register, login
and store data.
The API only supports HTTP POST, and can be accessed via /api/v1/. Requests must
contain a JSON body with the necessary entries, which are:
/api/v1/register AND /api/v1/login:
{
"username": "username",
"password": "password",
"encoding": "plaintext/base64"
}
(Note: passwords can be encoded via "base64" or "plaintext".)
/api/v1/store:
{
"jwt": "encrypted_key_here",
"url": "https://google.com/"
}
The flow is:
- register via /api/v1/register;
- login via /api/v1/login, listen for JWT token in response;
- store via /api/v1/store, by sending JWT and URL to store.
The SQLite database now has 2 tables, "users" and "history".
The "users" table is used to store user data:
- username;
- password, secured via bcrypt;
- random user UUID.
The "history" table is used to store browsing history:
- user UUID, to identify the user;
- browsed url.
The secret used to sign JWTs is stored in the config.yml file.
Other new features include SQL-injection protection,
multiple validity/security checks on usernames and passwords, etc.
Signed-off-by: Lorenzo Dellacà <lorenzo.dellaca@mind-overflow.net>
2020-08-22 12:51:33 +02:00
|
|
|
routeContext.send("OK!");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2020-08-22 18:32:46 +02:00
|
|
|
// todo: move to safety class?
|
|
|
|
private boolean dateCheckPassed(ZonedDateTime timestamp, ZonedDateTime now)
|
|
|
|
{
|
|
|
|
// allow up to 5 minutes in the past and 1 minute in the future
|
|
|
|
//if(ChronoUnit.MINUTES.between(timestamp, now) > 5) todo: decide
|
|
|
|
if(Duration.between(timestamp, now).getSeconds() >= 5 * 60
|
|
|
|
|| Duration.between(timestamp, now).getSeconds() <= -1 * 60)
|
|
|
|
{
|
|
|
|
routeContext.send("Timestamp out of sync!");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
Implement RESTful API, JWT auth, SQLite storage
This update brings a huge change to the whole system's structure.
A new RESTful API has been implemented, which allows users to register, login
and store data.
The API only supports HTTP POST, and can be accessed via /api/v1/. Requests must
contain a JSON body with the necessary entries, which are:
/api/v1/register AND /api/v1/login:
{
"username": "username",
"password": "password",
"encoding": "plaintext/base64"
}
(Note: passwords can be encoded via "base64" or "plaintext".)
/api/v1/store:
{
"jwt": "encrypted_key_here",
"url": "https://google.com/"
}
The flow is:
- register via /api/v1/register;
- login via /api/v1/login, listen for JWT token in response;
- store via /api/v1/store, by sending JWT and URL to store.
The SQLite database now has 2 tables, "users" and "history".
The "users" table is used to store user data:
- username;
- password, secured via bcrypt;
- random user UUID.
The "history" table is used to store browsing history:
- user UUID, to identify the user;
- browsed url.
The secret used to sign JWTs is stored in the config.yml file.
Other new features include SQL-injection protection,
multiple validity/security checks on usernames and passwords, etc.
Signed-off-by: Lorenzo Dellacà <lorenzo.dellaca@mind-overflow.net>
2020-08-22 12:51:33 +02:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
2020-08-22 18:32:46 +02:00
|
|
|
// todo: move to safety class?
|
|
|
|
private boolean usernameCheckPassed(String username)
|
Implement RESTful API, JWT auth, SQLite storage
This update brings a huge change to the whole system's structure.
A new RESTful API has been implemented, which allows users to register, login
and store data.
The API only supports HTTP POST, and can be accessed via /api/v1/. Requests must
contain a JSON body with the necessary entries, which are:
/api/v1/register AND /api/v1/login:
{
"username": "username",
"password": "password",
"encoding": "plaintext/base64"
}
(Note: passwords can be encoded via "base64" or "plaintext".)
/api/v1/store:
{
"jwt": "encrypted_key_here",
"url": "https://google.com/"
}
The flow is:
- register via /api/v1/register;
- login via /api/v1/login, listen for JWT token in response;
- store via /api/v1/store, by sending JWT and URL to store.
The SQLite database now has 2 tables, "users" and "history".
The "users" table is used to store user data:
- username;
- password, secured via bcrypt;
- random user UUID.
The "history" table is used to store browsing history:
- user UUID, to identify the user;
- browsed url.
The secret used to sign JWTs is stored in the config.yml file.
Other new features include SQL-injection protection,
multiple validity/security checks on usernames and passwords, etc.
Signed-off-by: Lorenzo Dellacà <lorenzo.dellaca@mind-overflow.net>
2020-08-22 12:51:33 +02:00
|
|
|
{
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
2020-08-22 18:32:46 +02:00
|
|
|
private URI stringToURI(String url)
|
Implement RESTful API, JWT auth, SQLite storage
This update brings a huge change to the whole system's structure.
A new RESTful API has been implemented, which allows users to register, login
and store data.
The API only supports HTTP POST, and can be accessed via /api/v1/. Requests must
contain a JSON body with the necessary entries, which are:
/api/v1/register AND /api/v1/login:
{
"username": "username",
"password": "password",
"encoding": "plaintext/base64"
}
(Note: passwords can be encoded via "base64" or "plaintext".)
/api/v1/store:
{
"jwt": "encrypted_key_here",
"url": "https://google.com/"
}
The flow is:
- register via /api/v1/register;
- login via /api/v1/login, listen for JWT token in response;
- store via /api/v1/store, by sending JWT and URL to store.
The SQLite database now has 2 tables, "users" and "history".
The "users" table is used to store user data:
- username;
- password, secured via bcrypt;
- random user UUID.
The "history" table is used to store browsing history:
- user UUID, to identify the user;
- browsed url.
The secret used to sign JWTs is stored in the config.yml file.
Other new features include SQL-injection protection,
multiple validity/security checks on usernames and passwords, etc.
Signed-off-by: Lorenzo Dellacà <lorenzo.dellaca@mind-overflow.net>
2020-08-22 12:51:33 +02:00
|
|
|
{
|
|
|
|
try {
|
|
|
|
return new URL(url).toURI();
|
|
|
|
} catch (URISyntaxException e) {
|
|
|
|
routeContext.send("Invalid URI!");
|
|
|
|
}
|
|
|
|
catch (MalformedURLException e) {
|
|
|
|
routeContext.send("Invalid URL!");
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
2020-08-22 18:32:46 +02:00
|
|
|
|
|
|
|
private ZonedDateTime stringToZDT(String timestamp)
|
|
|
|
{
|
|
|
|
DateTimeFormatter formatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME;
|
|
|
|
return ZonedDateTime.parse(timestamp, formatter);
|
|
|
|
// todo: error check
|
|
|
|
}
|
Implement RESTful API, JWT auth, SQLite storage
This update brings a huge change to the whole system's structure.
A new RESTful API has been implemented, which allows users to register, login
and store data.
The API only supports HTTP POST, and can be accessed via /api/v1/. Requests must
contain a JSON body with the necessary entries, which are:
/api/v1/register AND /api/v1/login:
{
"username": "username",
"password": "password",
"encoding": "plaintext/base64"
}
(Note: passwords can be encoded via "base64" or "plaintext".)
/api/v1/store:
{
"jwt": "encrypted_key_here",
"url": "https://google.com/"
}
The flow is:
- register via /api/v1/register;
- login via /api/v1/login, listen for JWT token in response;
- store via /api/v1/store, by sending JWT and URL to store.
The SQLite database now has 2 tables, "users" and "history".
The "users" table is used to store user data:
- username;
- password, secured via bcrypt;
- random user UUID.
The "history" table is used to store browsing history:
- user UUID, to identify the user;
- browsed url.
The secret used to sign JWTs is stored in the config.yml file.
Other new features include SQL-injection protection,
multiple validity/security checks on usernames and passwords, etc.
Signed-off-by: Lorenzo Dellacà <lorenzo.dellaca@mind-overflow.net>
2020-08-22 12:51:33 +02:00
|
|
|
}
|