diff --git a/EXAMPLES.md b/EXAMPLES.md index 1fc8ca1ee..52ea9eae9 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -2800,8 +2800,19 @@ To sign up a user with passkey ```kotlin // Using Coroutines try { + val userData = UserData( + email = "user@example.com", + phoneNumber = "+11234567890", + name = "John Doe", + givenName = "John", + familyName = "Doe", + nickName = "johnny", + picture = "https://example.com/photo.png", + userMetadata = mapOf("signup_source" to "android_app") + ) + val challenge = authenticationApiClient.signupWithPasskey( - "{user-data}", + userData, "{realm}", "{organization-id}" ).await() @@ -2831,7 +2842,19 @@ try { Using Java ```java - authenticationAPIClient.signupWithPasskey("{user-data}", "{realm}","{organization-id}") + UserData userData = new UserData( + "user@example.com", // email + "+11234567890", // phoneNumber + null, // userName + "John Doe", // name + "John", // givenName + "Doe", // familyName + "johnny", // nickName + "https://example.com/photo.png", // picture + Map.of("signup_source", "android_app") // userMetadata + ); + + authenticationAPIClient.signupWithPasskey(userData, "{realm}","{organization-id}") .start(new Callback() { @Override public void onSuccess(PasskeyRegistrationChallenge result) { diff --git a/auth0/src/main/java/com/auth0/android/authentication/AuthenticationAPIClient.kt b/auth0/src/main/java/com/auth0/android/authentication/AuthenticationAPIClient.kt index f33aee9ca..1f986e917 100755 --- a/auth0/src/main/java/com/auth0/android/authentication/AuthenticationAPIClient.kt +++ b/auth0/src/main/java/com/auth0/android/authentication/AuthenticationAPIClient.kt @@ -313,17 +313,24 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe * * Example usage: * - * * ``` - * client.signupWithPasskey("{userData}","{realm}","{organization}") - * .addParameter("scope","scope") - * .start(object: Callback { - * override fun onSuccess(result: PasskeyRegistration) { } + * val userData = UserData( + * email = "user@example.com", + * name = "John Doe", + * givenName = "John", + * familyName = "Doe", + * nickName = "johnny", + * picture = "https://example.com/photo.png", + * userMetadata = mapOf("signup_source" to "android_app") + * ) + * client.signupWithPasskey(userData, "{realm}", "{organization}") + * .start(object: Callback { + * override fun onSuccess(result: PasskeyRegistrationChallenge) { } * override fun onFailure(error: AuthenticationException) { } * }) * ``` * - * @param userData user information of the client + * @param userData user information for registration. * @param realm the connection to use. If excluded, the application will use the default connection configured in the tenant * @param organization id of the organization to be associated with the user while signing up * @return a request to configure and start that will yield [PasskeyRegistrationChallenge] @@ -333,7 +340,6 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe realm: String? = null, organization: String? = null ): Request { - val user = gson.toJsonTree(userData) val url = auth0.getDomainUrl().toHttpUrl().newBuilder() .addPathSegment(PASSKEY_PATH) .addPathSegment(REGISTER_PATH) @@ -351,7 +357,8 @@ public class AuthenticationAPIClient @VisibleForTesting(otherwise = VisibleForTe ) val post = factory.post(url.toString(), passkeyRegistrationChallengeAdapter) .addParameters(params) as BaseRequest - post.addParameter(USER_PROFILE_KEY, user) + post.addParameter(USER_PROFILE_KEY, gson.toJsonTree(userData.toUserProfile())) + userData.userMetadata?.let { post.addParameter(USER_METADATA_KEY, it) } return post } diff --git a/auth0/src/main/java/com/auth0/android/request/UserData.kt b/auth0/src/main/java/com/auth0/android/request/UserData.kt index 693b64368..f1bc73f6d 100644 --- a/auth0/src/main/java/com/auth0/android/request/UserData.kt +++ b/auth0/src/main/java/com/auth0/android/request/UserData.kt @@ -8,10 +8,22 @@ import com.google.gson.annotations.SerializedName * @param phoneNumber the phone number of the user. phone number can be optional, required, or forbidden depending on the attribute configuration for the database * @param userName the username of the user. username can be optional, required, or forbidden depending on the attribute configuration for the database * @param name optional display name + * @param givenName the first name of the user + * @param familyName the last name of the user + * @param nickName the preferred nickname of the user + * @param picture URL pointing to the user's profile picture + * @param userMetadata additional user metadata as key-value pairs */ public data class UserData( @field:SerializedName("email") val email: String? = null, @field:SerializedName("phone_number") val phoneNumber: String? = null, @field:SerializedName("username") val userName: String? = null, @field:SerializedName("name") val name: String? = null, -) \ No newline at end of file + @field:SerializedName("given_name") val givenName: String? = null, + @field:SerializedName("family_name") val familyName: String? = null, + @field:SerializedName("nickname") val nickName: String? = null, + @field:SerializedName("picture") val picture: String? = null, + @field:SerializedName("user_metadata") val userMetadata: Map? = null, +) { + internal fun toUserProfile(): UserData = copy(userMetadata = null) +} \ No newline at end of file diff --git a/auth0/src/test/java/com/auth0/android/authentication/AuthenticationAPIClientTest.kt b/auth0/src/test/java/com/auth0/android/authentication/AuthenticationAPIClientTest.kt index 1eb962e33..acc23a85a 100755 --- a/auth0/src/test/java/com/auth0/android/authentication/AuthenticationAPIClientTest.kt +++ b/auth0/src/test/java/com/auth0/android/authentication/AuthenticationAPIClientTest.kt @@ -13,6 +13,7 @@ import com.auth0.android.provider.JwtTestUtils import com.auth0.android.request.HttpMethod import com.auth0.android.request.NetworkingClient import com.auth0.android.request.PublicKeyCredentials +import com.auth0.android.request.UserData import com.auth0.android.request.RequestOptions import com.auth0.android.request.ServerResponse import com.auth0.android.request.internal.RequestFactory @@ -243,7 +244,7 @@ public class AuthenticationAPIClientTest { val auth0 = auth0 val client = AuthenticationAPIClient(auth0) val registrationResponse = client.signupWithPasskey( - mock(), + UserData(email = "test@example.com"), MY_CONNECTION, "testOrganization" ) @@ -264,6 +265,49 @@ public class AuthenticationAPIClientTest { assertThat(registrationResponse.authSession, Matchers.comparesEqualTo(SESSION_ID)) } + @Test + public fun shouldSignupWithPasskeyWithAllUserDataFields() { + mockAPI.willReturnSuccessfulPasskeyRegistration() + val auth0 = auth0 + val client = AuthenticationAPIClient(auth0) + val userData = UserData( + email = "test@example.com", + phoneNumber = "+1234567890", + userName = "testuser", + name = "Test User", + givenName = "Test", + familyName = "User", + nickName = "testy", + picture = "https://example.com/photo.png", + userMetadata = mapOf("key1" to "value1") + ) + val registrationResponse = client.signupWithPasskey( + userData, + MY_CONNECTION, + "testOrganization" + ).execute() + val request = mockAPI.takeRequest() + val body = bodyFromRequest(request) + assertThat(request.path, Matchers.equalTo("/passkey/register")) + assertThat(body, Matchers.hasKey("user_profile")) + @Suppress("UNCHECKED_CAST") + val userProfile = body["user_profile"] as Map + assertThat(userProfile, Matchers.hasEntry("email", "test@example.com")) + assertThat(userProfile, Matchers.hasEntry("phone_number", "+1234567890")) + assertThat(userProfile, Matchers.hasEntry("username", "testuser")) + assertThat(userProfile, Matchers.hasEntry("name", "Test User")) + assertThat(userProfile, Matchers.hasEntry("given_name", "Test")) + assertThat(userProfile, Matchers.hasEntry("family_name", "User")) + assertThat(userProfile, Matchers.hasEntry("nickname", "testy")) + assertThat(userProfile, Matchers.hasEntry("picture", "https://example.com/photo.png")) + assertThat(userProfile, Matchers.not(Matchers.hasKey("user_metadata"))) + assertThat(body, Matchers.hasKey("user_metadata")) + @Suppress("UNCHECKED_CAST") + val metadata = body["user_metadata"] as Map + assertThat(metadata, Matchers.hasEntry("key1", "value1")) + assertThat(registrationResponse, Matchers.`is`(Matchers.notNullValue())) + } + @Test public fun shouldGetPasskeyChallenge() { mockAPI.willReturnSuccessfulPasskeyChallenge()