Merge pull request 'feature/auth-harmonization' (#155) from feature/auth-harmonization into main
Reviewed-on: #155
This commit was merged in pull request #155.
This commit is contained in:
@@ -84,6 +84,12 @@ jobs:
|
||||
- name: Build and Test
|
||||
run: mvn clean verify -DskipITs -U --batch-mode
|
||||
|
||||
- name: Deploy minter to Maven registry
|
||||
if: github.event_name == 'push'
|
||||
run: mvn deploy -DskipTests -DskipITs --batch-mode -pl .,cameleer-server-core,cameleer-license-minter
|
||||
env:
|
||||
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
||||
|
||||
docker:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.cameleer.server.app.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
@Schema(description = "Authentication capabilities reported to the SPA so it can render the login page deterministically")
|
||||
public record AuthCapabilitiesResponse(
|
||||
@Schema(description = "OIDC interactive login capability") Oidc oidc,
|
||||
@Schema(description = "Local username/password account capability") LocalAccounts localAccounts
|
||||
) {
|
||||
|
||||
@Schema(description = "OIDC interactive login")
|
||||
public record Oidc(
|
||||
@Schema(description = "Whether OIDC is configured AND enabled") boolean enabled,
|
||||
@Schema(description = "Best-effort display label, e.g. \"Logto\", \"Keycloak\", \"Single Sign-On\"") @NotNull String providerName,
|
||||
@Schema(description = "When true, OIDC is the canonical entry point and the SPA hides the local form unless ?local is set") boolean primary
|
||||
) {}
|
||||
|
||||
@Schema(description = "Local username/password accounts")
|
||||
public record LocalAccounts(
|
||||
@Schema(description = "Whether the local form is reachable at all") boolean enabled,
|
||||
@Schema(description = "When true, the SPA gates the local form behind ?local with an admin-recovery banner") boolean adminRecoveryOnly
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.cameleer.server.app.security;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* Pure utility — derives a display label for an OIDC provider from its issuer URI.
|
||||
* Used by {@link AuthCapabilitiesController} so the SPA can render
|
||||
* "Sign in with {providerName}" on the login page.
|
||||
*
|
||||
* <p>Pattern-match only — never network-discover. If the issuer doesn't match a
|
||||
* known vendor pattern, we return the generic "Single Sign-On" label rather than
|
||||
* leaking hostnames into the UI.
|
||||
*/
|
||||
public final class OidcProviderNameDeriver {
|
||||
|
||||
private static final String GENERIC = "Single Sign-On";
|
||||
|
||||
private OidcProviderNameDeriver() {}
|
||||
|
||||
public static String deriveName(String issuerUri) {
|
||||
if (issuerUri == null || issuerUri.isBlank()) {
|
||||
return GENERIC;
|
||||
}
|
||||
String host;
|
||||
try {
|
||||
URI uri = URI.create(issuerUri.trim());
|
||||
host = uri.getHost();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return GENERIC;
|
||||
}
|
||||
if (host == null || host.isBlank()) {
|
||||
return GENERIC;
|
||||
}
|
||||
String h = host.toLowerCase();
|
||||
if (h.contains("logto")) return "Logto";
|
||||
if (h.contains("keycloak")) return "Keycloak";
|
||||
if (h.endsWith("auth0.com")) return "Auth0";
|
||||
if (h.endsWith("okta.com") || h.endsWith("oktapreview.com")) return "Okta";
|
||||
return GENERIC;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.cameleer.server.app.security;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/** Unit tests for {@link OidcProviderNameDeriver}. No Spring context. */
|
||||
class OidcProviderNameDeriverTest {
|
||||
|
||||
@Test
|
||||
void logtoIssuer_returnsLogto() {
|
||||
assertThat(OidcProviderNameDeriver.deriveName("https://auth.logto.example/")).isEqualTo("Logto");
|
||||
assertThat(OidcProviderNameDeriver.deriveName("https://logto.cameleer.local")).isEqualTo("Logto");
|
||||
}
|
||||
|
||||
@Test
|
||||
void keycloakIssuer_returnsKeycloak() {
|
||||
assertThat(OidcProviderNameDeriver.deriveName("https://keycloak.example/realms/cameleer")).isEqualTo("Keycloak");
|
||||
}
|
||||
|
||||
@Test
|
||||
void auth0Issuer_returnsAuth0() {
|
||||
assertThat(OidcProviderNameDeriver.deriveName("https://example.auth0.com/")).isEqualTo("Auth0");
|
||||
}
|
||||
|
||||
@Test
|
||||
void oktaIssuer_returnsOkta() {
|
||||
assertThat(OidcProviderNameDeriver.deriveName("https://dev-123.okta.com/")).isEqualTo("Okta");
|
||||
assertThat(OidcProviderNameDeriver.deriveName("https://login.oktapreview.com/")).isEqualTo("Okta");
|
||||
}
|
||||
|
||||
@Test
|
||||
void unknownIssuer_returnsGenericLabel() {
|
||||
assertThat(OidcProviderNameDeriver.deriveName("https://idp.example.com/")).isEqualTo("Single Sign-On");
|
||||
}
|
||||
|
||||
@Test
|
||||
void blankOrNullIssuer_returnsGenericLabel() {
|
||||
assertThat(OidcProviderNameDeriver.deriveName("")).isEqualTo("Single Sign-On");
|
||||
assertThat(OidcProviderNameDeriver.deriveName(null)).isEqualTo("Single Sign-On");
|
||||
assertThat(OidcProviderNameDeriver.deriveName(" ")).isEqualTo("Single Sign-On");
|
||||
}
|
||||
|
||||
@Test
|
||||
void malformedUri_returnsGenericLabel() {
|
||||
assertThat(OidcProviderNameDeriver.deriveName("not a url")).isEqualTo("Single Sign-On");
|
||||
}
|
||||
|
||||
@Test
|
||||
void caseInsensitiveMatching() {
|
||||
assertThat(OidcProviderNameDeriver.deriveName("https://AUTH.LOGTO.EXAMPLE/")).isEqualTo("Logto");
|
||||
}
|
||||
}
|
||||
1167
docs/superpowers/plans/2026-04-26-auth-harmonization.md
Normal file
1167
docs/superpowers/plans/2026-04-26-auth-harmonization.md
Normal file
File diff suppressed because it is too large
Load Diff
11
pom.xml
11
pom.xml
@@ -65,6 +65,17 @@
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<distributionManagement>
|
||||
<repository>
|
||||
<id>gitea</id>
|
||||
<url>https://gitea.siegeln.net/api/packages/cameleer/maven</url>
|
||||
</repository>
|
||||
<snapshotRepository>
|
||||
<id>gitea</id>
|
||||
<url>https://gitea.siegeln.net/api/packages/cameleer/maven</url>
|
||||
</snapshotRepository>
|
||||
</distributionManagement>
|
||||
|
||||
<build>
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
|
||||
Reference in New Issue
Block a user