Skip to content

Commit

Permalink
Implement some login features.
Browse files Browse the repository at this point in the history
  • Loading branch information
FireMasterK committed Dec 3, 2021
1 parent dc009f3 commit 3b4d02a
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 8 deletions.
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ dependencies {
implementation 'org.hibernate:hibernate-hikaricp:5.6.1.Final'
implementation 'com.zaxxer:HikariCP:5.0.0'
implementation 'org.springframework.security:spring-security-crypto:5.6.0'
implementation 'dev.samstevens.totp:totp:1.7.1'
implementation 'commons-logging:commons-logging:1.2'
}

Expand Down
3 changes: 2 additions & 1 deletion src/main/java/me/kavin/piped/ServerLauncher.java
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,8 @@ AsyncServlet mainServlet(Executor executor) {
try {
LoginRequest body = Constants.mapper.readValue(request.loadBody().getResult().asArray(),
LoginRequest.class);
return getJsonResponse(ResponseHelper.loginResponse(body.username, body.password), "private");
return getJsonResponse(ResponseHelper.loginResponse(body.username, body.password, body.totp),
"private");
} catch (Exception e) {
return getErrorResponse(e, request.getPath());
}
Expand Down
105 changes: 100 additions & 5 deletions src/main/java/me/kavin/piped/utils/ResponseHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
Expand Down Expand Up @@ -69,6 +70,12 @@
import com.rometools.rome.io.FeedException;
import com.rometools.rome.io.SyndFeedOutput;

import dev.samstevens.totp.code.CodeGenerator;
import dev.samstevens.totp.code.CodeVerifier;
import dev.samstevens.totp.code.DefaultCodeGenerator;
import dev.samstevens.totp.code.DefaultCodeVerifier;
import dev.samstevens.totp.time.SystemTimeProvider;
import dev.samstevens.totp.time.TimeProvider;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import me.kavin.piped.consts.Constants;
Expand Down Expand Up @@ -97,6 +104,7 @@
import me.kavin.piped.utils.resp.CompromisedPasswordResponse;
import me.kavin.piped.utils.resp.DisabledRegistrationResponse;
import me.kavin.piped.utils.resp.IncorrectCredentialsResponse;
import me.kavin.piped.utils.resp.InvalidOldPasswordResponse;
import me.kavin.piped.utils.resp.InvalidRequestResponse;
import me.kavin.piped.utils.resp.LoginResponse;
import me.kavin.piped.utils.resp.SubscribeStatusResponse;
Expand Down Expand Up @@ -597,7 +605,11 @@ public static final byte[] registerResponse(String user, String pass) throws IOE

private static final BCryptPasswordEncoder bcryptPasswordEncoder = new BCryptPasswordEncoder();

public static final byte[] loginResponse(String user, String pass)
private static final TimeProvider timeProvider = new SystemTimeProvider();
private static final CodeGenerator codeGenerator = new DefaultCodeGenerator();
private static final CodeVerifier verifier = new DefaultCodeVerifier(codeGenerator, timeProvider);

public static final byte[] loginResponse(String user, String pass, String totp)
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {

if (user == null || pass == null)
Expand All @@ -616,14 +628,20 @@ public static final byte[] loginResponse(String user, String pass)
if (dbuser != null) {
String hash = dbuser.getPassword();
if (hash.startsWith("$argon2")) {
if (argon2PasswordEncoder.matches(pass, hash)) {
if (!argon2PasswordEncoder.matches(pass, hash)) {
s.close();
return Constants.mapper.writeValueAsBytes(new LoginResponse(dbuser.getSessionId()));
return Constants.mapper.writeValueAsBytes(new IncorrectCredentialsResponse());
}
} else if (bcryptPasswordEncoder.matches(pass, hash)) {
} else if (!bcryptPasswordEncoder.matches(pass, hash)) {
s.close();
return Constants.mapper.writeValueAsBytes(new LoginResponse(dbuser.getSessionId()));
return Constants.mapper.writeValueAsBytes(new IncorrectCredentialsResponse());
}

String totpSecret = dbuser.getTotp();
if (totpSecret != null && !verifier.isValidCode(totpSecret, totp))
return Constants.mapper.writeValueAsBytes(new IncorrectCredentialsResponse());

return Constants.mapper.writeValueAsBytes(new LoginResponse(dbuser.getSessionId()));
}

s.close();
Expand All @@ -632,6 +650,83 @@ public static final byte[] loginResponse(String user, String pass)

}

public static final byte[] changePasswordResponse(String session, String oldpass, String newpass)
throws IOException, InterruptedException, URISyntaxException {

if (oldpass == null || newpass == null)
return Constants.mapper.writeValueAsBytes(new InvalidRequestResponse());

Session s = DatabaseSessionFactory.createSession();

User user = DatabaseHelper.getUserFromSession(s, session);

if (user != null) {
String hash = user.getPassword();
if (hash.startsWith("$argon2")) {
if (!argon2PasswordEncoder.matches(oldpass, hash)) {
s.close();
return Constants.mapper.writeValueAsBytes(new InvalidOldPasswordResponse());
}
} else if (!bcryptPasswordEncoder.matches(oldpass, hash)) {
s.close();
return Constants.mapper.writeValueAsBytes(new InvalidOldPasswordResponse());
}

if (Constants.COMPROMISED_PASSWORD_CHECK) {
String sha1Hash = DigestUtils.sha1Hex(newpass).toUpperCase();
String prefix = sha1Hash.substring(0, 5);
String suffix = sha1Hash.substring(5);
String[] entries = RequestUtils
.sendGet("https://api.pwnedpasswords.com/range/" + prefix, "github.com/TeamPiped/Piped-Backend")
.split("\n");
for (String entry : entries)
if (StringUtils.substringBefore(entry, ":").equals(suffix))
return Constants.mapper.writeValueAsBytes(new CompromisedPasswordResponse());
}

user.setPassword(argon2PasswordEncoder.encode(newpass));
s.saveOrUpdate(user);
s.getTransaction().begin();
s.getTransaction().commit();
}

s.close();

return Constants.mapper.writeValueAsBytes(new AuthenticationFailureResponse());
}

public static final byte[] authValidResponse(String session) throws JsonProcessingException {
Session s = DatabaseSessionFactory.createSession();

if (((Long) s.createQuery("SELECT COUNT(user) from User user where user.sessionId = :sessionId")
.setParameter("sessionId", session).uniqueResult()).intValue() > 0) {
s.close();
return Constants.mapper.writeValueAsBytes(new AcceptedResponse());
}

s.close();

return Constants.mapper.writeValueAsBytes(new AuthenticationFailureResponse());
}

public static final byte[] logoutResponse(String session) throws JsonProcessingException {
Session s = DatabaseSessionFactory.createSession();

s.getTransaction().begin();

if (s.createQuery("UPDATE User user SET user.sessionId = :newSessionId where user.sessionId = :sessionId")
.setParameter("sessionId", session).setParameter("newSessionId", String.valueOf(UUID.randomUUID()))
.executeUpdate() > 0) {
s.getTransaction().commit();
s.close();
return Constants.mapper.writeValueAsBytes(new AcceptedResponse());
}

s.close();

return Constants.mapper.writeValueAsBytes(new AuthenticationFailureResponse());
}

public static final byte[] subscribeResponse(String session, String channelId)
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {

Expand Down
11 changes: 11 additions & 0 deletions src/main/java/me/kavin/piped/utils/obj/db/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ public class User implements Serializable {
@Column(name = "session_id", length = 36)
private String sessionId;

@Column(name = "totp_token", length = 32, insertable = false)
private String totp;

@ElementCollection
@CollectionTable(name = "users_subscribed", joinColumns = @JoinColumn(name = "subscriber"), indexes = {
@Index(columnList = "subscriber", name = "users_subscribed_subscriber_idx"),
Expand Down Expand Up @@ -86,6 +89,14 @@ public void setPassword(String password) {
this.password = password;
}

public String getTotp() {
return totp;
}

public void setTotp(String totp) {
this.totp = totp;
}

public List<String> getSubscribed() {
return subscribed_ids;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@

public class IncorrectCredentialsResponse {

public String error = "The username or password you have entered is incorrect.";
public String error = "Invalid credentials. Re-check your username, password and totp.";

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package me.kavin.piped.utils.resp;

public class InvalidOldPasswordResponse {

public String error = "The old password you provided is incorrect.";

}
2 changes: 1 addition & 1 deletion src/main/java/me/kavin/piped/utils/resp/LoginRequest.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@

public class LoginRequest {

public String username, password;
public String username, password, totp;

}

0 comments on commit 3b4d02a

Please sign in to comment.