Add OIDC admin config page with auto-signup toggle
Some checks failed
CI / build (push) Successful in 1m12s
CI / docker (push) Successful in 50s
CI / deploy (push) Failing after 2m10s

Backend: add autoSignup field to OidcConfig, ClickHouse schema, repository,
and admin controller. Gate OIDC login when auto-signup is disabled and user
is not pre-created (returns 403).

Frontend: add OIDC admin page with full CRUD (save/test/delete), role-gated
Admin nav link parsed from JWT, and matching design system styles.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-03-14 13:56:02 +01:00
parent 377908cc61
commit 0c47ac9b1a
12 changed files with 802 additions and 14 deletions

View File

@@ -80,7 +80,8 @@ public class OidcConfigAdminController {
request.clientId() != null ? request.clientId() : "",
clientSecret,
request.rolesClaim() != null ? request.rolesClaim() : "realm_access.roles",
request.defaultRoles() != null ? request.defaultRoles() : List.of("VIEWER")
request.defaultRoles() != null ? request.defaultRoles() : List.of("VIEWER"),
request.autoSignup()
);
configRepository.save(config);
@@ -134,6 +135,7 @@ public class OidcConfigAdminController {
map.put("clientSecretSet", !config.clientSecret().isBlank());
map.put("rolesClaim", config.rolesClaim());
map.put("defaultRoles", config.defaultRoles());
map.put("autoSignup", config.autoSignup());
return map;
}
@@ -143,6 +145,7 @@ public class OidcConfigAdminController {
String clientId,
String clientSecret,
String rolesClaim,
List<String> defaultRoles
List<String> defaultRoles,
boolean autoSignup
) {}
}

View File

@@ -90,8 +90,15 @@ public class OidcAuthController {
String issuerHost = URI.create(config.get().issuerUri()).getHost();
String provider = "oidc:" + issuerHost;
// Check auto-signup gate: if disabled, user must already exist
Optional<UserInfo> existingUser = userRepository.findById(userId);
if (!config.get().autoSignup() && existingUser.isEmpty()) {
return ResponseEntity.status(403)
.body(Map.of("message", "Account not provisioned. Contact your administrator."));
}
// Resolve roles: DB override > OIDC claim > default
List<String> roles = resolveRoles(userId, oidcUser.roles(), config.get());
List<String> roles = resolveRoles(existingUser, oidcUser.roles(), config.get());
userRepository.upsert(new UserInfo(
userId, provider, oidcUser.email(), oidcUser.name(), roles, Instant.now()));
@@ -110,8 +117,7 @@ public class OidcAuthController {
}
}
private List<String> resolveRoles(String userId, List<String> oidcRoles, OidcConfig config) {
Optional<UserInfo> existing = userRepository.findById(userId);
private List<String> resolveRoles(Optional<UserInfo> existing, List<String> oidcRoles, OidcConfig config) {
if (existing.isPresent() && !existing.get().roles().isEmpty()) {
return existing.get().roles();
}

View File

@@ -73,7 +73,8 @@ public class SecurityBeanConfig {
envOidc.getClientId(),
envOidc.getClientSecret() != null ? envOidc.getClientSecret() : "",
envOidc.getRolesClaim(),
envOidc.getDefaultRoles()
envOidc.getDefaultRoles(),
true
);
configRepository.save(config);
log.info("OIDC config seeded from environment variables: issuer={}", envOidc.getIssuerUri());

View File

@@ -27,7 +27,7 @@ public class ClickHouseOidcConfigRepository implements OidcConfigRepository {
@Override
public Optional<OidcConfig> find() {
List<OidcConfig> results = jdbc.query(
"SELECT enabled, issuer_uri, client_id, client_secret, roles_claim, default_roles "
"SELECT enabled, issuer_uri, client_id, client_secret, roles_claim, default_roles, auto_signup "
+ "FROM oidc_config FINAL WHERE config_id = 'default'",
this::mapRow
);
@@ -37,14 +37,15 @@ public class ClickHouseOidcConfigRepository implements OidcConfigRepository {
@Override
public void save(OidcConfig config) {
jdbc.update(
"INSERT INTO oidc_config (config_id, enabled, issuer_uri, client_id, client_secret, roles_claim, default_roles, updated_at) "
+ "VALUES ('default', ?, ?, ?, ?, ?, ?, now64(3, 'UTC'))",
"INSERT INTO oidc_config (config_id, enabled, issuer_uri, client_id, client_secret, roles_claim, default_roles, auto_signup, updated_at) "
+ "VALUES ('default', ?, ?, ?, ?, ?, ?, ?, now64(3, 'UTC'))",
config.enabled(),
config.issuerUri(),
config.clientId(),
config.clientSecret(),
config.rolesClaim(),
config.defaultRoles().toArray(new String[0])
config.defaultRoles().toArray(new String[0]),
config.autoSignup()
);
}
@@ -61,7 +62,8 @@ public class ClickHouseOidcConfigRepository implements OidcConfigRepository {
rs.getString("client_id"),
rs.getString("client_secret"),
rs.getString("roles_claim"),
Arrays.asList(rolesArray)
Arrays.asList(rolesArray),
rs.getBoolean("auto_signup")
);
}
}