Add UserHandler with promote/demote capabilities.

Fix bug in ConfigLoad preventing salt and hash fields from being populated when loading users.
Convert User to a class.
Fix UserService.getUser bug where it would compare the input user string instead of the resulting user against null.
This commit is contained in:
天クマ 2026-04-05 15:42:01 -03:00
commit 9f4c55e80d
8 changed files with 95 additions and 4 deletions

View file

@ -20,7 +20,9 @@ The server includes a (working but very WIP) HTTP API for third-party clients an
- [x] Search
- [x] Downloads
- [ ] Resumable downloads
- [ ] User Management
- [x] User Management
- [x] Promote/demote users
- [ ] Create/delete users
### Web interface
- [x] Authentication

View file

@ -1,9 +1,40 @@
package org.adrianvictor.livingroom.auth;
import org.adrianvictor.livingroom.config.AppConfig;
import org.adrianvictor.livingroom.utils.PasswordUtils;
public record User(String username, String hashedPassword, String salt, Role role) {
public class User {
private final String username;
private final String hashedPassword;
private final String salt;
private Role role;
public User(String username, String hashedPassword, String salt, Role role) {
this.username = username;
this.hashedPassword = hashedPassword;
this.salt = salt;
this.role = role;
}
public boolean auth(String password) {
return PasswordUtils.verifyPassword(password, salt, hashedPassword);
}
public AppConfig.UserConfig toConfig() {
AppConfig.UserConfig config = new AppConfig.UserConfig();
config.setRole(role.toString().toLowerCase());
config.setHash(this.hashedPassword);
config.setSalt(this.salt);
return config;
}
public void setRole(Role role) {
this.role = role;
}
public String getName() {
return username;
}
public Role role() { return role; }
}

View file

@ -1,6 +1,7 @@
package org.adrianvictor.livingroom.config;
import org.adrianvictor.livingroom.Logger;
import org.adrianvictor.livingroom.auth.User;
import org.adrianvictor.livingroom.utils.PasswordUtils;
import org.json.simple.JSONObject;

View file

@ -50,6 +50,8 @@ public class ConfigLoader {
AppConfig.UserConfig user = new AppConfig.UserConfig();
user.setRole((String) userJson.get("role"));
user.setPassword((String) userJson.get("password"));
user.setSalt((String) userJson.get("salt"));
user.setHash((String) userJson.get("hash"));
users.put(username, user);
}

View file

@ -1,6 +1,7 @@
package org.adrianvictor.livingroom.http;
import org.adrianvictor.livingroom.http.handlers.*;
import org.adrianvictor.livingroom.http.handlers.UserHandler;
import java.util.HashMap;
@ -16,6 +17,7 @@ public class Handlers {
map.put(new WebRedirectHandler(), "/");
map.put(new LoginHandler(), "/login");
map.put(new SearchHandler(), "/search");
map.put(new UserHandler(), "/user");
}
public static HashMap<Handler, String> getAll() {

View file

@ -73,6 +73,7 @@ public class Server {
if (session == null) {
try {
AuthenticationHelper.sendUnauthorized(exchange, null);
return;
} catch (IOException ignored) {}
}
}
@ -98,7 +99,7 @@ public class Server {
} catch (IOException ignored) {
} catch (Exception e) {
Logger.error("Handler error: " + e.getMessage());
//e.printStackTrace();
e.printStackTrace();
try {
exchange.sendResponseHeaders(500, 0);
exchange.close();

View file

@ -0,0 +1,52 @@
package org.adrianvictor.livingroom.http.handlers;
import com.sun.net.httpserver.HttpExchange;
import org.adrianvictor.livingroom.Main;
import org.adrianvictor.livingroom.auth.Role;
import org.adrianvictor.livingroom.auth.Session;
import org.adrianvictor.livingroom.auth.User;
import org.adrianvictor.livingroom.http.Handler;
import org.adrianvictor.livingroom.http.HttpResponse;
import org.adrianvictor.livingroom.http.utils.AuthenticationHelper;
import org.adrianvictor.livingroom.services.UserService;
public class UserHandler implements Handler {
@Override
public HttpResponse result(String baseAddress, String path, HttpExchange exchange) {
String[] parts = path.split("/");
Session session = AuthenticationHelper.getAuthenticatedSession(exchange);
String username = session.getUsername();
if (parts.length < 2) {
return HttpResponse.json(400, "Usage: '.../user/<promote|demote>/<username>'");
}
if (UserService.getInstance().getUser(username).role() != Role.ADMIN) {
return HttpResponse.json(401, "You are not an administrator.");
}
User user = UserService.getInstance().getUser(parts[1]);
if (user == null) {
return HttpResponse.json(401, "This user does not exist.");
}
try {
switch (parts[0]) {
case "promote":
user.setRole(Role.ADMIN);
break;
case "demote":
user.setRole(Role.USER);
break;
default:
return HttpResponse.json(400, "Usage: '.../user/<promote|demote>/<username>'");
}
Main.getConfig().addUser(user.getName(), user.toConfig());
Main.getConfig().saveConfig();
return HttpResponse.json(200, "Success.");
} catch (Exception e) {
return HttpResponse.json(500, "");
}
}
}

View file

@ -25,7 +25,7 @@ public class UserService {
public User getUser(String user) {
AppConfig.UserConfig raw = users.get(user);
if (user == null) {
if (raw == null) {
return null;
}