Skip to content

Commit 834823a

Browse files
authored
Merge branch 'master' into fern-bot/2026-04-08T09-53Z
2 parents 354a583 + 47acb61 commit 834823a

File tree

8 files changed

+443
-3
lines changed

8 files changed

+443
-3
lines changed

.fernignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ src/main/java/com/auth0/client/ClientCredentialsTokenProvider.java
4949
src/main/java/com/auth0/client/ManagementApiWithTokenProvider.java
5050
src/main/java/com/auth0/client/TokenProvider.java
5151
src/main/java/com/auth0/client/interceptors/
52+
src/main/java/com/auth0/client/mgmt/core/CustomDomainInterceptor.java
53+
src/main/java/com/auth0/client/mgmt/CustomDomainHeader.java
5254

5355
# Custom OAuth client credentials support
5456
src/main/java/com/auth0/client/mgmt/core/RequestOptions.java
@@ -63,6 +65,8 @@ src/main/java/com/auth0/client/mgmt/ManagementApiBuilder.java
6365
src/test/java/com/auth0/client/mgmt/DynamicTokenManagementTest.java
6466
src/test/java/com/auth0/client/mgmt/OAuthTokenSupplierTest.java
6567
src/test/java/com/auth0/client/mgmt/ManagementApiBuilderTest.java
68+
src/test/java/com/auth0/client/mgmt/CustomDomainInterceptorTest.java
69+
src/test/java/com/auth0/client/mgmt/CustomDomainHeaderIntegrationTest.java
6670

6771
# Configuration files from auth0-real
6872
.codecov.yml

build.gradle

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ logger.lifecycle("Using version ${version} for ${name} group $group")
2222
dependencies {
2323
// Core dependencies
2424
api 'com.squareup.okhttp3:okhttp:5.2.1'
25-
api 'com.fasterxml.jackson.core:jackson-databind:2.18.6'
26-
api 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.18.6'
27-
api 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.18.6'
25+
api 'com.fasterxml.jackson.core:jackson-databind:2.21.2'
26+
api 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.21.2'
27+
api 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.21.2'
2828

2929
// Dependencies for legacy management code from auth0-real
3030
implementation 'com.squareup.okhttp3:logging-interceptor:5.2.1'
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package com.auth0.client.mgmt;
2+
3+
import com.auth0.client.mgmt.core.CustomDomainInterceptor;
4+
import com.auth0.client.mgmt.core.RequestOptions;
5+
6+
/**
7+
* Convenience helper for creating per-request custom domain overrides.
8+
*
9+
* <p>Use this to override the global custom domain for a specific API call.
10+
* The header is only sent to whitelisted endpoints that generate user-facing links.
11+
*
12+
* <p>Example usage:
13+
* <pre>{@code
14+
* // Override the custom domain for a specific request
15+
* client.users().list(CustomDomainHeader.of("other.mycompany.com"));
16+
*
17+
* // Use with tickets
18+
* client.tickets().createEmailVerification(request, CustomDomainHeader.of("login.mycompany.com"));
19+
* }</pre>
20+
*
21+
* @see ManagementApiBuilder#customDomain(String) for setting a global custom domain
22+
*/
23+
public final class CustomDomainHeader {
24+
25+
private CustomDomainHeader() {}
26+
27+
/**
28+
* Creates a {@link RequestOptions} instance with the Auth0-Custom-Domain header set.
29+
*
30+
* @param domain The custom domain to use (e.g., "login.mycompany.com")
31+
* @return RequestOptions with the custom domain header configured
32+
*/
33+
public static RequestOptions of(String domain) {
34+
return RequestOptions.builder()
35+
.addHeader(CustomDomainInterceptor.HEADER_NAME, domain)
36+
.build();
37+
}
38+
}

src/main/java/com/auth0/client/mgmt/ManagementApiBuilder.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package com.auth0.client.mgmt;
55

66
import com.auth0.client.mgmt.core.ClientOptions;
7+
import com.auth0.client.mgmt.core.CustomDomainInterceptor;
78
import com.auth0.client.mgmt.core.Environment;
89
import com.auth0.client.mgmt.core.OAuthTokenSupplier;
910
import java.util.HashMap;
@@ -25,6 +26,8 @@ public class ManagementApiBuilder {
2526

2627
private OkHttpClient httpClient;
2728

29+
private String customDomain = null;
30+
2831
// Domain-based initialization fields
2932
private String domain = null;
3033
private String clientId = null;
@@ -107,6 +110,40 @@ public ManagementApiBuilder audience(String audience) {
107110
return this;
108111
}
109112

113+
/**
114+
* Sets the custom domain for the Auth0-Custom-Domain header.
115+
* When configured, the header is automatically sent on whitelisted API endpoints
116+
* that generate user-facing links (email verification, password change, invitations, etc.).
117+
*
118+
* <p>The header is only sent to whitelisted endpoints:
119+
* <ul>
120+
* <li>{@code /jobs/verification-email}</li>
121+
* <li>{@code /tickets/email-verification}</li>
122+
* <li>{@code /tickets/password-change}</li>
123+
* <li>{@code /organizations/{id}/invitations}</li>
124+
* <li>{@code /users} and {@code /users/{id}}</li>
125+
* <li>{@code /guardian/enrollments/ticket}</li>
126+
* <li>{@code /self-service-profiles/{id}/sso-ticket}</li>
127+
* </ul>
128+
*
129+
* <p>Example:
130+
* <pre>{@code
131+
* ManagementApi client = ManagementApi.builder()
132+
* .domain("your-tenant.auth0.com")
133+
* .token("YOUR_TOKEN")
134+
* .customDomain("login.mycompany.com")
135+
* .build();
136+
* }</pre>
137+
*
138+
* @param customDomain The custom domain (e.g., "login.mycompany.com")
139+
* @return This builder for method chaining
140+
* @see CustomDomainHeader#of(String) for per-request custom domain overrides
141+
*/
142+
public ManagementApiBuilder customDomain(String customDomain) {
143+
this.customDomain = customDomain;
144+
return this;
145+
}
146+
110147
/**
111148
* Sets the timeout (in seconds) for the client. Defaults to 60 seconds.
112149
*/
@@ -154,6 +191,10 @@ protected ClientOptions buildClientOptions() {
154191
for (Map.Entry<String, String> header : this.customHeaders.entrySet()) {
155192
builder.addHeader(header.getKey(), header.getValue());
156193
}
194+
if (this.customDomain != null) {
195+
builder.addHeader(CustomDomainInterceptor.HEADER_NAME, this.customDomain);
196+
builder.addInterceptor(new CustomDomainInterceptor());
197+
}
157198
setAdditional(builder);
158199
return builder.build();
159200
}

src/main/java/com/auth0/client/mgmt/core/ClientOptions.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@
44
package com.auth0.client.mgmt.core;
55

66
import com.auth0.net.Telemetry;
7+
import java.util.ArrayList;
78
import java.util.HashMap;
9+
import java.util.List;
810
import java.util.Map;
911
import java.util.Optional;
1012
import java.util.concurrent.TimeUnit;
1113
import java.util.function.Supplier;
14+
import okhttp3.Interceptor;
1215
import okhttp3.OkHttpClient;
1316

1417
public final class ClientOptions {
@@ -100,6 +103,8 @@ public static class Builder {
100103

101104
private final Map<String, Supplier<String>> headerSuppliers = new HashMap<>();
102105

106+
private final List<Interceptor> interceptors = new ArrayList<>();
107+
103108
private int maxRetries = 2;
104109

105110
private Optional<Integer> timeout = Optional.empty();
@@ -150,6 +155,14 @@ public Builder httpClient(OkHttpClient httpClient) {
150155
return this;
151156
}
152157

158+
/**
159+
* Add an OkHttp interceptor to the client.
160+
*/
161+
public Builder addInterceptor(Interceptor interceptor) {
162+
this.interceptors.add(interceptor);
163+
return this;
164+
}
165+
153166
public ClientOptions build() {
154167
OkHttpClient.Builder httpClientBuilder =
155168
this.httpClient != null ? this.httpClient.newBuilder() : new OkHttpClient.Builder();
@@ -169,6 +182,10 @@ public ClientOptions build() {
169182
.addInterceptor(new RetryInterceptor(this.maxRetries));
170183
}
171184

185+
for (Interceptor interceptor : this.interceptors) {
186+
httpClientBuilder.addInterceptor(interceptor);
187+
}
188+
172189
this.httpClient = httpClientBuilder.build();
173190
this.timeout = Optional.of(httpClient.callTimeoutMillis() / 1000);
174191

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package com.auth0.client.mgmt.core;
2+
3+
import java.io.IOException;
4+
import java.util.Arrays;
5+
import java.util.List;
6+
import java.util.regex.Pattern;
7+
import okhttp3.Interceptor;
8+
import okhttp3.Request;
9+
import okhttp3.Response;
10+
11+
/**
12+
* OkHttp interceptor that enforces the Auth0-Custom-Domain header whitelist.
13+
*
14+
* <p>The Auth0-Custom-Domain header is only sent to specific API endpoints that generate
15+
* user-facing links (email verification, password change, invitations, etc.). This interceptor
16+
* strips the header from requests to non-whitelisted paths.
17+
*
18+
* <p>Whitelisted endpoints:
19+
* <ul>
20+
* <li>{@code /jobs/verification-email}</li>
21+
* <li>{@code /tickets/email-verification}</li>
22+
* <li>{@code /tickets/password-change}</li>
23+
* <li>{@code /organizations/{id}/invitations}</li>
24+
* <li>{@code /users} and {@code /users/{id}}</li>
25+
* <li>{@code /guardian/enrollments/ticket}</li>
26+
* <li>{@code /self-service-profiles/{id}/sso-ticket}</li>
27+
* </ul>
28+
*/
29+
public class CustomDomainInterceptor implements Interceptor {
30+
31+
public static final String HEADER_NAME = "Auth0-Custom-Domain";
32+
33+
private static final List<Pattern> WHITELISTED_PATHS = Arrays.asList(
34+
Pattern.compile(".*/jobs/verification-email$"),
35+
Pattern.compile(".*/tickets/email-verification$"),
36+
Pattern.compile(".*/tickets/password-change$"),
37+
Pattern.compile(".*/organizations/[^/]+/invitations(/[^/]+)?$"),
38+
Pattern.compile(".*/users(/[^/]+)?$"),
39+
Pattern.compile(".*/guardian/enrollments/ticket$"),
40+
Pattern.compile(".*/self-service-profiles/[^/]+/sso-ticket(/[^/]+/revoke)?$"));
41+
42+
@Override
43+
public Response intercept(Chain chain) throws IOException {
44+
Request request = chain.request();
45+
46+
if (request.header(HEADER_NAME) != null && !isWhitelisted(request.url().encodedPath())) {
47+
request = request.newBuilder().removeHeader(HEADER_NAME).build();
48+
}
49+
50+
return chain.proceed(request);
51+
}
52+
53+
public static boolean isWhitelisted(String path) {
54+
for (Pattern pattern : WHITELISTED_PATHS) {
55+
if (pattern.matcher(path).matches()) {
56+
return true;
57+
}
58+
}
59+
return false;
60+
}
61+
}

0 commit comments

Comments
 (0)