From 27e3eee8ca60a94cf54dfd36d1e2053e3eb45b21 Mon Sep 17 00:00:00 2001 From: FireMasterK <20838718+FireMasterK@users.noreply.github.com> Date: Mon, 12 Jul 2021 03:19:22 +0530 Subject: [PATCH] Improve a lot of things. --- build.gradle | 3 +- .../java/me/kavin/piped/ServerLauncher.java | 6 +- .../piped/utils/CustomServletDecorator.java | 4 +- .../piped/utils/DatabaseSessionFactory.java | 5 +- .../me/kavin/piped/utils/PasswordHash.java | 152 ------------------ .../me/kavin/piped/utils/ResponseHelper.java | 12 +- .../me/kavin/piped/utils/obj/db/Channel.java | 46 +++++- .../me/kavin/piped/utils/obj/db/Video.java | 19 ++- 8 files changed, 78 insertions(+), 169 deletions(-) delete mode 100644 src/main/java/me/kavin/piped/utils/PasswordHash.java diff --git a/build.gradle b/build.gradle index d9240c0..cbcbe59 100644 --- a/build.gradle +++ b/build.gradle @@ -34,7 +34,8 @@ dependencies { implementation 'org.postgresql:postgresql:42.2.19' implementation 'org.hibernate:hibernate-core:5.4.30.Final' implementation 'org.hibernate:hibernate-hikaricp:5.4.30.Final' - implementation 'net.java.dev.jna:jna-platform:5.8.0' + implementation 'org.springframework.security:spring-security-crypto:5.5.1' + implementation 'commons-logging:commons-logging:1.2' } shadowJar { diff --git a/src/main/java/me/kavin/piped/ServerLauncher.java b/src/main/java/me/kavin/piped/ServerLauncher.java index c577886..cce57ca 100644 --- a/src/main/java/me/kavin/piped/ServerLauncher.java +++ b/src/main/java/me/kavin/piped/ServerLauncher.java @@ -192,7 +192,7 @@ public class ServerLauncher extends MultithreadedHttpServerLauncher { } catch (Exception e) { return getErrorResponse(e); } - })).map("/subscribe", AsyncServlet.ofBlocking(executor, request -> { + })).map(HttpMethod.POST, "/subscribe", AsyncServlet.ofBlocking(executor, request -> { try { SubscriptionUpdateRequest body = Constants.mapper.readValue(request.loadBody().getResult().asArray(), SubscriptionUpdateRequest.class); @@ -201,7 +201,7 @@ public class ServerLauncher extends MultithreadedHttpServerLauncher { } catch (Exception e) { return getErrorResponse(e); } - })).map("/unsubscribe", AsyncServlet.ofBlocking(executor, request -> { + })).map(HttpMethod.POST, "/unsubscribe", AsyncServlet.ofBlocking(executor, request -> { try { SubscriptionUpdateRequest body = Constants.mapper.readValue(request.loadBody().getResult().asArray(), SubscriptionUpdateRequest.class); @@ -211,7 +211,7 @@ public class ServerLauncher extends MultithreadedHttpServerLauncher { } catch (Exception e) { return getErrorResponse(e); } - })); + })).map(HttpMethod.OPTIONS, "/*", request -> HttpResponse.ofCode(200)); return new CustomServletDecorator(router); } diff --git a/src/main/java/me/kavin/piped/utils/CustomServletDecorator.java b/src/main/java/me/kavin/piped/utils/CustomServletDecorator.java index 459399c..d0ec91f 100644 --- a/src/main/java/me/kavin/piped/utils/CustomServletDecorator.java +++ b/src/main/java/me/kavin/piped/utils/CustomServletDecorator.java @@ -1,5 +1,6 @@ package me.kavin.piped.utils; +import static io.activej.http.HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS; import static io.activej.http.HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN; import org.jetbrains.annotations.NotNull; @@ -29,7 +30,8 @@ public class CustomServletDecorator implements AsyncServlet { HttpHeaderValue headerValue = HttpHeaderValue.of("app;dur=" + (System.nanoTime() - before) / 1000000.0); - return response.withHeader(HEADER, headerValue).withHeader(ACCESS_CONTROL_ALLOW_ORIGIN, "*"); + return response.withHeader(HEADER, headerValue).withHeader(ACCESS_CONTROL_ALLOW_ORIGIN, "*") + .withHeader(ACCESS_CONTROL_ALLOW_HEADERS, "*"); }); } diff --git a/src/main/java/me/kavin/piped/utils/DatabaseSessionFactory.java b/src/main/java/me/kavin/piped/utils/DatabaseSessionFactory.java index 72a86cf..1976374 100644 --- a/src/main/java/me/kavin/piped/utils/DatabaseSessionFactory.java +++ b/src/main/java/me/kavin/piped/utils/DatabaseSessionFactory.java @@ -4,6 +4,7 @@ import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration; +import me.kavin.piped.utils.obj.db.Channel; import me.kavin.piped.utils.obj.db.User; import me.kavin.piped.utils.obj.db.Video; @@ -17,14 +18,14 @@ public class DatabaseSessionFactory { configuration.setProperty("hibernate.connection.url", "jdbc:postgresql://"); configuration.setProperty("hibernate.connection.driver_class", "org.postgresql.Driver"); - configuration.setProperty("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect"); + configuration.setProperty("hibernate.dialect", "org.hibernate.dialect.CockroachDB192Dialect"); configuration.setProperty("hibernate.connection.username", "piped"); configuration.setProperty("hibernate.connection.password", "@8LQuf7JUabCker$zQYS"); configuration.setProperty("hibernate.temp.use_jdbc_metadata_defaults", "false"); configuration.configure(); sessionFactory = configuration.addAnnotatedClass(User.class).addAnnotatedClass(Video.class) - .buildSessionFactory(); + .addAnnotatedClass(Channel.class).buildSessionFactory(); } public static final Session createSession() { diff --git a/src/main/java/me/kavin/piped/utils/PasswordHash.java b/src/main/java/me/kavin/piped/utils/PasswordHash.java deleted file mode 100644 index ebe11d9..0000000 --- a/src/main/java/me/kavin/piped/utils/PasswordHash.java +++ /dev/null @@ -1,152 +0,0 @@ -package me.kavin.piped.utils; - -import java.math.BigInteger; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.security.spec.InvalidKeySpecException; - -import javax.crypto.SecretKeyFactory; -import javax.crypto.spec.PBEKeySpec; - -/* - * PBKDF2 salted password hashing. - * Author: havoc AT defuse.ca - * www: http://crackstation.net/hashing-security.htm - * source: https://gist.github.com/jtan189/3804290 - */ -public class PasswordHash { - public static final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA512"; - - // The following constants may be changed without breaking existing hashes. - public static final int SALT_BYTES = 64; - public static final int HASH_BYTES = 64; - public static final int PBKDF2_ITERATIONS = 1000; - - public static final int ITERATION_INDEX = 0; - public static final int SALT_INDEX = 1; - public static final int PBKDF2_INDEX = 2; - - /** - * Returns a salted PBKDF2 hash of the password. - * - * @param password the password to hash - * @return a salted PBKDF2 hash of the password - */ - public static String createHash(String password) throws NoSuchAlgorithmException, InvalidKeySpecException { - return createHash(password.toCharArray()); - } - - /** - * Returns a salted PBKDF2 hash of the password. - * - * @param password the password to hash - * @return a salted PBKDF2 hash of the password - */ - public static String createHash(char[] password) throws NoSuchAlgorithmException, InvalidKeySpecException { - // Generate a random salt - SecureRandom random = new SecureRandom(); - byte[] salt = new byte[SALT_BYTES]; - random.nextBytes(salt); - - // Hash the password - byte[] hash = pbkdf2(password, salt, PBKDF2_ITERATIONS, HASH_BYTES); - // format iterations:salt:hash - return PBKDF2_ITERATIONS + ":" + toHex(salt) + ":" + toHex(hash); - } - - /** - * Validates a password using a hash. - * - * @param password the password to check - * @param goodHash the hash of the valid password - * @return true if the password is correct, false if not - */ - public static boolean validatePassword(String password, String goodHash) - throws NoSuchAlgorithmException, InvalidKeySpecException { - return validatePassword(password.toCharArray(), goodHash); - } - - /** - * Validates a password using a hash. - * - * @param password the password to check - * @param goodHash the hash of the valid password - * @return true if the password is correct, false if not - */ - public static boolean validatePassword(char[] password, String goodHash) - throws NoSuchAlgorithmException, InvalidKeySpecException { - // Decode the hash into its parameters - String[] params = goodHash.split(":"); - int iterations = Integer.parseInt(params[ITERATION_INDEX]); - byte[] salt = fromHex(params[SALT_INDEX]); - byte[] hash = fromHex(params[PBKDF2_INDEX]); - // Compute the hash of the provided password, using the same salt, - // iteration count, and hash length - byte[] testHash = pbkdf2(password, salt, iterations, hash.length); - // Compare the hashes in constant time. The password is correct if - // both hashes match. - return slowEquals(hash, testHash); - } - - /** - * Compares two byte arrays in length-constant time. This comparison method is - * used so that password hashes cannot be extracted from an on-line system using - * a timing attack and then attacked off-line. - * - * @param a the first byte array - * @param b the second byte array - * @return true if both byte arrays are the same, false if not - */ - private static boolean slowEquals(byte[] a, byte[] b) { - int diff = a.length ^ b.length; - for (int i = 0; i < a.length && i < b.length; i++) - diff |= a[i] ^ b[i]; - return diff == 0; - } - - /** - * Computes the PBKDF2 hash of a password. - * - * @param password the password to hash. - * @param salt the salt - * @param iterations the iteration count (slowness factor) - * @param bytes the length of the hash to compute in bytes - * @return the PBDKF2 hash of the password - */ - private static byte[] pbkdf2(char[] password, byte[] salt, int iterations, int bytes) - throws NoSuchAlgorithmException, InvalidKeySpecException { - PBEKeySpec spec = new PBEKeySpec(password, salt, iterations, bytes * 8); - SecretKeyFactory skf = SecretKeyFactory.getInstance(PBKDF2_ALGORITHM); - return skf.generateSecret(spec).getEncoded(); - } - - /** - * Converts a string of hexadecimal characters into a byte array. - * - * @param hex the hex string - * @return the hex string decoded into a byte array - */ - private static byte[] fromHex(String hex) { - byte[] binary = new byte[hex.length() / 2]; - for (int i = 0; i < binary.length; i++) { - binary[i] = (byte) Integer.parseInt(hex.substring(2 * i, 2 * i + 2), 16); - } - return binary; - } - - /** - * Converts a byte array into a hexadecimal string. - * - * @param array the byte array to convert - * @return a length*2 character string encoding the byte array - */ - private static String toHex(byte[] array) { - BigInteger bi = new BigInteger(1, array); - String hex = bi.toString(16); - int paddingLength = (array.length * 2) - hex.length(); - if (paddingLength > 0) - return String.format("%0" + paddingLength + "d", 0) + hex; - else - return hex; - } -} \ No newline at end of file diff --git a/src/main/java/me/kavin/piped/utils/ResponseHelper.java b/src/main/java/me/kavin/piped/utils/ResponseHelper.java index 06bbe87..21f69d9 100644 --- a/src/main/java/me/kavin/piped/utils/ResponseHelper.java +++ b/src/main/java/me/kavin/piped/utils/ResponseHelper.java @@ -40,6 +40,7 @@ import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem; import org.schabi.newpipe.extractor.search.SearchInfo; import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamInfoItem; +import org.springframework.security.crypto.argon2.Argon2PasswordEncoder; import com.fasterxml.jackson.core.JsonProcessingException; import com.github.benmanes.caffeine.cache.Caffeine; @@ -475,6 +476,8 @@ public class ResponseHelper { } + private static final Argon2PasswordEncoder argon2PasswordEncoder = new Argon2PasswordEncoder(); + public static final byte[] registerResponse(String user, String pass) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException { @@ -492,7 +495,7 @@ public class ResponseHelper { return Constants.mapper.writeValueAsBytes(new AlreadyRegisteredResponse()); } - User newuser = new User(user, PasswordHash.createHash(pass), Collections.emptyList()); + User newuser = new User(user, argon2PasswordEncoder.encode(pass), Collections.emptyList()); s.save(newuser); s.beginTransaction().commit(); @@ -516,16 +519,11 @@ public class ResponseHelper { User dbuser = s.createQuery(cr).uniqueResult(); - if (dbuser != null && PasswordHash.validatePassword(pass, dbuser.getPassword())) { + if (dbuser != null && argon2PasswordEncoder.matches(pass, dbuser.getPassword())) { s.close(); return Constants.mapper.writeValueAsBytes(new LoginResponse(dbuser.getSessionId())); } - User newuser = new User(user, PasswordHash.createHash(pass), Collections.emptyList()); - - s.save(newuser); - s.beginTransaction().commit(); - s.close(); return Constants.mapper.writeValueAsBytes(new IncorrectCredentialsResponse()); diff --git a/src/main/java/me/kavin/piped/utils/obj/db/Channel.java b/src/main/java/me/kavin/piped/utils/obj/db/Channel.java index e241a85..db94722 100644 --- a/src/main/java/me/kavin/piped/utils/obj/db/Channel.java +++ b/src/main/java/me/kavin/piped/utils/obj/db/Channel.java @@ -3,13 +3,16 @@ package me.kavin.piped.utils.obj.db; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; +import javax.persistence.Index; +import javax.persistence.Table; @Entity +@Table(name = "channels", indexes = { @Index(columnList = "uploader_id", name = "uploader_id_idx") }) public class Channel { @Id @Column(name = "uploader_id", length = 30) - private String uploaderId; + private String uploader_id; @Column(name = "uploader", length = 80) private String uploader; @@ -20,4 +23,45 @@ public class Channel { @Column(name = "verified") private boolean verified; + public Channel() { + } + + public Channel(String uploader_id, String uploader, String uploaderAvatar, boolean verified) { + this.uploader_id = uploader_id; + this.uploader = uploader; + this.uploaderAvatar = uploaderAvatar; + this.verified = verified; + } + + public String getUploaderId() { + return uploader_id; + } + + public void setUploaderId(String uploader_id) { + this.uploader_id = uploader_id; + } + + public String getUploader() { + return uploader; + } + + public void setUploader(String uploader) { + this.uploader = uploader; + } + + public String getUploaderAvatar() { + return uploaderAvatar; + } + + public void setUploaderAvatar(String uploaderAvatar) { + this.uploaderAvatar = uploaderAvatar; + } + + public boolean isVerified() { + return verified; + } + + public void setVerified(boolean verified) { + this.verified = verified; + } } diff --git a/src/main/java/me/kavin/piped/utils/obj/db/Video.java b/src/main/java/me/kavin/piped/utils/obj/db/Video.java index 7acb979..d74a5fd 100644 --- a/src/main/java/me/kavin/piped/utils/obj/db/Video.java +++ b/src/main/java/me/kavin/piped/utils/obj/db/Video.java @@ -2,8 +2,11 @@ package me.kavin.piped.utils.obj.db; import javax.persistence.Column; import javax.persistence.Entity; +import javax.persistence.FetchType; import javax.persistence.Id; import javax.persistence.Index; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; import javax.persistence.Table; @Entity @@ -29,17 +32,21 @@ public class Video { @Column(name = "thumbnail", length = 150) private String thumbnail; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "uploader_id") + private Channel channel; + public Video() { } - public Video(String id, String title, long views, int duration, long uploaded, String uploader, String uploaderUrl, - String uploaderAvatar, boolean verified, String thumbnail) { + public Video(String id, String title, long views, int duration, long uploaded, String thumbnail, Channel channel) { this.id = id; this.title = title; this.views = views; this.duration = duration; this.uploaded = uploaded; this.thumbnail = thumbnail; + this.channel = channel; } public String getId() { @@ -89,4 +96,12 @@ public class Video { public void setThumbnail(String thumbnail) { this.thumbnail = thumbnail; } + + public Channel getChannel() { + return channel; + } + + public void setChannel(Channel channel) { + this.channel = channel; + } }