mirror of
https://github.com/TeamPiped/Piped-Backend.git
synced 2024-08-14 23:51:41 +00:00
Improve a lot of things.
This commit is contained in:
parent
55eb4f46fa
commit
27e3eee8ca
8 changed files with 78 additions and 169 deletions
|
@ -34,7 +34,8 @@ dependencies {
|
||||||
implementation 'org.postgresql:postgresql:42.2.19'
|
implementation 'org.postgresql:postgresql:42.2.19'
|
||||||
implementation 'org.hibernate:hibernate-core:5.4.30.Final'
|
implementation 'org.hibernate:hibernate-core:5.4.30.Final'
|
||||||
implementation 'org.hibernate:hibernate-hikaricp: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 {
|
shadowJar {
|
||||||
|
|
|
@ -192,7 +192,7 @@ public class ServerLauncher extends MultithreadedHttpServerLauncher {
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return getErrorResponse(e);
|
return getErrorResponse(e);
|
||||||
}
|
}
|
||||||
})).map("/subscribe", AsyncServlet.ofBlocking(executor, request -> {
|
})).map(HttpMethod.POST, "/subscribe", AsyncServlet.ofBlocking(executor, request -> {
|
||||||
try {
|
try {
|
||||||
SubscriptionUpdateRequest body = Constants.mapper.readValue(request.loadBody().getResult().asArray(),
|
SubscriptionUpdateRequest body = Constants.mapper.readValue(request.loadBody().getResult().asArray(),
|
||||||
SubscriptionUpdateRequest.class);
|
SubscriptionUpdateRequest.class);
|
||||||
|
@ -201,7 +201,7 @@ public class ServerLauncher extends MultithreadedHttpServerLauncher {
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return getErrorResponse(e);
|
return getErrorResponse(e);
|
||||||
}
|
}
|
||||||
})).map("/unsubscribe", AsyncServlet.ofBlocking(executor, request -> {
|
})).map(HttpMethod.POST, "/unsubscribe", AsyncServlet.ofBlocking(executor, request -> {
|
||||||
try {
|
try {
|
||||||
SubscriptionUpdateRequest body = Constants.mapper.readValue(request.loadBody().getResult().asArray(),
|
SubscriptionUpdateRequest body = Constants.mapper.readValue(request.loadBody().getResult().asArray(),
|
||||||
SubscriptionUpdateRequest.class);
|
SubscriptionUpdateRequest.class);
|
||||||
|
@ -211,7 +211,7 @@ public class ServerLauncher extends MultithreadedHttpServerLauncher {
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return getErrorResponse(e);
|
return getErrorResponse(e);
|
||||||
}
|
}
|
||||||
}));
|
})).map(HttpMethod.OPTIONS, "/*", request -> HttpResponse.ofCode(200));
|
||||||
|
|
||||||
return new CustomServletDecorator(router);
|
return new CustomServletDecorator(router);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package me.kavin.piped.utils;
|
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 static io.activej.http.HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
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);
|
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, "*");
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import org.hibernate.Session;
|
||||||
import org.hibernate.SessionFactory;
|
import org.hibernate.SessionFactory;
|
||||||
import org.hibernate.cfg.Configuration;
|
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.User;
|
||||||
import me.kavin.piped.utils.obj.db.Video;
|
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.url", "jdbc:postgresql://");
|
||||||
configuration.setProperty("hibernate.connection.driver_class", "org.postgresql.Driver");
|
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.username", "piped");
|
||||||
configuration.setProperty("hibernate.connection.password", "@8LQuf7JUabCker$zQYS");
|
configuration.setProperty("hibernate.connection.password", "@8LQuf7JUabCker$zQYS");
|
||||||
configuration.setProperty("hibernate.temp.use_jdbc_metadata_defaults", "false");
|
configuration.setProperty("hibernate.temp.use_jdbc_metadata_defaults", "false");
|
||||||
configuration.configure();
|
configuration.configure();
|
||||||
|
|
||||||
sessionFactory = configuration.addAnnotatedClass(User.class).addAnnotatedClass(Video.class)
|
sessionFactory = configuration.addAnnotatedClass(User.class).addAnnotatedClass(Video.class)
|
||||||
.buildSessionFactory();
|
.addAnnotatedClass(Channel.class).buildSessionFactory();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final Session createSession() {
|
public static final Session createSession() {
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -40,6 +40,7 @@ import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem;
|
||||||
import org.schabi.newpipe.extractor.search.SearchInfo;
|
import org.schabi.newpipe.extractor.search.SearchInfo;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||||
|
import org.springframework.security.crypto.argon2.Argon2PasswordEncoder;
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
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)
|
public static final byte[] registerResponse(String user, String pass)
|
||||||
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
|
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
|
||||||
|
|
||||||
|
@ -492,7 +495,7 @@ public class ResponseHelper {
|
||||||
return Constants.mapper.writeValueAsBytes(new AlreadyRegisteredResponse());
|
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.save(newuser);
|
||||||
s.beginTransaction().commit();
|
s.beginTransaction().commit();
|
||||||
|
@ -516,16 +519,11 @@ public class ResponseHelper {
|
||||||
|
|
||||||
User dbuser = s.createQuery(cr).uniqueResult();
|
User dbuser = s.createQuery(cr).uniqueResult();
|
||||||
|
|
||||||
if (dbuser != null && PasswordHash.validatePassword(pass, dbuser.getPassword())) {
|
if (dbuser != null && argon2PasswordEncoder.matches(pass, dbuser.getPassword())) {
|
||||||
s.close();
|
s.close();
|
||||||
return Constants.mapper.writeValueAsBytes(new LoginResponse(dbuser.getSessionId()));
|
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();
|
s.close();
|
||||||
|
|
||||||
return Constants.mapper.writeValueAsBytes(new IncorrectCredentialsResponse());
|
return Constants.mapper.writeValueAsBytes(new IncorrectCredentialsResponse());
|
||||||
|
|
|
@ -3,13 +3,16 @@ package me.kavin.piped.utils.obj.db;
|
||||||
import javax.persistence.Column;
|
import javax.persistence.Column;
|
||||||
import javax.persistence.Entity;
|
import javax.persistence.Entity;
|
||||||
import javax.persistence.Id;
|
import javax.persistence.Id;
|
||||||
|
import javax.persistence.Index;
|
||||||
|
import javax.persistence.Table;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
|
@Table(name = "channels", indexes = { @Index(columnList = "uploader_id", name = "uploader_id_idx") })
|
||||||
public class Channel {
|
public class Channel {
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
@Column(name = "uploader_id", length = 30)
|
@Column(name = "uploader_id", length = 30)
|
||||||
private String uploaderId;
|
private String uploader_id;
|
||||||
|
|
||||||
@Column(name = "uploader", length = 80)
|
@Column(name = "uploader", length = 80)
|
||||||
private String uploader;
|
private String uploader;
|
||||||
|
@ -20,4 +23,45 @@ public class Channel {
|
||||||
@Column(name = "verified")
|
@Column(name = "verified")
|
||||||
private boolean 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,11 @@ package me.kavin.piped.utils.obj.db;
|
||||||
|
|
||||||
import javax.persistence.Column;
|
import javax.persistence.Column;
|
||||||
import javax.persistence.Entity;
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.FetchType;
|
||||||
import javax.persistence.Id;
|
import javax.persistence.Id;
|
||||||
import javax.persistence.Index;
|
import javax.persistence.Index;
|
||||||
|
import javax.persistence.JoinColumn;
|
||||||
|
import javax.persistence.ManyToOne;
|
||||||
import javax.persistence.Table;
|
import javax.persistence.Table;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
|
@ -29,17 +32,21 @@ public class Video {
|
||||||
@Column(name = "thumbnail", length = 150)
|
@Column(name = "thumbnail", length = 150)
|
||||||
private String thumbnail;
|
private String thumbnail;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "uploader_id")
|
||||||
|
private Channel channel;
|
||||||
|
|
||||||
public Video() {
|
public Video() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Video(String id, String title, long views, int duration, long uploaded, String uploader, String uploaderUrl,
|
public Video(String id, String title, long views, int duration, long uploaded, String thumbnail, Channel channel) {
|
||||||
String uploaderAvatar, boolean verified, String thumbnail) {
|
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.title = title;
|
this.title = title;
|
||||||
this.views = views;
|
this.views = views;
|
||||||
this.duration = duration;
|
this.duration = duration;
|
||||||
this.uploaded = uploaded;
|
this.uploaded = uploaded;
|
||||||
this.thumbnail = thumbnail;
|
this.thumbnail = thumbnail;
|
||||||
|
this.channel = channel;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getId() {
|
public String getId() {
|
||||||
|
@ -89,4 +96,12 @@ public class Video {
|
||||||
public void setThumbnail(String thumbnail) {
|
public void setThumbnail(String thumbnail) {
|
||||||
this.thumbnail = thumbnail;
|
this.thumbnail = thumbnail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Channel getChannel() {
|
||||||
|
return channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setChannel(Channel channel) {
|
||||||
|
this.channel = channel;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue