From a2ec631a859894753b36457115059a0b67e1f71c Mon Sep 17 00:00:00 2001 From: Joao Moreira Date: Thu, 24 Jul 2025 14:25:41 -0400 Subject: [PATCH 1/8] Create new method to add Email based OOB authenticator and tests --- .../java/com/auth0/client/auth/AuthAPI.java | 48 +++++++++++++++++-- .../com/auth0/client/auth/AuthAPITest.java | 41 ++++++++++++++-- 2 files changed, 81 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/auth0/client/auth/AuthAPI.java b/src/main/java/com/auth0/client/auth/AuthAPI.java index fefe5abb5..16d68d9e6 100644 --- a/src/main/java/com/auth0/client/auth/AuthAPI.java +++ b/src/main/java/com/auth0/client/auth/AuthAPI.java @@ -1396,7 +1396,7 @@ public Request addOtpAuthenticator(String mfaToken) { } /** - * Associates or adds a new OOB authenticator for multi-factor authentication (MFA). + * Associates or adds a new phone based OOB authenticator for multi-factor authentication (MFA). * Confidential clients (Regular Web Apps) must have a client secret configured on this {@code AuthAPI} instance. *
      * {@code
@@ -1414,9 +1414,9 @@ public Request addOtpAuthenticator(String mfaToken) {
      * @param oobChannels The type of OOB channels supported by the client. Must not be null.
      * @param phoneNumber The phone number for "sms" or "voice" channels. May be null if not using "sms" or "voice".
      * @return a Request to execute.
-     * @see Add an Authenticator API documentation
+     * @see Enroll with SMS or voice
      */
-    public Request addOobAuthenticator(String mfaToken, List oobChannels, String phoneNumber) {
+    public Request addPhoneOobAuthenticator(String mfaToken, List oobChannels, String phoneNumber) {
         Asserts.assertNotNull(mfaToken, "mfa token");
         Asserts.assertNotNull(oobChannels, "OOB channels");
 
@@ -1441,6 +1441,48 @@ public Request addOobAuthenticator(String mfaToken, Listmust have a client secret configured on this {@code AuthAPI} instance.
+     * 
+     * {@code
+     * try {
+     *      CreatedOobResponse result = authAPI.addOobAuthenticator("the-mfa-token", "email-address")
+     *          .execute()
+     *          .getBody();
+     * } catch (Auth0Exception e) {
+     *      //Something happened
+     * }
+     * }
+     * 
+ * + * @param mfaToken The token received from mfa_required error. Must not be null. + * @param emailAddress The email address for "email" channel. + * @return a Request to execute. + * @see Enroll with email + */ + public Request addEmailOobAuthenticator(String mfaToken, String emailAddress) { + Asserts.assertNotNull(mfaToken, "mfa token"); + + String url = baseUrl + .newBuilder() + .addPathSegment("mfa") + .addPathSegment("associate") + .build() + .toString(); + + BaseRequest request = new BaseRequest<>(client, null, url, HttpMethod.POST, new TypeReference() { + }); + + request.addParameter("authenticator_types", Collections.singletonList("oob")); + request.addParameter("oob_channels", Collections.singletonList("email")); + request.addParameter(KEY_CLIENT_ID, clientId); + request.addParameter("email", emailAddress); + addClientAuthentication(request, false); + request.addHeader("Authorization", "Bearer " + mfaToken); + return request; + } + /** * Returns a list of authenticators associated with your application. diff --git a/src/test/java/com/auth0/client/auth/AuthAPITest.java b/src/test/java/com/auth0/client/auth/AuthAPITest.java index 800f4c202..fa0d7b7c0 100644 --- a/src/test/java/com/auth0/client/auth/AuthAPITest.java +++ b/src/test/java/com/auth0/client/auth/AuthAPITest.java @@ -1542,22 +1542,22 @@ public void addOtpAuthenticatorRequest() throws Exception { } @Test - public void addOobAuthenticatorThrowsWhenTokenNull() { + public void addPhoneOobAuthenticatorThrowsWhenTokenNull() { verifyThrows(IllegalArgumentException.class, - () -> api.addOobAuthenticator(null, Collections.singletonList("otp"), null), + () -> api.addPhoneOobAuthenticator(null, Collections.singletonList("otp"), null), "'mfa token' cannot be null!"); } @Test - public void addOobAuthenticatorThrowsWhenChannelsNull() { + public void addPhoneOobAuthenticatorThrowsWhenChannelsNull() { verifyThrows(IllegalArgumentException.class, - () -> api.addOobAuthenticator("mfaToken", null, null), + () -> api.addPhoneOobAuthenticator("mfaToken", null, null), "'OOB channels' cannot be null!"); } @Test public void addOobAuthenticatorRequest() throws Exception { - Request request = api.addOobAuthenticator("mfaToken", Collections.singletonList("sms"), "phone-number"); + Request request = api.addPhoneOobAuthenticator("mfaToken", Collections.singletonList("sms"), "phone-number"); server.jsonResponse(AUTH_OOB_AUTHENTICATOR_RESPONSE, 200); CreatedOobResponse response = request.execute().getBody(); @@ -1579,6 +1579,37 @@ public void addOobAuthenticatorRequest() throws Exception { assertThat(response.getRecoveryCodes(), notNullValue()); } + @Test + public void addEmailOobAuthenticatorThrowsWhenTokenNull() { + verifyThrows(IllegalArgumentException.class, + () -> api.addEmailOobAuthenticator(null, null), + "'mfa token' cannot be null!"); + } + + @Test + public void addEmailOobAuthenticatorRequest() throws Exception { + Request request = api.addEmailOobAuthenticator("mfaToken", "email-address"); + + server.jsonResponse(AUTH_OOB_AUTHENTICATOR_RESPONSE, 200); + CreatedOobResponse response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/mfa/associate")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body, hasEntry("authenticator_types", Collections.singletonList("oob"))); + assertThat(body, hasEntry("oob_channels", Collections.singletonList("email"))); + assertThat(body, hasEntry("email", "email-address")); + + assertThat(response, is(notNullValue())); + assertThat(response.getAuthenticatorType(), not(emptyOrNullString())); + assertThat(response.getOobChannel(), not(emptyOrNullString())); + assertThat(response.getOobCode(), not(emptyOrNullString())); + assertThat(response.getBarcodeUri(), not(emptyOrNullString())); + assertThat(response.getRecoveryCodes(), notNullValue()); + } + @Test public void listAuthenticatorsThrowsWhenTokenNull() { verifyThrows(IllegalArgumentException.class, From 8fe07a60c3757ac687efabb71d5400f477af009d Mon Sep 17 00:00:00 2001 From: Joao Moreira Date: Tue, 29 Jul 2025 13:02:40 -0400 Subject: [PATCH 2/8] Consolidate to a single method --- .../java/com/auth0/client/auth/AuthAPI.java | 58 +++++-------------- .../com/auth0/client/auth/AuthAPITest.java | 46 +++++---------- 2 files changed, 28 insertions(+), 76 deletions(-) diff --git a/src/main/java/com/auth0/client/auth/AuthAPI.java b/src/main/java/com/auth0/client/auth/AuthAPI.java index 16d68d9e6..03bb97d87 100644 --- a/src/main/java/com/auth0/client/auth/AuthAPI.java +++ b/src/main/java/com/auth0/client/auth/AuthAPI.java @@ -1396,7 +1396,7 @@ public Request addOtpAuthenticator(String mfaToken) { } /** - * Associates or adds a new phone based OOB authenticator for multi-factor authentication (MFA). + * Associates or adds a new OOB authenticator for multi-factor authentication (MFA). * Confidential clients (Regular Web Apps) must have a client secret configured on this {@code AuthAPI} instance. *
      * {@code
@@ -1413,12 +1413,19 @@ public Request addOtpAuthenticator(String mfaToken) {
      * @param mfaToken The token received from mfa_required error. Must not be null.
      * @param oobChannels The type of OOB channels supported by the client. Must not be null.
      * @param phoneNumber The phone number for "sms" or "voice" channels. May be null if not using "sms" or "voice".
+     * @param emailAddress The  email address for "email" channel. May be null if not using "email".
      * @return a Request to execute.
      * @see Enroll with SMS or voice
      */
-    public Request addPhoneOobAuthenticator(String mfaToken, List oobChannels, String phoneNumber) {
+    public Request addOobAuthenticator(String mfaToken, List oobChannels, String phoneNumber, String emailAddress) {
         Asserts.assertNotNull(mfaToken, "mfa token");
         Asserts.assertNotNull(oobChannels, "OOB channels");
+        if (oobChannels.contains("sms") || oobChannels.contains("voice")) {
+            Asserts.assertNotNull(phoneNumber, "phone number");
+        }
+        if (oobChannels.contains("email")) {
+            Asserts.assertNotNull(emailAddress, "email address");
+        }
 
         String url = baseUrl
             .newBuilder()
@@ -1433,57 +1440,18 @@ public Request addPhoneOobAuthenticator(String mfaToken, Lis
         request.addParameter("authenticator_types", Collections.singletonList("oob"));
         request.addParameter("oob_channels", oobChannels);
         request.addParameter(KEY_CLIENT_ID, clientId);
+
         if (phoneNumber != null) {
             request.addParameter("phone_number", phoneNumber);
         }
+        if (emailAddress != null) {
+            request.addParameter("email", emailAddress);
+        }
         addClientAuthentication(request, false);
         request.addHeader("Authorization", "Bearer " + mfaToken);
         return request;
     }
 
-    /**
-     * Associates or adds a new email based OOB authenticator for multi-factor authentication (MFA).
-     * Confidential clients (Regular Web Apps) must have a client secret configured on this {@code AuthAPI} instance.
-     * 
-     * {@code
-     * try {
-     *      CreatedOobResponse result = authAPI.addOobAuthenticator("the-mfa-token", "email-address")
-     *          .execute()
-     *          .getBody();
-     * } catch (Auth0Exception e) {
-     *      //Something happened
-     * }
-     * }
-     * 
- * - * @param mfaToken The token received from mfa_required error. Must not be null. - * @param emailAddress The email address for "email" channel. - * @return a Request to execute. - * @see Enroll with email - */ - public Request addEmailOobAuthenticator(String mfaToken, String emailAddress) { - Asserts.assertNotNull(mfaToken, "mfa token"); - - String url = baseUrl - .newBuilder() - .addPathSegment("mfa") - .addPathSegment("associate") - .build() - .toString(); - - BaseRequest request = new BaseRequest<>(client, null, url, HttpMethod.POST, new TypeReference() { - }); - - request.addParameter("authenticator_types", Collections.singletonList("oob")); - request.addParameter("oob_channels", Collections.singletonList("email")); - request.addParameter(KEY_CLIENT_ID, clientId); - request.addParameter("email", emailAddress); - addClientAuthentication(request, false); - request.addHeader("Authorization", "Bearer " + mfaToken); - return request; - } - - /** * Returns a list of authenticators associated with your application. *
diff --git a/src/test/java/com/auth0/client/auth/AuthAPITest.java b/src/test/java/com/auth0/client/auth/AuthAPITest.java
index fa0d7b7c0..80bb10538 100644
--- a/src/test/java/com/auth0/client/auth/AuthAPITest.java
+++ b/src/test/java/com/auth0/client/auth/AuthAPITest.java
@@ -1542,53 +1542,36 @@ public void addOtpAuthenticatorRequest() throws Exception {
     }
 
     @Test
-    public void addPhoneOobAuthenticatorThrowsWhenTokenNull() {
+    public void addOobAuthenticatorThrowsWhenTokenNull() {
         verifyThrows(IllegalArgumentException.class,
-            () -> api.addPhoneOobAuthenticator(null, Collections.singletonList("otp"), null),
+            () -> api.addOobAuthenticator(null, Collections.singletonList("otp"), null, null),
             "'mfa token' cannot be null!");
     }
 
     @Test
-    public void addPhoneOobAuthenticatorThrowsWhenChannelsNull() {
+    public void addOobAuthenticatorThrowsWhenChannelsNull() {
         verifyThrows(IllegalArgumentException.class,
-            () -> api.addPhoneOobAuthenticator("mfaToken", null, null),
+            () -> api.addOobAuthenticator("mfaToken", null, null, null),
             "'OOB channels' cannot be null!");
     }
 
     @Test
-    public void addOobAuthenticatorRequest() throws Exception {
-        Request request = api.addPhoneOobAuthenticator("mfaToken", Collections.singletonList("sms"), "phone-number");
-
-        server.jsonResponse(AUTH_OOB_AUTHENTICATOR_RESPONSE, 200);
-        CreatedOobResponse response = request.execute().getBody();
-        RecordedRequest recordedRequest = server.takeRequest();
-
-        assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/mfa/associate"));
-        assertThat(recordedRequest, hasHeader("Content-Type", "application/json"));
-
-        Map body = bodyFromRequest(recordedRequest);
-        assertThat(body, hasEntry("authenticator_types", Collections.singletonList("oob")));
-        assertThat(body, hasEntry("oob_channels", Collections.singletonList("sms")));
-        assertThat(body, hasEntry("phone_number", "phone-number"));
-
-        assertThat(response, is(notNullValue()));
-        assertThat(response.getAuthenticatorType(), not(emptyOrNullString()));
-        assertThat(response.getOobChannel(), not(emptyOrNullString()));
-        assertThat(response.getOobCode(), not(emptyOrNullString()));
-        assertThat(response.getBarcodeUri(), not(emptyOrNullString()));
-        assertThat(response.getRecoveryCodes(), notNullValue());
+    public void addOobAuthenticatorThrowsWhenPhoneNumberNull() {
+        verifyThrows(IllegalArgumentException.class,
+            () -> api.addOobAuthenticator("mfaToken", Collections.singletonList("sms"), null, null),
+            "'phone number' cannot be null!");
     }
 
     @Test
-    public void addEmailOobAuthenticatorThrowsWhenTokenNull() {
+    public void addOobAuthenticatorThrowsWhenEmailNull() {
         verifyThrows(IllegalArgumentException.class,
-            () -> api.addEmailOobAuthenticator(null, null),
-            "'mfa token' cannot be null!");
+            () -> api.addOobAuthenticator("mfaToken", Collections.singletonList("email"), null, null),
+            "'email address' cannot be null!");
     }
 
     @Test
-    public void addEmailOobAuthenticatorRequest() throws Exception {
-        Request request = api.addEmailOobAuthenticator("mfaToken", "email-address");
+    public void addOobAuthenticatorRequest() throws Exception {
+        Request request = api.addOobAuthenticator("mfaToken", Arrays.asList("sms", "email"), "phone-number", "email-address");
 
         server.jsonResponse(AUTH_OOB_AUTHENTICATOR_RESPONSE, 200);
         CreatedOobResponse response = request.execute().getBody();
@@ -1599,7 +1582,8 @@ public void addEmailOobAuthenticatorRequest() throws Exception {
 
         Map body = bodyFromRequest(recordedRequest);
         assertThat(body, hasEntry("authenticator_types", Collections.singletonList("oob")));
-        assertThat(body, hasEntry("oob_channels", Collections.singletonList("email")));
+        assertThat(body, hasEntry("oob_channels", Arrays.asList("sms", "email")));
+        assertThat(body, hasEntry("phone_number", "phone-number"));
         assertThat(body, hasEntry("email", "email-address"));
 
         assertThat(response, is(notNullValue()));

From ce280017496120735818ada10ac6d41b0e02658a Mon Sep 17 00:00:00 2001
From: Joao Moreira 
Date: Wed, 30 Jul 2025 11:12:24 -0400
Subject: [PATCH 3/8] Keep previous method but deprecate it

---
 .../java/com/auth0/client/auth/AuthAPI.java   | 48 +++++++++++++++++++
 .../com/auth0/client/auth/AuthAPITest.java    | 31 +++++++++++-
 2 files changed, 77 insertions(+), 2 deletions(-)

diff --git a/src/main/java/com/auth0/client/auth/AuthAPI.java b/src/main/java/com/auth0/client/auth/AuthAPI.java
index 03bb97d87..edde2510d 100644
--- a/src/main/java/com/auth0/client/auth/AuthAPI.java
+++ b/src/main/java/com/auth0/client/auth/AuthAPI.java
@@ -1413,6 +1413,54 @@ public Request addOtpAuthenticator(String mfaToken) {
      * @param mfaToken The token received from mfa_required error. Must not be null.
      * @param oobChannels The type of OOB channels supported by the client. Must not be null.
      * @param phoneNumber The phone number for "sms" or "voice" channels. May be null if not using "sms" or "voice".
+     * @return a Request to execute.
+     * @see Add an Authenticator API documentation
+     * @deprecated Use {@linkplain #addOobAuthenticator(String, List, String, String)} instead.
+     */
+    @Deprecated
+    public Request addOobAuthenticator(String mfaToken, List oobChannels, String phoneNumber) {
+        Asserts.assertNotNull(mfaToken, "mfa token");
+        Asserts.assertNotNull(oobChannels, "OOB channels");
+
+        String url = baseUrl
+            .newBuilder()
+            .addPathSegment("mfa")
+            .addPathSegment("associate")
+            .build()
+            .toString();
+
+        BaseRequest request = new BaseRequest<>(client, null, url, HttpMethod.POST, new TypeReference() {
+        });
+
+        request.addParameter("authenticator_types", Collections.singletonList("oob"));
+        request.addParameter("oob_channels", oobChannels);
+        request.addParameter(KEY_CLIENT_ID, clientId);
+        if (phoneNumber != null) {
+            request.addParameter("phone_number", phoneNumber);
+        }
+        addClientAuthentication(request, false);
+        request.addHeader("Authorization", "Bearer " + mfaToken);
+        return request;
+    }
+
+    /**
+     * Associates or adds a new OOB authenticator for multi-factor authentication (MFA).
+     * Confidential clients (Regular Web Apps) must have a client secret configured on this {@code AuthAPI} instance.
+     * 
+     * {@code
+     * try {
+     *      CreatedOobResponse result = authAPI.addOobAuthenticator("the-mfa-token", Arrays.asList("sms", "email"), "phone-number", "email-address")
+     *          .execute()
+     *          .getBody();
+     * } catch (Auth0Exception e) {
+     *      //Something happened
+     * }
+     * }
+     * 
+ * + * @param mfaToken The token received from mfa_required error. Must not be null. + * @param oobChannels The type of OOB channels supported by the client. Must not be null. + * @param phoneNumber The phone number for "sms" or "voice" channels. May be null if not using "sms" or "voice". * @param emailAddress The email address for "email" channel. May be null if not using "email". * @return a Request to execute. * @see Enroll with SMS or voice diff --git a/src/test/java/com/auth0/client/auth/AuthAPITest.java b/src/test/java/com/auth0/client/auth/AuthAPITest.java index 80bb10538..18273eb30 100644 --- a/src/test/java/com/auth0/client/auth/AuthAPITest.java +++ b/src/test/java/com/auth0/client/auth/AuthAPITest.java @@ -1548,13 +1548,15 @@ public void addOobAuthenticatorThrowsWhenTokenNull() { "'mfa token' cannot be null!"); } + @SuppressWarnings("deprecation") @Test public void addOobAuthenticatorThrowsWhenChannelsNull() { verifyThrows(IllegalArgumentException.class, - () -> api.addOobAuthenticator("mfaToken", null, null, null), + () -> api.addOobAuthenticator("mfaToken", null, null), "'OOB channels' cannot be null!"); } + @SuppressWarnings("deprecation") @Test public void addOobAuthenticatorThrowsWhenPhoneNumberNull() { verifyThrows(IllegalArgumentException.class, @@ -1562,6 +1564,31 @@ public void addOobAuthenticatorThrowsWhenPhoneNumberNull() { "'phone number' cannot be null!"); } + @SuppressWarnings("deprecation") + @Test + public void addOobAuthenticatorRequest() throws Exception { + Request request = api.addOobAuthenticator("mfaToken", Collections.singletonList("sms"), "phone-number"); + + server.jsonResponse(AUTH_OOB_AUTHENTICATOR_RESPONSE, 200); + CreatedOobResponse response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/mfa/associate")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body, hasEntry("authenticator_types", Collections.singletonList("oob"))); + assertThat(body, hasEntry("oob_channels", Collections.singletonList("sms"))); + assertThat(body, hasEntry("phone_number", "phone-number")); + + assertThat(response, is(notNullValue())); + assertThat(response.getAuthenticatorType(), not(emptyOrNullString())); + assertThat(response.getOobChannel(), not(emptyOrNullString())); + assertThat(response.getOobCode(), not(emptyOrNullString())); + assertThat(response.getBarcodeUri(), not(emptyOrNullString())); + assertThat(response.getRecoveryCodes(), notNullValue()); + } + @Test public void addOobAuthenticatorThrowsWhenEmailNull() { verifyThrows(IllegalArgumentException.class, @@ -1570,7 +1597,7 @@ public void addOobAuthenticatorThrowsWhenEmailNull() { } @Test - public void addOobAuthenticatorRequest() throws Exception { + public void addOobAuthenticatorRequestWithEmail() throws Exception { Request request = api.addOobAuthenticator("mfaToken", Arrays.asList("sms", "email"), "phone-number", "email-address"); server.jsonResponse(AUTH_OOB_AUTHENTICATOR_RESPONSE, 200); From fdf0146fd5d243fb8903d0005aee01827c8c211d Mon Sep 17 00:00:00 2001 From: Joao Moreira Date: Wed, 30 Jul 2025 11:25:15 -0400 Subject: [PATCH 4/8] Move shared code to common method --- .../java/com/auth0/client/auth/AuthAPI.java | 71 ++++++++----------- 1 file changed, 28 insertions(+), 43 deletions(-) diff --git a/src/main/java/com/auth0/client/auth/AuthAPI.java b/src/main/java/com/auth0/client/auth/AuthAPI.java index edde2510d..e5a3adc7d 100644 --- a/src/main/java/com/auth0/client/auth/AuthAPI.java +++ b/src/main/java/com/auth0/client/auth/AuthAPI.java @@ -1395,6 +1395,32 @@ public Request addOtpAuthenticator(String mfaToken) { return request; } + private BaseRequest createBaseOobRequest(String mfaToken, List oobChannels, String phoneNumber) { + Asserts.assertNotNull(mfaToken, "mfa token"); + Asserts.assertNotNull(oobChannels, "OOB channels"); + + String url = baseUrl + .newBuilder() + .addPathSegment("mfa") + .addPathSegment("associate") + .build() + .toString(); + + BaseRequest request = new BaseRequest<>(client, null, url, HttpMethod.POST, new TypeReference() { + }); + + request.addParameter("authenticator_types", Collections.singletonList("oob")); + request.addParameter("oob_channels", oobChannels); + request.addParameter(KEY_CLIENT_ID, clientId); + if (phoneNumber != null) { + request.addParameter("phone_number", phoneNumber); + } + addClientAuthentication(request, false); + request.addHeader("Authorization", "Bearer " + mfaToken); + + return request; + } + /** * Associates or adds a new OOB authenticator for multi-factor authentication (MFA). * Confidential clients (Regular Web Apps) must have a client secret configured on this {@code AuthAPI} instance. @@ -1419,28 +1445,7 @@ public Request addOtpAuthenticator(String mfaToken) { */ @Deprecated public Request addOobAuthenticator(String mfaToken, List oobChannels, String phoneNumber) { - Asserts.assertNotNull(mfaToken, "mfa token"); - Asserts.assertNotNull(oobChannels, "OOB channels"); - - String url = baseUrl - .newBuilder() - .addPathSegment("mfa") - .addPathSegment("associate") - .build() - .toString(); - - BaseRequest request = new BaseRequest<>(client, null, url, HttpMethod.POST, new TypeReference() { - }); - - request.addParameter("authenticator_types", Collections.singletonList("oob")); - request.addParameter("oob_channels", oobChannels); - request.addParameter(KEY_CLIENT_ID, clientId); - if (phoneNumber != null) { - request.addParameter("phone_number", phoneNumber); - } - addClientAuthentication(request, false); - request.addHeader("Authorization", "Bearer " + mfaToken); - return request; + return createBaseOobRequest(mfaToken, oobChannels, phoneNumber); } /** @@ -1466,8 +1471,6 @@ public Request addOobAuthenticator(String mfaToken, ListEnroll with SMS or voice */ public Request addOobAuthenticator(String mfaToken, List oobChannels, String phoneNumber, String emailAddress) { - Asserts.assertNotNull(mfaToken, "mfa token"); - Asserts.assertNotNull(oobChannels, "OOB channels"); if (oobChannels.contains("sms") || oobChannels.contains("voice")) { Asserts.assertNotNull(phoneNumber, "phone number"); } @@ -1475,28 +1478,10 @@ public Request addOobAuthenticator(String mfaToken, List request = new BaseRequest<>(client, null, url, HttpMethod.POST, new TypeReference() { - }); - - request.addParameter("authenticator_types", Collections.singletonList("oob")); - request.addParameter("oob_channels", oobChannels); - request.addParameter(KEY_CLIENT_ID, clientId); - - if (phoneNumber != null) { - request.addParameter("phone_number", phoneNumber); - } + BaseRequest request = createBaseOobRequest(mfaToken, oobChannels, phoneNumber); if (emailAddress != null) { request.addParameter("email", emailAddress); } - addClientAuthentication(request, false); - request.addHeader("Authorization", "Bearer " + mfaToken); return request; } From f8c1dfd1316af4c66c31c61cceea999ddb5ff9ae Mon Sep 17 00:00:00 2001 From: Joao Moreira Date: Wed, 30 Jul 2025 11:26:50 -0400 Subject: [PATCH 5/8] Clean up tests --- .../java/com/auth0/client/auth/AuthAPITest.java | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/test/java/com/auth0/client/auth/AuthAPITest.java b/src/test/java/com/auth0/client/auth/AuthAPITest.java index 18273eb30..d62c90768 100644 --- a/src/test/java/com/auth0/client/auth/AuthAPITest.java +++ b/src/test/java/com/auth0/client/auth/AuthAPITest.java @@ -1556,14 +1556,6 @@ public void addOobAuthenticatorThrowsWhenChannelsNull() { "'OOB channels' cannot be null!"); } - @SuppressWarnings("deprecation") - @Test - public void addOobAuthenticatorThrowsWhenPhoneNumberNull() { - verifyThrows(IllegalArgumentException.class, - () -> api.addOobAuthenticator("mfaToken", Collections.singletonList("sms"), null, null), - "'phone number' cannot be null!"); - } - @SuppressWarnings("deprecation") @Test public void addOobAuthenticatorRequest() throws Exception { @@ -1589,6 +1581,13 @@ public void addOobAuthenticatorRequest() throws Exception { assertThat(response.getRecoveryCodes(), notNullValue()); } + @Test + public void addOobAuthenticatorThrowsWhenPhoneNumberNull() { + verifyThrows(IllegalArgumentException.class, + () -> api.addOobAuthenticator("mfaToken", Collections.singletonList("sms"), null, null), + "'phone number' cannot be null!"); + } + @Test public void addOobAuthenticatorThrowsWhenEmailNull() { verifyThrows(IllegalArgumentException.class, From 6a8fa872abde55e1067fde912ea3c4519a34cd23 Mon Sep 17 00:00:00 2001 From: Joao Moreira Date: Mon, 4 Aug 2025 09:16:24 -0400 Subject: [PATCH 6/8] Move phone number checking out of common method --- .../java/com/auth0/client/auth/AuthAPI.java | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/auth0/client/auth/AuthAPI.java b/src/main/java/com/auth0/client/auth/AuthAPI.java index e5a3adc7d..8746f5847 100644 --- a/src/main/java/com/auth0/client/auth/AuthAPI.java +++ b/src/main/java/com/auth0/client/auth/AuthAPI.java @@ -1395,7 +1395,8 @@ public Request addOtpAuthenticator(String mfaToken) { return request; } - private BaseRequest createBaseOobRequest(String mfaToken, List oobChannels, String phoneNumber) { + + private BaseRequest createBaseOobRequest(String mfaToken, List oobChannels) { Asserts.assertNotNull(mfaToken, "mfa token"); Asserts.assertNotNull(oobChannels, "OOB channels"); @@ -1412,9 +1413,6 @@ private BaseRequest createBaseOobRequest(String mfaToken, Li request.addParameter("authenticator_types", Collections.singletonList("oob")); request.addParameter("oob_channels", oobChannels); request.addParameter(KEY_CLIENT_ID, clientId); - if (phoneNumber != null) { - request.addParameter("phone_number", phoneNumber); - } addClientAuthentication(request, false); request.addHeader("Authorization", "Bearer " + mfaToken); @@ -1445,7 +1443,12 @@ private BaseRequest createBaseOobRequest(String mfaToken, Li */ @Deprecated public Request addOobAuthenticator(String mfaToken, List oobChannels, String phoneNumber) { - return createBaseOobRequest(mfaToken, oobChannels, phoneNumber); + BaseRequest request = createBaseOobRequest(mfaToken, oobChannels); + if (phoneNumber != null) { + request.addParameter("phone_number", phoneNumber); + } + + return request; } /** @@ -1478,10 +1481,14 @@ public Request addOobAuthenticator(String mfaToken, List request = createBaseOobRequest(mfaToken, oobChannels, phoneNumber); + BaseRequest request = createBaseOobRequest(mfaToken, oobChannels); + if (phoneNumber != null) { + request.addParameter("phone_number", phoneNumber); + } if (emailAddress != null) { request.addParameter("email", emailAddress); } + return request; } From 8bd77fa37b36fdefc29a14ad64b083dbef0ee402 Mon Sep 17 00:00:00 2001 From: Joao Moreira Date: Mon, 4 Aug 2025 10:55:59 -0400 Subject: [PATCH 7/8] Add phone number null checking in deprecated method and add tests --- .../java/com/auth0/client/auth/AuthAPI.java | 12 +- .../com/auth0/client/auth/AuthAPITest.java | 110 +++++++++++++++++- 2 files changed, 115 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/auth0/client/auth/AuthAPI.java b/src/main/java/com/auth0/client/auth/AuthAPI.java index 8746f5847..6e310b356 100644 --- a/src/main/java/com/auth0/client/auth/AuthAPI.java +++ b/src/main/java/com/auth0/client/auth/AuthAPI.java @@ -1397,9 +1397,6 @@ public Request addOtpAuthenticator(String mfaToken) { private BaseRequest createBaseOobRequest(String mfaToken, List oobChannels) { - Asserts.assertNotNull(mfaToken, "mfa token"); - Asserts.assertNotNull(oobChannels, "OOB channels"); - String url = baseUrl .newBuilder() .addPathSegment("mfa") @@ -1443,7 +1440,14 @@ private BaseRequest createBaseOobRequest(String mfaToken, Li */ @Deprecated public Request addOobAuthenticator(String mfaToken, List oobChannels, String phoneNumber) { + Asserts.assertNotNull(mfaToken, "mfa token"); + Asserts.assertNotNull(oobChannels, "OOB channels"); + if (oobChannels.contains("sms") || oobChannels.contains("voice")) { + Asserts.assertNotNull(phoneNumber, "phone number"); + } + BaseRequest request = createBaseOobRequest(mfaToken, oobChannels); + if (phoneNumber != null) { request.addParameter("phone_number", phoneNumber); } @@ -1474,6 +1478,8 @@ public Request addOobAuthenticator(String mfaToken, ListEnroll with SMS or voice */ public Request addOobAuthenticator(String mfaToken, List oobChannels, String phoneNumber, String emailAddress) { + Asserts.assertNotNull(mfaToken, "mfa token"); + Asserts.assertNotNull(oobChannels, "OOB channels"); if (oobChannels.contains("sms") || oobChannels.contains("voice")) { Asserts.assertNotNull(phoneNumber, "phone number"); } diff --git a/src/test/java/com/auth0/client/auth/AuthAPITest.java b/src/test/java/com/auth0/client/auth/AuthAPITest.java index d62c90768..8a804f3b9 100644 --- a/src/test/java/com/auth0/client/auth/AuthAPITest.java +++ b/src/test/java/com/auth0/client/auth/AuthAPITest.java @@ -1541,16 +1541,17 @@ public void addOtpAuthenticatorRequest() throws Exception { assertThat(response.getRecoveryCodes(), notNullValue()); } + @SuppressWarnings("deprecation") @Test - public void addOobAuthenticatorThrowsWhenTokenNull() { + public void addOobAuthenticatorDeprecatedThrowsWhenTokenNull() { verifyThrows(IllegalArgumentException.class, - () -> api.addOobAuthenticator(null, Collections.singletonList("otp"), null, null), + () -> api.addOobAuthenticator(null, Collections.singletonList("auth0"), null), "'mfa token' cannot be null!"); } @SuppressWarnings("deprecation") @Test - public void addOobAuthenticatorThrowsWhenChannelsNull() { + public void addOobAuthenticatorDeprecatedThrowsWhenChannelsNull() { verifyThrows(IllegalArgumentException.class, () -> api.addOobAuthenticator("mfaToken", null, null), "'OOB channels' cannot be null!"); @@ -1558,7 +1559,15 @@ public void addOobAuthenticatorThrowsWhenChannelsNull() { @SuppressWarnings("deprecation") @Test - public void addOobAuthenticatorRequest() throws Exception { + public void addOobAuthenticatorDeprecatedThrowsWhenPhoneNumberNull() { + verifyThrows(IllegalArgumentException.class, + () -> api.addOobAuthenticator("mfaToken", Collections.singletonList("sms"), null), + "'phone number' cannot be null!"); + } + + @SuppressWarnings("deprecation") + @Test + public void addOobAuthenticatorDeprecatedRequest() throws Exception { Request request = api.addOobAuthenticator("mfaToken", Collections.singletonList("sms"), "phone-number"); server.jsonResponse(AUTH_OOB_AUTHENTICATOR_RESPONSE, 200); @@ -1581,6 +1590,21 @@ public void addOobAuthenticatorRequest() throws Exception { assertThat(response.getRecoveryCodes(), notNullValue()); } + @Test + public void addOobAuthenticatorThrowsWhenTokenNull() { + verifyThrows(IllegalArgumentException.class, + () -> api.addOobAuthenticator(null, Collections.singletonList("auth0"), null, null), + "'mfa token' cannot be null!"); + } + + + @Test + public void addOobAuthenticatorThrowsWhenChannelsNull() { + verifyThrows(IllegalArgumentException.class, + () -> api.addOobAuthenticator("mfaToken", null, null, null), + "'OOB channels' cannot be null!"); + } + @Test public void addOobAuthenticatorThrowsWhenPhoneNumberNull() { verifyThrows(IllegalArgumentException.class, @@ -1588,6 +1612,13 @@ public void addOobAuthenticatorThrowsWhenPhoneNumberNull() { "'phone number' cannot be null!"); } + @Test + public void addOobAuthenticatorThrowsWhenPhoneNumberNullWithVoiceChannel() { + verifyThrows(IllegalArgumentException.class, + () -> api.addOobAuthenticator("mfaToken", Collections.singletonList("voice"), null, null), + "'phone number' cannot be null!"); + } + @Test public void addOobAuthenticatorThrowsWhenEmailNull() { verifyThrows(IllegalArgumentException.class, @@ -1595,8 +1626,56 @@ public void addOobAuthenticatorThrowsWhenEmailNull() { "'email address' cannot be null!"); } + @Test + public void addOobAuthenticatorRequestWithPhoneNumber() throws Exception { + Request request = api.addOobAuthenticator("mfaToken", Collections.singletonList("sms"), "phone-number", null); + + server.jsonResponse(AUTH_OOB_AUTHENTICATOR_RESPONSE, 200); + CreatedOobResponse response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/mfa/associate")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body, hasEntry("authenticator_types", Collections.singletonList("oob"))); + assertThat(body, hasEntry("oob_channels", Collections.singletonList("sms"))); + assertThat(body, hasEntry("phone_number", "phone-number")); + + assertThat(response, is(notNullValue())); + assertThat(response.getAuthenticatorType(), not(emptyOrNullString())); + assertThat(response.getOobChannel(), not(emptyOrNullString())); + assertThat(response.getOobCode(), not(emptyOrNullString())); + assertThat(response.getBarcodeUri(), not(emptyOrNullString())); + assertThat(response.getRecoveryCodes(), notNullValue()); + } + @Test public void addOobAuthenticatorRequestWithEmail() throws Exception { + Request request = api.addOobAuthenticator("mfaToken", Collections.singletonList("email"), null, "email-address"); + + server.jsonResponse(AUTH_OOB_AUTHENTICATOR_RESPONSE, 200); + CreatedOobResponse response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/mfa/associate")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body, hasEntry("authenticator_types", Collections.singletonList("oob"))); + assertThat(body, hasEntry("oob_channels", Collections.singletonList("email"))); + assertThat(body, hasEntry("email", "email-address")); + + assertThat(response, is(notNullValue())); + assertThat(response.getAuthenticatorType(), not(emptyOrNullString())); + assertThat(response.getOobChannel(), not(emptyOrNullString())); + assertThat(response.getOobCode(), not(emptyOrNullString())); + assertThat(response.getBarcodeUri(), not(emptyOrNullString())); + assertThat(response.getRecoveryCodes(), notNullValue()); + } + + @Test + public void addOobAuthenticatorRequestWithMultipleChannels() throws Exception { Request request = api.addOobAuthenticator("mfaToken", Arrays.asList("sms", "email"), "phone-number", "email-address"); server.jsonResponse(AUTH_OOB_AUTHENTICATOR_RESPONSE, 200); @@ -1620,6 +1699,29 @@ public void addOobAuthenticatorRequestWithEmail() throws Exception { assertThat(response.getRecoveryCodes(), notNullValue()); } + @Test + public void addOobAuthenticatorRequestWithAuth0Channel() throws Exception { + Request request = api.addOobAuthenticator("mfaToken", Collections.singletonList("auth0"), null, "email-address"); + + server.jsonResponse(AUTH_OOB_AUTHENTICATOR_RESPONSE, 200); + CreatedOobResponse response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/mfa/associate")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body, hasEntry("authenticator_types", Collections.singletonList("oob"))); + assertThat(body, hasEntry("oob_channels", Collections.singletonList("auth0"))); + + assertThat(response, is(notNullValue())); + assertThat(response.getAuthenticatorType(), not(emptyOrNullString())); + assertThat(response.getOobChannel(), not(emptyOrNullString())); + assertThat(response.getOobCode(), not(emptyOrNullString())); + assertThat(response.getBarcodeUri(), not(emptyOrNullString())); + assertThat(response.getRecoveryCodes(), notNullValue()); + } + @Test public void listAuthenticatorsThrowsWhenTokenNull() { verifyThrows(IllegalArgumentException.class, From b6938e126c6d093fe91dc7e2f548d72a4e4e5f98 Mon Sep 17 00:00:00 2001 From: Joao Moreira Date: Mon, 4 Aug 2025 11:25:36 -0400 Subject: [PATCH 8/8] More test coverage --- .../com/auth0/client/auth/AuthAPITest.java | 73 ++++++++++++++++++- 1 file changed, 71 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/auth0/client/auth/AuthAPITest.java b/src/test/java/com/auth0/client/auth/AuthAPITest.java index 8a804f3b9..b89154a65 100644 --- a/src/test/java/com/auth0/client/auth/AuthAPITest.java +++ b/src/test/java/com/auth0/client/auth/AuthAPITest.java @@ -1565,6 +1565,14 @@ public void addOobAuthenticatorDeprecatedThrowsWhenPhoneNumberNull() { "'phone number' cannot be null!"); } + @SuppressWarnings("deprecation") + @Test + public void addOobAuthenticatorDeprecatedThrowsWhenPhoneNumberNullVoiceChannel() { + verifyThrows(IllegalArgumentException.class, + () -> api.addOobAuthenticator("mfaToken", Collections.singletonList("voice"), null), + "'phone number' cannot be null!"); + } + @SuppressWarnings("deprecation") @Test public void addOobAuthenticatorDeprecatedRequest() throws Exception { @@ -1590,6 +1598,30 @@ public void addOobAuthenticatorDeprecatedRequest() throws Exception { assertThat(response.getRecoveryCodes(), notNullValue()); } + @SuppressWarnings("deprecation") + @Test + public void addOobAuthenticatorDeprecatedRequestWithNoPhoneNumber() throws Exception { + Request request = api.addOobAuthenticator("mfaToken", Collections.singletonList("auth0"), null); + + server.jsonResponse(AUTH_OOB_AUTHENTICATOR_RESPONSE, 200); + CreatedOobResponse response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/mfa/associate")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body, hasEntry("authenticator_types", Collections.singletonList("oob"))); + assertThat(body, hasEntry("oob_channels", Collections.singletonList("auth0"))); + + assertThat(response, is(notNullValue())); + assertThat(response.getAuthenticatorType(), not(emptyOrNullString())); + assertThat(response.getOobChannel(), not(emptyOrNullString())); + assertThat(response.getOobCode(), not(emptyOrNullString())); + assertThat(response.getBarcodeUri(), not(emptyOrNullString())); + assertThat(response.getRecoveryCodes(), notNullValue()); + } + @Test public void addOobAuthenticatorThrowsWhenTokenNull() { verifyThrows(IllegalArgumentException.class, @@ -1605,6 +1637,20 @@ public void addOobAuthenticatorThrowsWhenChannelsNull() { "'OOB channels' cannot be null!"); } + @Test + public void addOobAuthenticatorThrowsWhenChannelsNullWithPhoneNumber() { + verifyThrows(IllegalArgumentException.class, + () -> api.addOobAuthenticator("mfaToken", null, "phone-number", null), + "'OOB channels' cannot be null!"); + } + + @Test + public void addOobAuthenticatorThrowsWhenChannelsNullWithEmail() { + verifyThrows(IllegalArgumentException.class, + () -> api.addOobAuthenticator("mfaToken", null, null, "email-address"), + "'OOB channels' cannot be null!"); + } + @Test public void addOobAuthenticatorThrowsWhenPhoneNumberNull() { verifyThrows(IllegalArgumentException.class, @@ -1674,9 +1720,32 @@ public void addOobAuthenticatorRequestWithEmail() throws Exception { assertThat(response.getRecoveryCodes(), notNullValue()); } + @Test + public void addOobAuthenticatorRequestWithNoContactInfo() throws Exception { + Request request = api.addOobAuthenticator("mfaToken", Collections.singletonList("auth0"), null, null); + + server.jsonResponse(AUTH_OOB_AUTHENTICATOR_RESPONSE, 200); + CreatedOobResponse response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/mfa/associate")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body, hasEntry("authenticator_types", Collections.singletonList("oob"))); + assertThat(body, hasEntry("oob_channels", Collections.singletonList("auth0"))); + + assertThat(response, is(notNullValue())); + assertThat(response.getAuthenticatorType(), not(emptyOrNullString())); + assertThat(response.getOobChannel(), not(emptyOrNullString())); + assertThat(response.getOobCode(), not(emptyOrNullString())); + assertThat(response.getBarcodeUri(), not(emptyOrNullString())); + assertThat(response.getRecoveryCodes(), notNullValue()); + } + @Test public void addOobAuthenticatorRequestWithMultipleChannels() throws Exception { - Request request = api.addOobAuthenticator("mfaToken", Arrays.asList("sms", "email"), "phone-number", "email-address"); + Request request = api.addOobAuthenticator("mfaToken", Arrays.asList("sms", "email", "auth0"), "phone-number", "email-address"); server.jsonResponse(AUTH_OOB_AUTHENTICATOR_RESPONSE, 200); CreatedOobResponse response = request.execute().getBody(); @@ -1687,7 +1756,7 @@ public void addOobAuthenticatorRequestWithMultipleChannels() throws Exception { Map body = bodyFromRequest(recordedRequest); assertThat(body, hasEntry("authenticator_types", Collections.singletonList("oob"))); - assertThat(body, hasEntry("oob_channels", Arrays.asList("sms", "email"))); + assertThat(body, hasEntry("oob_channels", Arrays.asList("sms", "email", "auth0"))); assertThat(body, hasEntry("phone_number", "phone-number")); assertThat(body, hasEntry("email", "email-address"));