feat: push OIDC config to provisioned server for SSO login
All checks were successful
CI / build (push) Successful in 52s
CI / docker (push) Successful in 33s

After provisioning a server, pushes Logto Traditional Web App
credentials (client ID + secret) via the server's OIDC admin API.
This enables SSO: users authenticated via Logto can access the
server dashboard without a separate login.

Reads tradAppSecret from bootstrap JSON via LogtoConfig.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-10 08:37:01 +02:00
parent 3efae43879
commit 7ee2985626
2 changed files with 29 additions and 1 deletions

View File

@@ -29,12 +29,13 @@ public class LogtoConfig {
private String serverEndpoint; private String serverEndpoint;
private String tradAppId; private String tradAppId;
private String tradAppSecret;
@PostConstruct @PostConstruct
public void init() { public void init() {
if (isConfigured()) return; if (isConfigured()) return;
// Fall back to bootstrap file for M2M credentials + trad app ID // Fall back to bootstrap file for M2M credentials + trad app
try { try {
File file = new File(BOOTSTRAP_FILE); File file = new File(BOOTSTRAP_FILE);
if (file.exists()) { if (file.exists()) {
@@ -48,6 +49,9 @@ public class LogtoConfig {
if (node.has("tradAppId")) { if (node.has("tradAppId")) {
tradAppId = node.get("tradAppId").asText(); tradAppId = node.get("tradAppId").asText();
} }
if (node.has("tradAppSecret")) {
tradAppSecret = node.get("tradAppSecret").asText();
}
log.info("Loaded M2M credentials from bootstrap file"); log.info("Loaded M2M credentials from bootstrap file");
} }
} catch (Exception e) { } catch (Exception e) {
@@ -60,6 +64,7 @@ public class LogtoConfig {
public String getM2mClientSecret() { return m2mClientSecret; } public String getM2mClientSecret() { return m2mClientSecret; }
public String getServerEndpoint() { return serverEndpoint; } public String getServerEndpoint() { return serverEndpoint; }
public String getTradAppId() { return tradAppId; } public String getTradAppId() { return tradAppId; }
public String getTradAppSecret() { return tradAppSecret; }
public boolean isConfigured() { public boolean isConfigured() {
return logtoEndpoint != null && !logtoEndpoint.isEmpty() return logtoEndpoint != null && !logtoEndpoint.isEmpty()

View File

@@ -25,6 +25,7 @@ import org.springframework.transaction.annotation.Transactional;
import java.time.Duration; import java.time.Duration;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
@@ -127,6 +128,28 @@ public class VendorTenantService {
} catch (Exception e) { } catch (Exception e) {
log.warn("License push failed for tenant {}: {}", tenant.getSlug(), e.getMessage()); log.warn("License push failed for tenant {}: {}", tenant.getSlug(), e.getMessage());
} }
// Configure OIDC on the provisioned server (SSO via Logto)
if (logtoConfig.getTradAppId() != null && logtoConfig.getTradAppSecret() != null) {
try {
String publicBase = provisioningProps.publicProtocol() + "://" + provisioningProps.publicHost();
serverApiClient.pushOidcConfig(result.serverEndpoint(), Map.of(
"enabled", true,
"issuerUri", publicBase + "/oidc",
"clientId", logtoConfig.getTradAppId(),
"clientSecret", logtoConfig.getTradAppSecret(),
"autoSignup", true,
"defaultRoles", List.of("VIEWER"),
"displayNameClaim", "name",
"rolesClaim", "roles",
"audience", "https://api.cameleer.local",
"additionalScopes", List.of()
));
log.info("Pushed OIDC config to server for tenant {}", tenant.getSlug());
} catch (Exception e) {
log.warn("OIDC config push failed for tenant {}: {}", tenant.getSlug(), e.getMessage());
}
}
} else { } else {
tenant.setProvisionError(result.error()); tenant.setProvisionError(result.error());
tenant.setStatus(TenantStatus.PROVISIONING); tenant.setStatus(TenantStatus.PROVISIONING);