diff --git a/app/common/src/main/java/stirling/software/common/configuration/ApplicationPropertiesConfigOverlay.java b/app/common/src/main/java/stirling/software/common/configuration/ApplicationPropertiesConfigOverlay.java
new file mode 100644
index 0000000000..bb6f08a016
--- /dev/null
+++ b/app/common/src/main/java/stirling/software/common/configuration/ApplicationPropertiesConfigOverlay.java
@@ -0,0 +1,82 @@
+package stirling.software.common.configuration;
+
+import org.eclipse.microprofile.config.Config;
+import org.eclipse.microprofile.config.ConfigProvider;
+
+import io.quarkus.runtime.StartupEvent;
+
+import jakarta.annotation.Priority;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.enterprise.event.Observes;
+import jakarta.inject.Inject;
+import jakarta.interceptor.Interceptor;
+
+import lombok.extern.slf4j.Slf4j;
+
+import stirling.software.common.model.ApplicationProperties;
+
+/**
+ * Overlays MicroProfile/Quarkus config (env vars, application.properties) onto {@link
+ * ApplicationProperties} at startup.
+ *
+ *
The Spring {@code @ConfigurationProperties} binding that populated {@code
+ * ApplicationProperties} from {@code settings.yml} + env was never migrated, so the POJO otherwise
+ * carries only its Java defaults (this is the root cause behind the {@code maxDPI=0} / {@code
+ * loginAttemptCount=0} class of bugs and ignored {@code SECURITY_*}/{@code STORAGE_*} env vars).
+ * SmallRye maps an env var like {@code SECURITY_ENABLELOGIN} to the property {@code
+ * security.enableLogin}, so each key below is pulled from config and applied in place on the shared
+ * bean (nested objects are referenced by the {@code AppConfig} producers, so mutating them
+ * propagates everywhere).
+ *
+ *
Runs before {@link stirling.software.proprietary.security.InitialSecuritySetup} (low
+ * {@code @Priority}) because that startup step reads {@code customGlobalAPIKey} and {@code
+ * enableLogin}.
+ *
+ *
TODO: this is a focused subset (auth/storage/SSO). A complete migration would bind every
+ * ApplicationProperties field generically (e.g. via {@code @ConfigMapping} or a reflective overlay)
+ * and also read {@code settings.yml}.
+ */
+@Slf4j
+@ApplicationScoped
+public class ApplicationPropertiesConfigOverlay {
+
+ @Inject ApplicationProperties applicationProperties;
+
+ void onStart(@Observes @Priority(Interceptor.Priority.APPLICATION) StartupEvent event) {
+ Config config = ConfigProvider.getConfig();
+ ApplicationProperties.Security security = applicationProperties.getSecurity();
+
+ applyBoolean(config, "security.enableLogin", security::setEnableLogin);
+ applyString(config, "security.loginMethod", security::setLoginMethod);
+ applyString(config, "security.customGlobalAPIKey", security::setCustomGlobalAPIKey);
+ applyBoolean(config, "storage.enabled", applicationProperties.getStorage()::setEnabled);
+
+ // SSO toggles - the detailed provider config is bound by the OIDC/SAML wiring.
+ if (security.getSaml2() != null) {
+ applyBoolean(config, "security.saml2.enabled", security.getSaml2()::setEnabled);
+ }
+ if (security.getOauth2() != null) {
+ applyBoolean(config, "security.oauth2.enabled", security.getOauth2()::setEnabled);
+ }
+ }
+
+ private void applyBoolean(
+ Config config, String key, java.util.function.Consumer setter) {
+ config.getOptionalValue(key, Boolean.class)
+ .ifPresent(
+ value -> {
+ setter.accept(value);
+ log.info("Applied config override {}={}", key, value);
+ });
+ }
+
+ private void applyString(
+ Config config, String key, java.util.function.Consumer setter) {
+ config.getOptionalValue(key, String.class)
+ .ifPresent(
+ value -> {
+ setter.accept(value);
+ log.info("Applied config override {}={}", key, value);
+ });
+ }
+}
diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/identity/UserSecurityIdentityAugmentor.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/identity/UserSecurityIdentityAugmentor.java
new file mode 100644
index 0000000000..77c0179b0c
--- /dev/null
+++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/identity/UserSecurityIdentityAugmentor.java
@@ -0,0 +1,50 @@
+package stirling.software.proprietary.security.identity;
+
+import java.util.Optional;
+
+import io.quarkus.security.identity.AuthenticationRequestContext;
+import io.quarkus.security.identity.SecurityIdentity;
+import io.quarkus.security.identity.SecurityIdentityAugmentor;
+import io.quarkus.security.runtime.QuarkusSecurityIdentity;
+import io.smallrye.mutiny.Uni;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+
+import stirling.software.proprietary.security.model.User;
+import stirling.software.proprietary.security.service.UserService;
+
+/**
+ * Attaches the {@link User} entity as the {@link SecurityIdentity} principal for any authenticated
+ * request. Spring exposed the {@code User} directly via {@code Authentication#getPrincipal()} (it
+ * implemented {@code UserDetails}), so a lot of the code base does {@code principal instanceof
+ * User} (folders, file storage, sessions, audit, UserController). This augmentor restores that for
+ * the Quarkus auth paths (JWT Bearer, X-API-KEY, and later OIDC/SAML): it re-loads the user by name
+ * and rebuilds the identity with the {@code User} as principal, preserving the roles the {@link
+ * JwtTokenIdentityProvider} / {@link ApiKeyIdentityProvider} already assigned. This is the
+ * augmentor the {@code // TODO: Migration required} comments across the security/storage code ask
+ * for.
+ */
+@ApplicationScoped
+public class UserSecurityIdentityAugmentor implements SecurityIdentityAugmentor {
+
+ @Inject UserService userService;
+
+ @Override
+ public Uni augment(
+ SecurityIdentity identity, AuthenticationRequestContext context) {
+ if (identity == null || identity.isAnonymous() || identity.getPrincipal() instanceof User) {
+ return Uni.createFrom().item(identity);
+ }
+ return context.runBlocking(() -> withUserPrincipal(identity));
+ }
+
+ private SecurityIdentity withUserPrincipal(SecurityIdentity identity) {
+ String username = identity.getPrincipal().getName();
+ Optional userOpt = userService.findByUsernameIgnoreCase(username);
+ if (userOpt.isEmpty()) {
+ return identity;
+ }
+ return QuarkusSecurityIdentity.builder(identity).setPrincipal(userOpt.get()).build();
+ }
+}
diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/model/User.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/model/User.java
index a1bcecefa5..b30753dfff 100644
--- a/app/proprietary/src/main/java/stirling/software/proprietary/security/model/User.java
+++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/model/User.java
@@ -1,6 +1,7 @@
package stirling.software.proprietary.security.model;
import java.io.Serializable;
+import java.security.Principal;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.HashSet;
@@ -40,10 +41,19 @@ import stirling.software.proprietary.model.Team;
// entity. The Lombok getters still expose getUsername()/getPassword()/getAuthorities()/
// isEnabled() so that adapter can read them directly. isEnabled() override below is retained
// as plain business logic (null-safe enabled flag).
-public class User implements Serializable {
+public class User implements Serializable, Principal {
private static final long serialVersionUID = 1L;
+ // Principal#getName - lets the User entity itself be the Quarkus SecurityIdentity principal, so
+ // the many `principal instanceof User` call sites (folders, file storage, sessions, audit) keep
+ // working. @JsonIgnore so it does not add a "name" field to serialized User JSON.
+ @Override
+ @JsonIgnore
+ public String getName() {
+ return username;
+ }
+
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "user_id")
diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/service/UserService.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/service/UserService.java
index fe14ef9c99..8ce6a540b3 100644
--- a/app/proprietary/src/main/java/stirling/software/proprietary/security/service/UserService.java
+++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/service/UserService.java
@@ -372,6 +372,7 @@ public class UserService implements UserServiceInterface {
return user;
}
+ @Transactional
public Optional findByUsernameIgnoreCase(String username) {
return userRepository.findByUsernameIgnoreCase(username);
}