add user privileges & add translations
This commit is contained in:
@@ -18,6 +18,7 @@ import io.vertx.ext.web.sstore.SessionStore;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import su.xserver.iikocon.config.AppConfig;
|
||||
import su.xserver.iikocon.handler.AdminHandler;
|
||||
import su.xserver.iikocon.handler.AuthHandler;
|
||||
import su.xserver.iikocon.handler.SecurityHandler;
|
||||
import su.xserver.iikocon.handler.SetupHandler;
|
||||
@@ -194,7 +195,50 @@ public class MainVerticle extends AbstractVerticle {
|
||||
.onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
|
||||
}));
|
||||
|
||||
// В initRouter после настройки authHandler, до объявления /api/admin/*:
|
||||
router.route("/api/admin/profile").handler(authHandler::requireAuth);
|
||||
router.get("/api/admin/profile").handler(rc -> {
|
||||
Integer userId = rc.session().get("userId");
|
||||
userService.getProfile(userId)
|
||||
.onSuccess(profile -> rc.response().putHeader("Content-Type", "application/json").end(profile.encode()))
|
||||
.onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
|
||||
});
|
||||
router.put("/api/admin/profile").handler(rc -> {
|
||||
Integer userId = rc.session().get("userId");
|
||||
JsonObject body = rc.body().asJsonObject();
|
||||
String email = body.getString("email");
|
||||
String password = body.getString("password");
|
||||
String language = body.getString("language");
|
||||
userService.updateProfile(userId, email, password, language)
|
||||
.onSuccess(v -> {
|
||||
if (language != null) rc.session().put("language", language);
|
||||
rc.response().end(new JsonObject().put("success", true).encode());
|
||||
})
|
||||
.onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
|
||||
});
|
||||
router.put("/api/admin/language").handler(rc -> {
|
||||
Integer userId = rc.session().get("userId");
|
||||
JsonObject body = rc.body().asJsonObject();
|
||||
String language = body.getString("language");
|
||||
if (language == null || (!"en".equals(language) && !"ru".equals(language))) {
|
||||
rc.response().setStatusCode(400).end("Invalid language");
|
||||
return;
|
||||
}
|
||||
userService.updateLanguage(userId, language)
|
||||
.onSuccess(v -> {
|
||||
rc.session().put("language", language);
|
||||
rc.response().end(new JsonObject().put("success", true).encode());
|
||||
})
|
||||
.onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
|
||||
});
|
||||
|
||||
// Затем существующий блок router.route("/api/admin/*").handler(authHandler::requireAuth);
|
||||
router.route("/api/admin/*").handler(authHandler::requireAuth);
|
||||
// Добавить проверку роли для чувствительных эндпоинтов:
|
||||
// router.route("/api/admin/users*").handler(AdminHandler::requireAdmin);
|
||||
// router.route("/api/admin/restaurants*").handler(AdminHandler::requireAdmin);
|
||||
// router.route("/api/admin/settings*").handler(AdminHandler::requireAdmin);
|
||||
// router.route("/api/admin/active-sessions").handler(AdminHandler::requireAdmin);
|
||||
|
||||
router.get("/api/admin/users").handler(rc -> userService.getAllUsers().onComplete(ar -> {
|
||||
if (ar.succeeded()) {
|
||||
@@ -211,13 +255,14 @@ public class MainVerticle extends AbstractVerticle {
|
||||
String login = body.getString("login");
|
||||
String email = body.getString("email");
|
||||
String password = body.getString("password");
|
||||
String role = body.getString("role");
|
||||
String ip = rc.request().remoteAddress().host();
|
||||
if (login == null || email == null || password == null) {
|
||||
rc.response().setStatusCode(400).end("Missing login, email or password");
|
||||
return;
|
||||
}
|
||||
// Создаём активного пользователя (active = true)
|
||||
userService.createUser(login, email, password, ip, true)
|
||||
if (role == null || role.isEmpty()) role = "user";
|
||||
userService.createUser(login, email, password, ip, true, role)
|
||||
.onSuccess(v -> rc.response().setStatusCode(201).end())
|
||||
.onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
|
||||
});
|
||||
@@ -228,12 +273,13 @@ public class MainVerticle extends AbstractVerticle {
|
||||
String login = body.getString("login");
|
||||
String email = body.getString("email");
|
||||
String password = body.getString("password");
|
||||
String role = body.getString("role");
|
||||
String ip = rc.request().remoteAddress().host();
|
||||
if (login == null || email == null) {
|
||||
rc.response().setStatusCode(400).end("Missing login or email");
|
||||
return;
|
||||
}
|
||||
userService.updateUser(id, login, email, password, ip)
|
||||
userService.updateUser(id, login, email, password, ip, role)
|
||||
.onSuccess(v -> rc.response().end())
|
||||
.onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
|
||||
});
|
||||
|
||||
15
src/main/java/su/xserver/iikocon/handler/AdminHandler.java
Normal file
15
src/main/java/su/xserver/iikocon/handler/AdminHandler.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package su.xserver.iikocon.handler;
|
||||
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import io.vertx.ext.web.RoutingContext;
|
||||
|
||||
public class AdminHandler {
|
||||
public static void requireAdmin(RoutingContext ctx) {
|
||||
String role = ctx.session().get("role");
|
||||
if (!"admin".equals(role)) {
|
||||
ctx.response().setStatusCode(403).end(new JsonObject().put("error", "Admin access required").encode());
|
||||
return;
|
||||
}
|
||||
ctx.next();
|
||||
}
|
||||
}
|
||||
@@ -53,6 +53,8 @@ public class AuthHandler {
|
||||
Session session = ctx.session();
|
||||
session.put("userId", user.getInteger("id"));
|
||||
session.put("login", user.getString("login"));
|
||||
session.put("role", user.getString("role"));
|
||||
session.put("language", user.getString("language"));
|
||||
ctx.response().end(new JsonObject().put("success", true).put("login", user.getString("login")).encode());
|
||||
} else {
|
||||
ctx.response().setStatusCode(401).end("Invalid credentials");
|
||||
|
||||
@@ -56,7 +56,7 @@ public class SetupHandler {
|
||||
if (clientIp == null) {
|
||||
clientIp = ctx.request().remoteAddress().host();
|
||||
}
|
||||
userService.createUser(login, email, password, clientIp, true).onComplete(cr -> {
|
||||
userService.createUser(login, email, password, clientIp, true, "admin").onComplete(cr -> {
|
||||
if (cr.succeeded()) {
|
||||
ctx.response().setStatusCode(201)
|
||||
.end(new JsonObject().put("success", true).encode());
|
||||
|
||||
@@ -28,6 +28,8 @@ public class UserService {
|
||||
email VARCHAR(255) UNIQUE NOT NULL,
|
||||
password VARCHAR(255) NOT NULL,
|
||||
active BOOLEAN DEFAULT FALSE,
|
||||
role VARCHAR(50) DEFAULT 'user',
|
||||
language VARCHAR(5) DEFAULT 'en',
|
||||
ip VARCHAR(45),
|
||||
created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
@@ -42,24 +44,28 @@ public class UserService {
|
||||
.map(rows -> rows.iterator().next().getLong("cnt"));
|
||||
}
|
||||
|
||||
public Future<Void> createUser(String login, String email, String password, String ip, boolean active) {
|
||||
public Future<Void> createUser(String login, String email, String password, String ip, boolean active, String role) {
|
||||
String hash = BCrypt.hashpw(password, BCrypt.gensalt());
|
||||
Map<String, Object> params = Map.of(
|
||||
"login", login,
|
||||
"email", email,
|
||||
"password", hash,
|
||||
"ip", ip,
|
||||
"active", active
|
||||
"active", active,
|
||||
"role", role
|
||||
);
|
||||
return SqlTemplate.forUpdate(pool,
|
||||
"INSERT INTO users (login, email, password, ip, active) VALUES (#{login}, #{email}, #{password}, #{ip}, #{active})")
|
||||
"INSERT INTO users (login, email, password, ip, active, role) VALUES (#{login}, #{email}, #{password}, #{ip}, #{active}, #{role})")
|
||||
.execute(params)
|
||||
.mapEmpty();
|
||||
}
|
||||
|
||||
// Существующий метод оставляем, но он будет создавать неактивного пользователя (active = false)
|
||||
public Future<Void> createUser(String login, String email, String password, String ip, boolean active) {
|
||||
return createUser(login, email, password, ip, active, "user");
|
||||
}
|
||||
|
||||
public Future<Void> createUser(String login, String email, String password, String ip) {
|
||||
return createUser(login, email, password, ip, false);
|
||||
return createUser(login, email, password, ip, false, "user");
|
||||
}
|
||||
|
||||
public Future<Void> setActive(int id, boolean active) {
|
||||
@@ -68,7 +74,7 @@ public class UserService {
|
||||
}
|
||||
|
||||
public Future<JsonObject> findByLoginOrEmail(String loginOrEmail) {
|
||||
String sql = "SELECT id, login, email, password, active, ip, created, updated FROM users WHERE login = ? OR email = ?";
|
||||
String sql = "SELECT id, login, email, password, active, role, language, ip, created, updated FROM users WHERE login = ? OR email = ?";
|
||||
return pool.preparedQuery(sql)
|
||||
.execute(Tuple.of(loginOrEmail, loginOrEmail))
|
||||
.map(rows -> {
|
||||
@@ -80,31 +86,8 @@ public class UserService {
|
||||
});
|
||||
}
|
||||
|
||||
public Future<JsonObject> findByEmail(String email) {
|
||||
return SqlTemplate.forQuery(pool, "SELECT id, login, email, password, active, ip, created, updated FROM users WHERE email = #{email}")
|
||||
.mapTo(this::toJson)
|
||||
.execute(Map.of("email", email))
|
||||
.map(rows -> rows.iterator().hasNext() ? rows.iterator().next() : null);
|
||||
}
|
||||
|
||||
public Future<JsonObject> findByLogin(String login) {
|
||||
return SqlTemplate.forQuery(pool,
|
||||
"SELECT id, login, password, created, updated, ip FROM users WHERE login = #{login}")
|
||||
.mapTo(row -> new JsonObject()
|
||||
.put("id", row.getInteger("id"))
|
||||
.put("login", row.getString("login"))
|
||||
.put("password", row.getString("password"))
|
||||
.put("created", row.getLocalDateTime("created") != null ?
|
||||
row.getLocalDateTime("created").toString() : null)
|
||||
.put("updated", row.getLocalDateTime("updated") != null ?
|
||||
row.getLocalDateTime("updated").toString() : null)
|
||||
.put("ip", row.getString("ip")))
|
||||
.execute(Collections.singletonMap("login", login))
|
||||
.map(rows -> rows.iterator().hasNext() ? rows.iterator().next() : null);
|
||||
}
|
||||
|
||||
public Future<JsonArray> getAllUsers() {
|
||||
return pool.query("SELECT id, login, email, active, ip, created, updated FROM users ORDER BY id")
|
||||
return pool.query("SELECT id, login, email, active, role, language, ip, created, updated FROM users ORDER BY id")
|
||||
.execute()
|
||||
.map(rows -> {
|
||||
JsonArray array = new JsonArray();
|
||||
@@ -114,6 +97,8 @@ public class UserService {
|
||||
.put("login", row.getString("login"))
|
||||
.put("email", row.getString("email"))
|
||||
.put("active", row.getBoolean("active"))
|
||||
.put("role", row.getString("role"))
|
||||
.put("language", row.getString("language"))
|
||||
.put("ip", row.getString("ip"))
|
||||
.put("created", row.getLocalDateTime("created") != null ? row.getLocalDateTime("created").toString() : null)
|
||||
.put("updated", row.getLocalDateTime("updated") != null ? row.getLocalDateTime("updated").toString() : null));
|
||||
@@ -122,19 +107,23 @@ public class UserService {
|
||||
});
|
||||
}
|
||||
|
||||
public Future<Void> updateUser(int id, String login, String email, String password, String ip) {
|
||||
public Future<Void> updateUser(int id, String login, String email, String password, String ip, String role) {
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("id", id);
|
||||
params.put("login", login);
|
||||
params.put("email", email);
|
||||
params.put("ip", ip);
|
||||
if (role != null) params.put("role", role);
|
||||
|
||||
String sql;
|
||||
if (password != null && !password.isEmpty()) {
|
||||
String hash = BCrypt.hashpw(password, BCrypt.gensalt());
|
||||
params.put("password", hash);
|
||||
sql = "UPDATE users SET login = #{login}, email = #{email}, password = #{password}, ip = #{ip} WHERE id = #{id}";
|
||||
sql = "UPDATE users SET login = #{login}, email = #{email}, password = #{password}, ip = #{ip}"
|
||||
+ (role != null ? ", role = #{role}" : "") + " WHERE id = #{id}";
|
||||
} else {
|
||||
sql = "UPDATE users SET login = #{login}, email = #{email}, ip = #{ip} WHERE id = #{id}";
|
||||
sql = "UPDATE users SET login = #{login}, email = #{email}, ip = #{ip}"
|
||||
+ (role != null ? ", role = #{role}" : "") + " WHERE id = #{id}";
|
||||
}
|
||||
return SqlTemplate.forUpdate(pool, sql).execute(params).mapEmpty();
|
||||
}
|
||||
@@ -151,6 +140,44 @@ public class UserService {
|
||||
.mapEmpty();
|
||||
}
|
||||
|
||||
public Future<JsonObject> getProfile(int userId) {
|
||||
return SqlTemplate.forQuery(pool,
|
||||
"SELECT id, login, email, role, language, ip, created, updated FROM users WHERE id = #{id}")
|
||||
.mapTo(row -> new JsonObject()
|
||||
.put("id", row.getInteger("id"))
|
||||
.put("login", row.getString("login"))
|
||||
.put("email", row.getString("email"))
|
||||
.put("role", row.getString("role"))
|
||||
.put("language", row.getString("language"))
|
||||
.put("ip", row.getString("ip"))
|
||||
.put("created", row.getLocalDateTime("created") != null ? row.getLocalDateTime("created").toString() : null)
|
||||
.put("updated", row.getLocalDateTime("updated") != null ? row.getLocalDateTime("updated").toString() : null))
|
||||
.execute(Map.of("id", userId))
|
||||
.map(rows -> rows.iterator().hasNext() ? rows.iterator().next() : null);
|
||||
}
|
||||
|
||||
public Future<Void> updateProfile(int userId, String email, String password, String language) {
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("id", userId);
|
||||
params.put("email", email);
|
||||
if (language != null) params.put("language", language);
|
||||
String sql;
|
||||
if (password != null && !password.isEmpty()) {
|
||||
String hash = BCrypt.hashpw(password, BCrypt.gensalt());
|
||||
params.put("password", hash);
|
||||
sql = "UPDATE users SET email = #{email}, password = #{password}, language = COALESCE(#{language}, language) WHERE id = #{id}";
|
||||
} else {
|
||||
sql = "UPDATE users SET email = #{email}, language = COALESCE(#{language}, language) WHERE id = #{id}";
|
||||
}
|
||||
return SqlTemplate.forUpdate(pool, sql).execute(params).mapEmpty();
|
||||
}
|
||||
|
||||
public Future<Void> updateLanguage(int userId, String language) {
|
||||
return SqlTemplate.forUpdate(pool, "UPDATE users SET language = #{lang} WHERE id = #{id}")
|
||||
.execute(Map.of("id", userId, "lang", language))
|
||||
.mapEmpty();
|
||||
}
|
||||
|
||||
public boolean checkPassword(String plain, String hash) {
|
||||
try {
|
||||
return BCrypt.checkpw(plain, hash);
|
||||
@@ -164,8 +191,10 @@ public class UserService {
|
||||
.put("id", row.getInteger("id"))
|
||||
.put("login", row.getString("login"))
|
||||
.put("email", row.getString("email"))
|
||||
.put("password", row.getString("password")) // ← ДОБАВИТЬ ЭТУ СТРОКУ
|
||||
.put("password", row.getString("password"))
|
||||
.put("active", row.getBoolean("active"))
|
||||
.put("role", row.getString("role"))
|
||||
.put("language", row.getString("language"))
|
||||
.put("ip", row.getString("ip"))
|
||||
.put("created", row.getLocalDateTime("created") != null ? row.getLocalDateTime("created").toString() : null)
|
||||
.put("updated", row.getLocalDateTime("updated") != null ? row.getLocalDateTime("updated").toString() : null);
|
||||
|
||||
Reference in New Issue
Block a user