🆕 Added Reset password / Email and chenge username in user settings

This commit is contained in:
duckietm
2026-05-11 18:06:34 +02:00
parent d9465a0a65
commit 47be392d8e
7 changed files with 566 additions and 25 deletions
@@ -0,0 +1,5 @@
ALTER TABLE users
ADD COLUMN `last_username_change` INT(11) NOT NULL;
INSERT INTO emulator_settings (`key`, `value`, `comment`)
VALUES ('rename.cooldown_days', '30', 'Days between username changes');
@@ -1,18 +0,0 @@
package com.eu.habbo.habbohotel.commands;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.gameclients.GameClient;
import com.eu.habbo.messages.outgoing.users.UserDataComposer;
public class ChangeNameCommand extends Command {
public ChangeNameCommand() {
super("cmd_changename", Emulator.getTexts().getValue("commands.keys.cmd_changename").split(";"));
}
@Override
public boolean handle(GameClient gameClient, String[] params) throws Exception {
gameClient.getHabbo().getHabboStats().allowNameChange = !gameClient.getHabbo().getHabboStats().allowNameChange;
gameClient.sendResponse(new UserDataComposer(gameClient.getHabbo()));
return true;
}
}
@@ -184,7 +184,6 @@ public class CommandHandler {
addCommand(new BlockAlertCommand());
addCommand(new BotsCommand());
addCommand(new CalendarCommand());
addCommand(new ChangeNameCommand());
addCommand(new ChatTypeCommand());
addCommand(new CommandsCommand());
addCommand(new ControlCommand());
@@ -38,6 +38,9 @@ public class AuthHttpHandler extends ChannelInboundHandlerAdapter {
private static final String REFRESH_PATH = "/api/auth/refresh";
private static final String SERVER_KEY_PATH = "/api/auth/server-key";
private static final String SSO_TOKEN_PATH = "/api/auth/sso-token";
private static final String CHANGE_PASSWORD_PATH = "/api/auth/change-password";
private static final String CHANGE_EMAIL_PATH = "/api/auth/change-email";
private static final String CHANGE_USERNAME_PATH = "/api/auth/change-username";
private static final String HEALTH_PATH = "/api/health";
private static final Pattern USERNAME_RE = Pattern.compile("^[A-Za-z0-9._-]{3,32}$");
@@ -64,6 +67,9 @@ public class AuthHttpHandler extends ChannelInboundHandlerAdapter {
&& !path.equals(REFRESH_PATH)
&& !path.equals(SERVER_KEY_PATH)
&& !path.equals(SSO_TOKEN_PATH)
&& !path.equals(CHANGE_PASSWORD_PATH)
&& !path.equals(CHANGE_EMAIL_PATH)
&& !path.equals(CHANGE_USERNAME_PATH)
&& !path.equals(HEALTH_PATH)) {
super.channelRead(ctx, msg);
return;
@@ -180,6 +186,18 @@ public class AuthHttpHandler extends ChannelInboundHandlerAdapter {
handleSsoToken(ctx, req, body, ip);
return;
}
if (path.equals(CHANGE_PASSWORD_PATH)) {
handleChangePassword(ctx, req, body, ip);
return;
}
if (path.equals(CHANGE_EMAIL_PATH)) {
handleChangeEmail(ctx, req, body, ip);
return;
}
if (path.equals(CHANGE_USERNAME_PATH)) {
handleChangeUsername(ctx, req, body, ip);
return;
}
String turnstileToken = readString(body, "turnstileToken");
if (!TurnstileVerifier.verify(turnstileToken, ip)) {
@@ -274,6 +292,517 @@ public class AuthHttpHandler extends ChannelInboundHandlerAdapter {
sendJson(ctx, req, HttpResponseStatus.OK, res);
}
private void handleChangePassword(ChannelHandlerContext ctx, FullHttpRequest req, JsonObject body, String ip) {
String authHeader = req.headers().get(HttpHeaderNames.AUTHORIZATION);
String bearer = "";
if (authHeader != null && authHeader.regionMatches(true, 0, "Bearer ", 0, 7)) {
bearer = authHeader.substring(7).trim();
}
int userId = AccessTokenService.verify(bearer);
if (userId <= 0) {
AuthRateLimiter.recordFailure(ip);
sendJson(ctx, req, HttpResponseStatus.UNAUTHORIZED, errorPayload("Not authenticated."));
return;
}
String currentPassword = readString(body, "currentPassword");
String newPassword = readString(body, "newPassword");
String confirmPassword = readString(body, "confirmPassword");
if (currentPassword.isEmpty() || newPassword.isEmpty() || confirmPassword.isEmpty()) {
sendJson(ctx, req, HttpResponseStatus.BAD_REQUEST,
errorPayload("All fields are required."));
return;
}
if (currentPassword.length() > 256 || newPassword.length() > 256) {
sendJson(ctx, req, HttpResponseStatus.BAD_REQUEST,
errorPayload("Password too long."));
return;
}
if (!newPassword.equals(confirmPassword)) {
sendJson(ctx, req, HttpResponseStatus.BAD_REQUEST,
errorPayload("New passwords do not match."));
return;
}
if (newPassword.length() < 8) {
sendJson(ctx, req, HttpResponseStatus.BAD_REQUEST,
errorPayload("Password must be at least 8 characters."));
return;
}
if (newPassword.equals(currentPassword)) {
sendJson(ctx, req, HttpResponseStatus.BAD_REQUEST,
errorPayload("New password must be different from the current password."));
return;
}
try (Connection conn = Emulator.getDatabase().getDataSource().getConnection()) {
String storedHash = null;
String username = null;
try (PreparedStatement lookup = conn.prepareStatement(
"SELECT username, password FROM users WHERE id = ? LIMIT 1")) {
lookup.setInt(1, userId);
try (ResultSet rs = lookup.executeQuery()) {
if (rs.next()) {
username = rs.getString("username");
storedHash = rs.getString("password");
}
}
}
if (storedHash == null || storedHash.isEmpty()) {
AuthRateLimiter.recordFailure(ip);
sendJson(ctx, req, HttpResponseStatus.UNAUTHORIZED, errorPayload("Account not found."));
return;
}
if (!checkPassword(currentPassword, storedHash)) {
AuthRateLimiter.recordFailure(ip);
LOGGER.info("[auth/change-password] current password mismatch for user id={} username='{}'", userId, username);
sendJson(ctx, req, HttpResponseStatus.UNAUTHORIZED,
errorPayload("Current password is incorrect."));
return;
}
String hashed = BCrypt.hashpw(newPassword, BCrypt.gensalt(12));
try (PreparedStatement upd = conn.prepareStatement(
"UPDATE users SET password = ? WHERE id = ? LIMIT 1")) {
upd.setString(1, hashed);
upd.setInt(2, userId);
upd.executeUpdate();
}
AuthRateLimiter.recordSuccess(ip);
LOGGER.info("[auth/change-password] password updated for user id={} username='{}' ip='{}'", userId, username, ip);
JsonObject ok = new JsonObject();
ok.addProperty("message", "Password updated successfully.");
sendJson(ctx, req, HttpResponseStatus.OK, ok);
} catch (Exception e) {
LOGGER.error("[auth/change-password] failed for user id=" + userId, e);
sendJson(ctx, req, HttpResponseStatus.INTERNAL_SERVER_ERROR, errorPayload("Server error."));
}
}
private void handleChangeEmail(ChannelHandlerContext ctx, FullHttpRequest req, JsonObject body, String ip) {
String authHeader = req.headers().get(HttpHeaderNames.AUTHORIZATION);
String bearer = "";
if (authHeader != null && authHeader.regionMatches(true, 0, "Bearer ", 0, 7)) {
bearer = authHeader.substring(7).trim();
}
int userId = AccessTokenService.verify(bearer);
if (userId <= 0) {
AuthRateLimiter.recordFailure(ip);
sendJson(ctx, req, HttpResponseStatus.UNAUTHORIZED, errorPayload("Not authenticated."));
return;
}
String currentPassword = readString(body, "currentPassword");
String newEmail = readString(body, "newEmail").trim();
if (currentPassword.isEmpty() || newEmail.isEmpty()) {
sendJson(ctx, req, HttpResponseStatus.BAD_REQUEST,
errorPayload("All fields are required."));
return;
}
if (currentPassword.length() > 256 || newEmail.length() > 254) {
sendJson(ctx, req, HttpResponseStatus.BAD_REQUEST,
errorPayload("Field too long."));
return;
}
if (!EMAIL_RE.matcher(newEmail).matches()) {
sendJson(ctx, req, HttpResponseStatus.BAD_REQUEST,
errorPayload("Invalid email address."));
return;
}
try (Connection conn = Emulator.getDatabase().getDataSource().getConnection()) {
String storedHash = null;
String username = null;
String currentEmail = null;
try (PreparedStatement lookup = conn.prepareStatement(
"SELECT username, password, mail FROM users WHERE id = ? LIMIT 1")) {
lookup.setInt(1, userId);
try (ResultSet rs = lookup.executeQuery()) {
if (rs.next()) {
username = rs.getString("username");
storedHash = rs.getString("password");
currentEmail = rs.getString("mail");
}
}
}
if (storedHash == null || storedHash.isEmpty()) {
AuthRateLimiter.recordFailure(ip);
sendJson(ctx, req, HttpResponseStatus.UNAUTHORIZED, errorPayload("Account not found."));
return;
}
if (!checkPassword(currentPassword, storedHash)) {
AuthRateLimiter.recordFailure(ip);
LOGGER.info("[auth/change-email] password mismatch for user id={} username='{}'", userId, username);
sendJson(ctx, req, HttpResponseStatus.UNAUTHORIZED,
errorPayload("Current password is incorrect."));
return;
}
if (currentEmail != null && currentEmail.equalsIgnoreCase(newEmail)) {
sendJson(ctx, req, HttpResponseStatus.BAD_REQUEST,
errorPayload("New email must be different from the current email."));
return;
}
try (PreparedStatement check = conn.prepareStatement(
"SELECT id FROM users WHERE mail = ? AND id <> ? LIMIT 1")) {
check.setString(1, newEmail);
check.setInt(2, userId);
try (ResultSet rs = check.executeQuery()) {
if (rs.next()) {
sendJson(ctx, req, HttpResponseStatus.CONFLICT,
errorPayload("That email address is already in use."));
return;
}
}
}
try (PreparedStatement upd = conn.prepareStatement(
"UPDATE users SET mail = ? WHERE id = ? LIMIT 1")) {
upd.setString(1, newEmail);
upd.setInt(2, userId);
upd.executeUpdate();
}
if (currentEmail != null && !currentEmail.isEmpty()) AvailabilityCache.invalidateEmail(currentEmail);
AvailabilityCache.invalidateEmail(newEmail);
AuthRateLimiter.recordSuccess(ip);
LOGGER.info("[auth/change-email] email updated for user id={} username='{}' ip='{}'", userId, username, ip);
JsonObject ok = new JsonObject();
ok.addProperty("message", "Email updated successfully.");
ok.addProperty("email", newEmail);
sendJson(ctx, req, HttpResponseStatus.OK, ok);
} catch (Exception e) {
LOGGER.error("[auth/change-email] failed for user id=" + userId, e);
sendJson(ctx, req, HttpResponseStatus.INTERNAL_SERVER_ERROR, errorPayload("Server error."));
}
}
private void handleChangeUsername(ChannelHandlerContext ctx, FullHttpRequest req, JsonObject body, String ip) {
String authHeader = req.headers().get(HttpHeaderNames.AUTHORIZATION);
String bearer = "";
if (authHeader != null && authHeader.regionMatches(true, 0, "Bearer ", 0, 7)) {
bearer = authHeader.substring(7).trim();
}
int userId = AccessTokenService.verify(bearer);
if (userId <= 0) {
AuthRateLimiter.recordFailure(ip);
sendJson(ctx, req, HttpResponseStatus.UNAUTHORIZED, errorPayload("Not authenticated."));
return;
}
String currentPassword = readString(body, "currentPassword");
String newUsername = readString(body, "newUsername").trim();
if (currentPassword.isEmpty() || newUsername.isEmpty()) {
sendJson(ctx, req, HttpResponseStatus.BAD_REQUEST,
errorPayload("All fields are required."));
return;
}
if (currentPassword.length() > 256) {
sendJson(ctx, req, HttpResponseStatus.BAD_REQUEST,
errorPayload("Field too long."));
return;
}
if (newUsername.length() > 25) {
sendJson(ctx, req, HttpResponseStatus.BAD_REQUEST,
errorPayload("Username can be at most 25 characters."));
return;
}
if (!USERNAME_RE.matcher(newUsername).matches()) {
sendJson(ctx, req, HttpResponseStatus.BAD_REQUEST,
errorPayload("Username must be 3-25 characters (letters, numbers, . _ -)."));
return;
}
long cooldownDays = Math.max(0, Emulator.getConfig().getInt("rename.cooldown_days", 30));
long cooldownSeconds = cooldownDays * 86400L;
try (Connection conn = Emulator.getDatabase().getDataSource().getConnection()) {
String storedHash = null;
String currentUsername = null;
int lastChange = 0;
boolean cooldownColumnExists = true;
try (PreparedStatement lookup = conn.prepareStatement(
"SELECT username, password, last_username_change FROM users WHERE id = ? LIMIT 1")) {
lookup.setInt(1, userId);
try (ResultSet rs = lookup.executeQuery()) {
if (rs.next()) {
currentUsername = rs.getString("username");
storedHash = rs.getString("password");
lastChange = rs.getInt("last_username_change");
}
}
} catch (SQLException missingColumn) {
cooldownColumnExists = false;
LOGGER.warn("[auth/change-username] users.last_username_change column missing — cooldown disabled. Run the migration in config/Database.sql.");
try (PreparedStatement lookup = conn.prepareStatement(
"SELECT username, password FROM users WHERE id = ? LIMIT 1")) {
lookup.setInt(1, userId);
try (ResultSet rs = lookup.executeQuery()) {
if (rs.next()) {
currentUsername = rs.getString("username");
storedHash = rs.getString("password");
}
}
}
}
if (storedHash == null || storedHash.isEmpty()) {
AuthRateLimiter.recordFailure(ip);
sendJson(ctx, req, HttpResponseStatus.UNAUTHORIZED, errorPayload("Account not found."));
return;
}
if (!checkPassword(currentPassword, storedHash)) {
AuthRateLimiter.recordFailure(ip);
LOGGER.info("[auth/change-username] password mismatch for user id={} username='{}'", userId, currentUsername);
sendJson(ctx, req, HttpResponseStatus.UNAUTHORIZED,
errorPayload("Current password is incorrect."));
return;
}
// Case-only changes (e.g. "bob" -> "Bob") are allowed; identical strings are not.
if (currentUsername != null && currentUsername.equals(newUsername)) {
sendJson(ctx, req, HttpResponseStatus.BAD_REQUEST,
errorPayload("New username must be different from the current username."));
return;
}
int now = Emulator.getIntUnixTimestamp();
if (cooldownColumnExists && cooldownSeconds > 0 && lastChange > 0) {
long allowedAt = (long) lastChange + cooldownSeconds;
if (now < allowedAt) {
long remaining = allowedAt - now;
long days = remaining / 86400L;
long hours = (remaining % 86400L) / 3600L;
String wait = days > 0 ? (days + " day" + (days == 1 ? "" : "s")) : (hours + " hour" + (hours == 1 ? "" : "s"));
sendJson(ctx, req, HttpResponseStatus.TOO_MANY_REQUESTS,
errorPayload("You can rename again in " + wait + "."));
return;
}
}
try (PreparedStatement banned = conn.prepareStatement(
"SELECT 1 FROM banned_usernames WHERE LOWER(username) = LOWER(?) LIMIT 1")) {
banned.setString(1, newUsername);
try (ResultSet rs = banned.executeQuery()) {
if (rs.next()) {
sendJson(ctx, req, HttpResponseStatus.CONFLICT,
errorPayload("That username is not allowed."));
return;
}
}
} catch (SQLException bannedTableError) {
// Only swallow "table doesn't exist" — banned_usernames is optional in some installs.
// Any other DB error (timeout, syntax, permission, connection drop) must fail the
// request so a transient outage can't silently bypass the blocklist.
if (bannedTableError.getErrorCode() != 1146
&& !"42S02".equals(bannedTableError.getSQLState())) {
throw bannedTableError;
}
}
try (PreparedStatement check = conn.prepareStatement(
"SELECT id FROM users WHERE LOWER(username) = LOWER(?) AND id <> ? LIMIT 1")) {
check.setString(1, newUsername);
check.setInt(2, userId);
try (ResultSet rs = check.executeQuery()) {
if (rs.next()) {
sendJson(ctx, req, HttpResponseStatus.CONFLICT,
errorPayload("That username is already taken."));
return;
}
}
}
boolean previousAutoCommit = conn.getAutoCommit();
conn.setAutoCommit(false);
boolean cooldownRace = false;
boolean duplicateName = false;
try {
int rowsUpdated = 0;
// Folding the cooldown into the WHERE clause makes the rename atomic against
// a second concurrent request with the same token: only one of them will see
// rowsUpdated == 1, the other gets 0 and we surface it as 429 instead of letting
// both succeed and writing last_username_change twice.
try (PreparedStatement upd = conn.prepareStatement(
cooldownColumnExists
? "UPDATE users SET username = ?, last_username_change = ? "
+ "WHERE id = ? "
+ " AND (last_username_change = 0 OR last_username_change + ? <= ?) "
+ "LIMIT 1"
: "UPDATE users SET username = ? WHERE id = ? LIMIT 1")) {
upd.setString(1, newUsername);
if (cooldownColumnExists) {
upd.setInt(2, now);
upd.setInt(3, userId);
upd.setLong(4, cooldownSeconds);
upd.setInt(5, now);
} else {
upd.setInt(2, userId);
}
try {
rowsUpdated = upd.executeUpdate();
} catch (SQLException dup) {
// ER_DUP_ENTRY (1062) / SQLState 23000 — the UNIQUE KEY on users.username
// raced us between the pre-check and the UPDATE. Surface as 409 instead of
// a generic 500 so the UI can show "username taken".
if (dup.getErrorCode() == 1062 || "23000".equals(dup.getSQLState())) {
duplicateName = true;
} else {
throw dup;
}
}
}
if (duplicateName || (cooldownColumnExists && rowsUpdated == 0)) {
if (!duplicateName) cooldownRace = true;
conn.rollback();
} else {
try (PreparedStatement upd = conn.prepareStatement(
"UPDATE rooms SET owner_name = ? WHERE owner_id = ?")) {
upd.setString(1, newUsername);
upd.setInt(2, userId);
upd.executeUpdate();
}
try (PreparedStatement upd = conn.prepareStatement(
"UPDATE rooms_for_sale SET owner_name = ? WHERE user_id = ?")) {
upd.setString(1, newUsername);
upd.setInt(2, userId);
upd.executeUpdate();
} catch (SQLException roomsForSale) {
// rooms_for_sale is optional — only swallow "table doesn't exist".
if (roomsForSale.getErrorCode() != 1146
&& !"42S02".equals(roomsForSale.getSQLState())) {
throw roomsForSale;
}
}
conn.commit();
}
} catch (SQLException txError) {
try { conn.rollback(); } catch (SQLException ignore) {}
throw txError;
} finally {
conn.setAutoCommit(previousAutoCommit);
}
if (duplicateName) {
LOGGER.info("[auth/change-username] dup-entry race for user id={} wanted='{}'", userId, newUsername);
sendJson(ctx, req, HttpResponseStatus.CONFLICT,
errorPayload("That username is already taken."));
return;
}
if (cooldownRace) {
LOGGER.info("[auth/change-username] cooldown race for user id={} (concurrent rename rejected)", userId);
sendJson(ctx, req, HttpResponseStatus.TOO_MANY_REQUESTS,
errorPayload("Rename already in progress — please wait."));
return;
}
// Refresh in-memory caches BEFORE we drop the client. On disconnect, Habbo.disconnect()
// calls HabboInfo.run() which persists every field of HabboInfo back to the users row
// (including username) — so if the in-memory object still holds the old name it will
// overwrite the DB update we just committed. We also need to re-key the
// HabboManager.onlineHabbosByName map and update any loaded Room.ownerName fields so
// navigator / room cards stop showing the old name without a server restart.
try {
if (Emulator.getGameServer() != null && Emulator.getGameServer().getGameClientManager() != null
&& Emulator.getGameEnvironment() != null && Emulator.getGameEnvironment().getHabboManager() != null) {
com.eu.habbo.habbohotel.users.Habbo habbo =
Emulator.getGameServer().getGameClientManager().getHabbo(userId);
if (habbo != null) {
// Re-key the username map AND update the in-memory username so the
// disconnect-time HabboInfo.run() persists the new name, not the old one.
Emulator.getGameEnvironment().getHabboManager().removeHabbo(habbo);
habbo.getHabboInfo().setUsername(newUsername);
Emulator.getGameEnvironment().getHabboManager().addHabbo(habbo);
}
}
} catch (Exception cacheError) {
LOGGER.warn("[auth/change-username] failed to refresh HabboManager cache", cacheError);
}
try {
if (Emulator.getGameEnvironment() != null && Emulator.getGameEnvironment().getRoomManager() != null) {
for (com.eu.habbo.habbohotel.rooms.Room room : Emulator.getGameEnvironment().getRoomManager().getActiveRooms()) {
if (room.getOwnerId() == userId) {
room.setOwnerName(newUsername);
}
}
}
} catch (Exception cacheError) {
LOGGER.warn("[auth/change-username] failed to refresh Room.ownerName cache", cacheError);
}
// Marketplace offer-list pages bake the seller name into the serialized payload,
// so invalidate it; the next /offers request will rebuild it with the new name.
try {
com.eu.habbo.messages.incoming.catalog.marketplace.RequestOffersEvent.cachedResults.clear();
} catch (Exception cacheError) {
LOGGER.warn("[auth/change-username] failed to clear marketplace cache", cacheError);
}
// Drop any active session so the user reconnects with the fresh identity
// (friend list, navigator, messenger threads all refresh on relogin).
try (PreparedStatement clear = conn.prepareStatement(
"UPDATE users SET auth_ticket = '', online = '0' WHERE id = ? LIMIT 1")) {
clear.setInt(1, userId);
clear.executeUpdate();
}
if (Emulator.getGameServer() != null
&& Emulator.getGameServer().getGameClientManager() != null) {
com.eu.habbo.habbohotel.users.Habbo habbo =
Emulator.getGameServer().getGameClientManager().getHabbo(userId);
if (habbo != null && habbo.getClient() != null) {
Emulator.getGameServer().getGameClientManager().disposeClient(habbo.getClient());
}
}
AuthRateLimiter.recordSuccess(ip);
LOGGER.info("[auth/change-username] '{}' -> '{}' (user id={}, ip='{}')",
currentUsername, newUsername, userId, ip);
JsonObject ok = new JsonObject();
ok.addProperty("message", "Username updated. Please log in again with your new name.");
ok.addProperty("username", newUsername);
ok.addProperty("relogin", true);
sendJson(ctx, req, HttpResponseStatus.OK, ok);
} catch (Exception e) {
LOGGER.error("[auth/change-username] failed for user id=" + userId, e);
sendJson(ctx, req, HttpResponseStatus.INTERNAL_SERVER_ERROR, errorPayload("Server error."));
}
}
private void handleLogout(ChannelHandlerContext ctx, FullHttpRequest req, com.google.gson.JsonObject body) {
String ssoTicket = readString(body, "ssoTicket");
String rememberToken = readString(body, "rememberToken").trim();
@@ -1135,11 +1664,20 @@ public class AuthHttpHandler extends ChannelInboundHandlerAdapter {
String origin = req.headers().get(HttpHeaderNames.ORIGIN);
if (origin != null && !origin.isEmpty()) {
response.headers().set("Access-Control-Allow-Origin", origin);
response.headers().set("Vary", "Origin");
response.headers().set("Access-Control-Allow-Credentials", "true");
}
response.headers().set("Access-Control-Allow-Methods", "GET, HEAD, POST, OPTIONS");
response.headers().set("Access-Control-Allow-Headers", "Content-Type, X-Requested-With, X-Nitro-Key, X-Nitro-Api");
String requestedHeaders = req.headers().get("Access-Control-Request-Headers");
if (requestedHeaders != null && !requestedHeaders.isEmpty()) {
response.headers().set("Access-Control-Allow-Headers", requestedHeaders);
} else {
response.headers().set("Access-Control-Allow-Headers",
"Authorization, Content-Type, X-Requested-With, X-Nitro-Key, X-Nitro-Api");
}
response.headers().set("Vary", "Origin, Access-Control-Request-Headers, Access-Control-Request-Method");
response.headers().set("Access-Control-Max-Age", "600");
response.headers().set("Access-Control-Expose-Headers", "X-Nitro-Sec, X-Nitro-Key-Fp, X-Nitro-Derive-Fp");
}
@@ -267,11 +267,20 @@ public class NitroSecureApiHandler extends ChannelDuplexHandler {
String origin = req.headers().get(HttpHeaderNames.ORIGIN);
if (origin != null && !origin.isEmpty()) {
response.headers().set("Access-Control-Allow-Origin", origin);
response.headers().set("Vary", "Origin");
response.headers().set("Access-Control-Allow-Credentials", "true");
}
response.headers().set("Access-Control-Allow-Methods", "GET, HEAD, POST, OPTIONS");
response.headers().set("Access-Control-Allow-Headers", "Content-Type, X-Requested-With, X-Nitro-Key, X-Nitro-Api");
String requestedHeaders = req.headers().get("Access-Control-Request-Headers");
if (requestedHeaders != null && !requestedHeaders.isEmpty()) {
response.headers().set("Access-Control-Allow-Headers", requestedHeaders);
} else {
response.headers().set("Access-Control-Allow-Headers",
"Authorization, Content-Type, X-Requested-With, X-Nitro-Key, X-Nitro-Api");
}
response.headers().set("Vary", "Origin, Access-Control-Request-Headers, Access-Control-Request-Method");
response.headers().set("Access-Control-Max-Age", "600");
response.headers().set("Access-Control-Expose-Headers", "X-Nitro-Sec, X-Nitro-Key-Fp, X-Nitro-Derive-Fp");
}
@@ -299,10 +299,18 @@ public class NitroSecureAssetHandler extends ChannelInboundHandlerAdapter {
String origin = req.headers().get(HttpHeaderNames.ORIGIN);
if (origin != null && !origin.isEmpty()) {
response.headers().set("Access-Control-Allow-Origin", origin);
response.headers().set("Vary", "Origin");
}
response.headers().set("Access-Control-Allow-Methods", "GET, HEAD, POST, OPTIONS");
response.headers().set("Access-Control-Allow-Headers", "Content-Type, X-Nitro-Key");
String requestedHeaders = req.headers().get("Access-Control-Request-Headers");
if (requestedHeaders != null && !requestedHeaders.isEmpty()) {
response.headers().set("Access-Control-Allow-Headers", requestedHeaders);
} else {
response.headers().set("Access-Control-Allow-Headers", "Authorization, Content-Type, X-Nitro-Key");
}
response.headers().set("Vary", "Origin, Access-Control-Request-Headers, Access-Control-Request-Method");
response.headers().set("Access-Control-Max-Age", "600");
response.headers().set("Access-Control-Expose-Headers", "X-Nitro-Sec, X-Nitro-Key-Fp, X-Nitro-Derive-Fp");
}
Binary file not shown.