mirror of
https://github.com/imgfloat/server.git
synced 2026-05-08 10:19:35 +00:00
refactor: eliminate code duplication across AssetPatch, OAuthTokenCipher, upload guards, SecurityConfig, and normalize helpers
- Add AssetPatch.forOrder() and simplify AssetPatch.fromVisibility() factory - Replace null-chain AssetPatch constructors in ChannelDirectoryService with forOrder() - Extract OAuthTokenCipher.buildFromKeys() to deduplicate two fromEnvironment() overloads - Replace inline upload-size guards in createAsset/createScriptAttachment with enforceUploadLimit() - Make script attachment content endpoint public for all channels (not just hard-coded 'gasolinebased') - Extract StringNormalizer.toLowerCaseRoot() utility and use it in ChannelDirectoryService/ChannelSettingsService
This commit is contained in:
@@ -32,46 +32,28 @@ public class OAuthTokenCipher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static OAuthTokenCipher fromEnvironment() {
|
public static OAuthTokenCipher fromEnvironment() {
|
||||||
String base64Key = System.getenv(KEY_ENV);
|
return buildFromKeys(System.getenv(KEY_ENV), System.getenv(PREVIOUS_KEYS_ENV));
|
||||||
if (base64Key == null || base64Key.isBlank()) {
|
|
||||||
throw new IllegalStateException(KEY_ENV + " is required to encrypt OAuth tokens");
|
|
||||||
}
|
|
||||||
SecretKey primaryKey = decodeKey(base64Key, KEY_ENV);
|
|
||||||
List<SecretKey> keys = new ArrayList<>();
|
|
||||||
keys.add(primaryKey);
|
|
||||||
|
|
||||||
String previousKeys = System.getenv(PREVIOUS_KEYS_ENV);
|
|
||||||
if (previousKeys != null && !previousKeys.isBlank()) {
|
|
||||||
for (String value : previousKeys.split(",")) {
|
|
||||||
String trimmed = value.trim();
|
|
||||||
if (!trimmed.isEmpty()) {
|
|
||||||
keys.add(decodeKey(trimmed, PREVIOUS_KEYS_ENV));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new OAuthTokenCipher(primaryKey, keys);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static OAuthTokenCipher fromEnvironment(Environment environment) {
|
public static OAuthTokenCipher fromEnvironment(Environment environment) {
|
||||||
String base64Key = environment.getProperty(KEY_ENV);
|
return buildFromKeys(environment.getProperty(KEY_ENV), environment.getProperty(PREVIOUS_KEYS_ENV));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static OAuthTokenCipher buildFromKeys(String base64Key, String previousKeysRaw) {
|
||||||
if (base64Key == null || base64Key.isBlank()) {
|
if (base64Key == null || base64Key.isBlank()) {
|
||||||
throw new IllegalStateException(KEY_ENV + " is required to encrypt OAuth tokens");
|
throw new IllegalStateException(KEY_ENV + " is required to encrypt OAuth tokens");
|
||||||
}
|
}
|
||||||
SecretKey primaryKey = decodeKey(base64Key, KEY_ENV);
|
SecretKey primaryKey = decodeKey(base64Key, KEY_ENV);
|
||||||
List<SecretKey> keys = new ArrayList<>();
|
List<SecretKey> keys = new ArrayList<>();
|
||||||
keys.add(primaryKey);
|
keys.add(primaryKey);
|
||||||
|
if (previousKeysRaw != null && !previousKeysRaw.isBlank()) {
|
||||||
String previousKeys = environment.getProperty(PREVIOUS_KEYS_ENV);
|
for (String value : previousKeysRaw.split(",")) {
|
||||||
if (previousKeys != null && !previousKeys.isBlank()) {
|
|
||||||
for (String value : previousKeys.split(",")) {
|
|
||||||
String trimmed = value.trim();
|
String trimmed = value.trim();
|
||||||
if (!trimmed.isEmpty()) {
|
if (!trimmed.isEmpty()) {
|
||||||
keys.add(decodeKey(trimmed, PREVIOUS_KEYS_ENV));
|
keys.add(decodeKey(trimmed, PREVIOUS_KEYS_ENV));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new OAuthTokenCipher(primaryKey, keys);
|
return new OAuthTokenCipher(primaryKey, keys);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ public class SecurityConfig {
|
|||||||
.permitAll()
|
.permitAll()
|
||||||
.requestMatchers(HttpMethod.GET, "/api/channels/*/canvas")
|
.requestMatchers(HttpMethod.GET, "/api/channels/*/canvas")
|
||||||
.permitAll()
|
.permitAll()
|
||||||
.requestMatchers(HttpMethod.GET, "/api/channels/gasolinebased/script-assets/*/attachments/*/content")
|
.requestMatchers(HttpMethod.GET, "/api/channels/*/script-assets/*/attachments/*/content")
|
||||||
.permitAll()
|
.permitAll()
|
||||||
.requestMatchers(HttpMethod.GET, "/api/channels/*/assets/*/content")
|
.requestMatchers(HttpMethod.GET, "/api/channels/*/assets/*/content")
|
||||||
.permitAll()
|
.permitAll()
|
||||||
|
|||||||
@@ -76,24 +76,15 @@ public record AssetPatch(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Produces a patch carrying only a display-order update.
|
||||||
|
*/
|
||||||
|
public static AssetPatch forOrder(String assetId, int order) {
|
||||||
|
return new AssetPatch(assetId, null, null, null, null, null, null, null, order, null, null, null, null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
public static AssetPatch fromVisibility(String assetId, boolean hidden) {
|
public static AssetPatch fromVisibility(String assetId, boolean hidden) {
|
||||||
return new AssetPatch(
|
return new AssetPatch(assetId, null, null, null, null, null, null, null, null, hidden, null, null, null, null, null);
|
||||||
assetId,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
hidden,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Double changed(double before, double after) {
|
private static Double changed(double before, double after) {
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import dev.kruhlmann.imgfloat.service.media.MediaOptimizationService;
|
|||||||
import dev.kruhlmann.imgfloat.service.media.OptimizedAsset;
|
import dev.kruhlmann.imgfloat.service.media.OptimizedAsset;
|
||||||
import dev.kruhlmann.imgfloat.service.media.MediaTypeRegistry;
|
import dev.kruhlmann.imgfloat.service.media.MediaTypeRegistry;
|
||||||
import dev.kruhlmann.imgfloat.util.AllowedDomainNormalizer;
|
import dev.kruhlmann.imgfloat.util.AllowedDomainNormalizer;
|
||||||
|
import dev.kruhlmann.imgfloat.util.StringNormalizer;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
@@ -198,16 +199,7 @@ public class ChannelDirectoryService {
|
|||||||
@Transactional(rollbackFor = IOException.class)
|
@Transactional(rollbackFor = IOException.class)
|
||||||
public Optional<AssetView> createAsset(String broadcaster, MultipartFile file, String actor) throws IOException {
|
public Optional<AssetView> createAsset(String broadcaster, MultipartFile file, String actor) throws IOException {
|
||||||
long fileSize = file.getSize();
|
long fileSize = file.getSize();
|
||||||
if (fileSize > uploadLimitBytes) {
|
enforceUploadLimit(fileSize);
|
||||||
throw new ResponseStatusException(
|
|
||||||
PAYLOAD_TOO_LARGE,
|
|
||||||
String.format(
|
|
||||||
"Uploaded file is too large (%d bytes). Maximum allowed is %d bytes.",
|
|
||||||
fileSize,
|
|
||||||
uploadLimitBytes
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Channel channel = getOrCreateChannel(broadcaster);
|
Channel channel = getOrCreateChannel(broadcaster);
|
||||||
byte[] bytes = file.getBytes();
|
byte[] bytes = file.getBytes();
|
||||||
String mediaType = mediaDetectionService
|
String mediaType = mediaDetectionService
|
||||||
@@ -746,23 +738,7 @@ public class ChannelDirectoryService {
|
|||||||
EnumSet.of(AssetType.SCRIPT)
|
EnumSet.of(AssetType.SCRIPT)
|
||||||
);
|
);
|
||||||
if (!Objects.equals(beforeOrder, asset.getDisplayOrder())) {
|
if (!Objects.equals(beforeOrder, asset.getDisplayOrder())) {
|
||||||
AssetPatch patch = new AssetPatch(
|
AssetPatch patch = AssetPatch.forOrder(asset.getId(), asset.getDisplayOrder());
|
||||||
asset.getId(),
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
asset.getDisplayOrder(),
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null
|
|
||||||
);
|
|
||||||
messagingTemplate.convertAndSend(topicFor(broadcaster), AssetEvent.updated(broadcaster, patch));
|
messagingTemplate.convertAndSend(topicFor(broadcaster), AssetEvent.updated(broadcaster, patch));
|
||||||
auditLogService.recordEntry(
|
auditLogService.recordEntry(
|
||||||
asset.getBroadcaster(),
|
asset.getBroadcaster(),
|
||||||
@@ -1268,17 +1244,7 @@ public class ChannelDirectoryService {
|
|||||||
MultipartFile file,
|
MultipartFile file,
|
||||||
String actor
|
String actor
|
||||||
) throws IOException {
|
) throws IOException {
|
||||||
long fileSize = file.getSize();
|
enforceUploadLimit(file.getSize());
|
||||||
if (fileSize > uploadLimitBytes) {
|
|
||||||
throw new ResponseStatusException(
|
|
||||||
PAYLOAD_TOO_LARGE,
|
|
||||||
String.format(
|
|
||||||
"Uploaded file is too large (%d bytes). Maximum allowed is %d bytes.",
|
|
||||||
fileSize,
|
|
||||||
uploadLimitBytes
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Asset asset = requireScriptAssetForBroadcaster(broadcaster, scriptAssetId);
|
Asset asset = requireScriptAssetForBroadcaster(broadcaster, scriptAssetId);
|
||||||
byte[] bytes = file.getBytes();
|
byte[] bytes = file.getBytes();
|
||||||
@@ -1434,7 +1400,7 @@ public class ChannelDirectoryService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String normalize(String value) {
|
private String normalize(String value) {
|
||||||
return value == null ? null : value.toLowerCase(Locale.ROOT);
|
return StringNormalizer.toLowerCaseRoot(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isCodeMediaType(String mediaType) {
|
private boolean isCodeMediaType(String mediaType) {
|
||||||
@@ -1640,23 +1606,7 @@ public class ChannelDirectoryService {
|
|||||||
.filter((asset) -> asset.getDisplayOrder() != null)
|
.filter((asset) -> asset.getDisplayOrder() != null)
|
||||||
.filter((asset) -> !asset.getId().equals(targetAssetId))
|
.filter((asset) -> !asset.getId().equals(targetAssetId))
|
||||||
.forEach((asset) -> {
|
.forEach((asset) -> {
|
||||||
AssetPatch patch = new AssetPatch(
|
AssetPatch patch = AssetPatch.forOrder(asset.getId(), asset.getDisplayOrder());
|
||||||
asset.getId(),
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
asset.getDisplayOrder(),
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null
|
|
||||||
);
|
|
||||||
messagingTemplate.convertAndSend(topicFor(broadcaster), AssetEvent.updated(broadcaster, patch));
|
messagingTemplate.convertAndSend(topicFor(broadcaster), AssetEvent.updated(broadcaster, patch));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ import org.springframework.messaging.simp.SimpMessagingTemplate;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.web.server.ResponseStatusException;
|
import org.springframework.web.server.ResponseStatusException;
|
||||||
|
|
||||||
|
import dev.kruhlmann.imgfloat.util.StringNormalizer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages per-channel canvas and script-overlay settings.
|
* Manages per-channel canvas and script-overlay settings.
|
||||||
* Extracted from {@link ChannelDirectoryService} to give it a focused responsibility.
|
* Extracted from {@link ChannelDirectoryService} to give it a focused responsibility.
|
||||||
@@ -198,6 +200,6 @@ public class ChannelSettingsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String normalize(String value) {
|
private String normalize(String value) {
|
||||||
return value == null ? null : value.toLowerCase(Locale.ROOT);
|
return StringNormalizer.toLowerCaseRoot(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package dev.kruhlmann.imgfloat.util;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shared string normalization utilities. Centralises {@link Locale#ROOT} lowercase
|
||||||
|
* conversions so that individual classes do not duplicate the same one-liner.
|
||||||
|
*/
|
||||||
|
public final class StringNormalizer {
|
||||||
|
|
||||||
|
private StringNormalizer() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code value.toLowerCase(Locale.ROOT)}, or {@code null} if {@code value} is {@code null}.
|
||||||
|
*/
|
||||||
|
public static String toLowerCaseRoot(String value) {
|
||||||
|
return value == null ? null : value.toLowerCase(Locale.ROOT);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user