webmarker-server/src/main/java/net/mindoverflow/webmarker/webserver/controllers/StorageController.java

206 lines
6.2 KiB
Java

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.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
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(!usernameCheckPassed(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 = stringToURI(url);
if(confirmedUrl == null) return;
// 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);
routeContext.send("OK!");
}
// 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;
}
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();
}
// todo: move to safety class?
private boolean usernameCheckPassed(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 stringToURI(String url)
{
try {
return new URL(url).toURI();
} catch (URISyntaxException e) {
routeContext.send("Invalid URI!");
}
catch (MalformedURLException e) {
routeContext.send("Invalid URL!");
}
return null;
}
private ZonedDateTime stringToZDT(String timestamp)
{
DateTimeFormatter formatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME;
return ZonedDateTime.parse(timestamp, formatter);
// todo: error check
}
}