From 6ffb89e2d8d758a803d3d9306ca72962e512df3a Mon Sep 17 00:00:00 2001 From: Daniel DeGroff Date: Thu, 31 Jul 2025 09:40:15 -0600 Subject: [PATCH 1/4] Client builder sync --- .../io/fusionauth/domain/oauth2/OAuthError.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/fusionauth/domain/oauth2/OAuthError.java b/src/main/java/io/fusionauth/domain/oauth2/OAuthError.java index 3e455790..ed8780b2 100644 --- a/src/main/java/io/fusionauth/domain/oauth2/OAuthError.java +++ b/src/main/java/io/fusionauth/domain/oauth2/OAuthError.java @@ -88,6 +88,7 @@ public enum OAuthErrorReason { invalid_pkce_code_verifier, invalid_pkce_code_challenge, invalid_pkce_code_challenge_method, + invalid_prompt, invalid_redirect_uri, invalid_response_mode, invalid_response_type, @@ -143,7 +144,14 @@ public enum OAuthErrorReason { unknown, missing_required_scope, unknown_scope, - consent_canceled + consent_canceled, + + // reasons for login_required + authentication_required, + multi_factor_challenge_required, + + // reasons for consent_required + consent_required } public enum OAuthErrorType { @@ -154,6 +162,11 @@ public enum OAuthErrorType { // Described in section 5.3.3 of the OpenID Connect Core https://openid.net/specs/openid-connect-core-1_0.html#UserInfoError invalid_token, + // OpenID Connect Core section 3.1.2.6 + consent_required, + interaction_required, + login_required, + unauthorized_client, invalid_scope, server_error, From b39c434523340fdb1a123eb59e2c9dc1443c9c85 Mon Sep 17 00:00:00 2001 From: Daniel DeGroff Date: Fri, 1 Aug 2025 13:04:29 -0600 Subject: [PATCH 2/4] sync client builder --- src/main/java/io/fusionauth/domain/oauth2/OAuthError.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/io/fusionauth/domain/oauth2/OAuthError.java b/src/main/java/io/fusionauth/domain/oauth2/OAuthError.java index ed8780b2..1f6cd3e6 100644 --- a/src/main/java/io/fusionauth/domain/oauth2/OAuthError.java +++ b/src/main/java/io/fusionauth/domain/oauth2/OAuthError.java @@ -148,7 +148,11 @@ public enum OAuthErrorReason { // reasons for login_required authentication_required, + email_verification_required, multi_factor_challenge_required, + registration_missing_requirement, + registration_required, + registration_verification_required, // reasons for consent_required consent_required From 9c81c919e19cbbcdda8bab83e1de4c0d21d32849 Mon Sep 17 00:00:00 2001 From: Daniel DeGroff Date: Mon, 18 Aug 2025 10:01:21 -0600 Subject: [PATCH 3/4] sync domain builds --- .../fusionauth/client/ExceptionDelegate.java | 4 +- .../fusionauth/client/FusionAuthClient.java | 106 +++++++++++- .../client/FusionAuthClientException.java | 2 +- .../java/io/fusionauth/client/JWTManager.java | 14 +- .../io/fusionauth/client/LambdaDelegate.java | 2 +- .../client/json/ConnectorJacksonHelper.java | 14 +- .../json/ConnectorRequestDeserializer.java | 14 +- .../json/ConnectorResponseDeserializer.java | 14 +- .../client/json/FusionAuthJacksonModule.java | 19 +++ .../json/IdentityProviderJacksonHelper.java | 14 +- .../IdentityProviderRequestDeserializer.java | 14 +- .../IdentityProviderResponseDeserializer.java | 14 +- ...ityProviderSearchResponseDeserializer.java | 14 +- .../client/json/IdentityTypeDeserializer.java | 35 ++++ .../client/json/IdentityTypeSerializer.java | 41 +++++ .../json/MessageTemplateJacksonHelper.java | 17 +- .../MessageTemplateRequestDeserializer.java | 14 +- .../MessageTemplateResponseDeserializer.java | 14 +- .../client/json/MessengerJacksonHelper.java | 14 +- .../json/MessengerRequestDeserializer.java | 14 +- .../json/MessengerResponseDeserializer.java | 14 +- ...iewMessageTemplateRequestDeserializer.java | 14 +- .../client/json/WebhookEventDeserializer.java | 18 +- .../io/fusionauth/domain/Application.java | 9 +- .../domain/ApplicationPhoneConfiguration.java | 106 ++++++++++++ .../io/fusionauth/domain/ContentStatus.java | 2 +- .../domain/DisplayableRawLogin.java | 28 ++- .../fusionauth/domain/EmailConfiguration.java | 22 +-- .../ExternalIdentifierConfiguration.java | 22 ++- .../io/fusionauth/domain/IdentityType.java | 104 ++++++++++++ .../domain/IdentityVerifiedReason.java | 62 +++++++ .../domain/OpenIdConfiguration.java | 2 +- .../domain/PasswordlessStrategy.java | 24 +++ .../domain/PhoneUnverifiedOptions.java | 63 +++++++ .../domain/RateLimitedRequestType.java | 19 ++- .../io/fusionauth/domain/SecureIdentity.java | 17 +- .../domain/SendSetPasswordIdentityType.java | 26 +++ .../java/io/fusionauth/domain/Tenant.java | 7 +- .../domain/TenantPhoneConfiguration.java | 129 ++++++++++++++ .../domain/TenantRateLimitConfiguration.java | 23 ++- src/main/java/io/fusionauth/domain/Theme.java | 34 +++- src/main/java/io/fusionauth/domain/User.java | 139 ++++++++++++--- .../io/fusionauth/domain/UserIdentity.java | 160 ++++++++++++++++++ .../domain/VerificationStrategy.java | 16 +- .../io/fusionauth/domain/WebhookEventLog.java | 2 +- .../fusionauth/domain/api/LoginRequest.java | 16 +- .../fusionauth/domain/api/LoginResponse.java | 16 +- .../io/fusionauth/domain/api/UserRequest.java | 25 ++- .../fusionauth/domain/api/UserResponse.java | 17 +- .../domain/api/WebAuthnStartRequest.java | 20 ++- .../verify/VerifyCompleteRequest.java | 28 +++ .../verify/VerifyCompleteResponse.java | 27 +++ .../api/identity/verify/VerifyRequest.java | 28 +++ .../identity/verify/VerifySendRequest.java | 34 ++++ .../identity/verify/VerifyStartRequest.java | 37 ++++ .../identity/verify/VerifyStartResponse.java | 36 ++++ .../IdentityProviderStartLoginRequest.java | 8 +- .../domain/api/jwt/RefreshRequest.java | 17 +- .../PasswordlessLoginRequest.java | 4 +- .../passwordless/PasswordlessSendRequest.java | 28 ++- .../PasswordlessStartRequest.java | 13 +- .../PasswordlessStartResponse.java | 9 +- .../api/twoFactor/TwoFactorStartRequest.java | 6 +- .../api/user/ChangePasswordRequest.java | 5 +- .../api/user/ForgotPasswordRequest.java | 15 +- .../domain/api/user/RegistrationRequest.java | 9 +- .../domain/connector/ConnectorType.java | 4 +- .../domain/event/BaseUserEvent.java | 44 ++++- .../fusionauth/domain/event/EventRequest.java | 2 +- .../io/fusionauth/domain/event/EventType.java | 12 +- .../event/JWTRefreshTokenRevokeEvent.java | 19 ++- .../domain/event/UserIdentityUpdateEvent.java | 95 +++++++++++ .../event/UserIdentityVerifiedEvent.java | 87 ++++++++++ .../domain/event/UserLoginFailedEvent.java | 3 +- .../UserLoginIdDuplicateOnCreateEvent.java | 26 ++- .../UserLoginIdDuplicateOnUpdateEvent.java | 9 +- .../UserRegistrationCreateCompleteEvent.java | 19 ++- .../event/UserRegistrationCreateEvent.java | 19 ++- .../UserRegistrationDeleteCompleteEvent.java | 19 ++- .../event/UserRegistrationDeleteEvent.java | 19 ++- .../UserRegistrationUpdateCompleteEvent.java | 19 ++- .../event/UserRegistrationUpdateEvent.java | 20 ++- .../event/UserRegistrationVerifiedEvent.java | 19 ++- .../domain/event/UserUpdateCompleteEvent.java | 15 +- .../domain/event/UserUpdateEvent.java | 15 +- .../fusionauth/domain/form/FormDataType.java | 3 +- .../fusionauth/domain/form/ManagedFields.java | 11 +- .../fusionauth/domain/oauth2/OAuthError.java | 1 + .../fusionauth/domain/oauth2/UserState.java | 6 +- .../domain/search/UserSearchCriteria.java | 3 +- .../io/fusionauth/domain/util/Normalizer.java | 14 ++ 91 files changed, 2159 insertions(+), 198 deletions(-) create mode 100644 src/main/java/io/fusionauth/client/json/FusionAuthJacksonModule.java create mode 100644 src/main/java/io/fusionauth/client/json/IdentityTypeDeserializer.java create mode 100644 src/main/java/io/fusionauth/client/json/IdentityTypeSerializer.java create mode 100644 src/main/java/io/fusionauth/domain/ApplicationPhoneConfiguration.java create mode 100644 src/main/java/io/fusionauth/domain/IdentityType.java create mode 100644 src/main/java/io/fusionauth/domain/IdentityVerifiedReason.java create mode 100644 src/main/java/io/fusionauth/domain/PasswordlessStrategy.java create mode 100644 src/main/java/io/fusionauth/domain/PhoneUnverifiedOptions.java create mode 100644 src/main/java/io/fusionauth/domain/SendSetPasswordIdentityType.java create mode 100644 src/main/java/io/fusionauth/domain/TenantPhoneConfiguration.java create mode 100644 src/main/java/io/fusionauth/domain/UserIdentity.java create mode 100644 src/main/java/io/fusionauth/domain/api/identity/verify/VerifyCompleteRequest.java create mode 100644 src/main/java/io/fusionauth/domain/api/identity/verify/VerifyCompleteResponse.java create mode 100644 src/main/java/io/fusionauth/domain/api/identity/verify/VerifyRequest.java create mode 100644 src/main/java/io/fusionauth/domain/api/identity/verify/VerifySendRequest.java create mode 100644 src/main/java/io/fusionauth/domain/api/identity/verify/VerifyStartRequest.java create mode 100644 src/main/java/io/fusionauth/domain/api/identity/verify/VerifyStartResponse.java create mode 100644 src/main/java/io/fusionauth/domain/event/UserIdentityUpdateEvent.java create mode 100644 src/main/java/io/fusionauth/domain/event/UserIdentityVerifiedEvent.java diff --git a/src/main/java/io/fusionauth/client/ExceptionDelegate.java b/src/main/java/io/fusionauth/client/ExceptionDelegate.java index 0dd2a846..3086bd60 100644 --- a/src/main/java/io/fusionauth/client/ExceptionDelegate.java +++ b/src/main/java/io/fusionauth/client/ExceptionDelegate.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, FusionAuth, All Rights Reserved + * Copyright (c) 2018-2024, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,7 +40,7 @@ public ExceptionDelegate() { * @param The error response type. * @return The success response type if the API call was successful. * @throws FusionAuthClientException If the status code was anything exception 2xx or 404 or there was a problem calling - * the API (network or something similar). + * the API (network or something similar). */ @SuppressWarnings("unchecked") public T execute(Supplier> supplier) { diff --git a/src/main/java/io/fusionauth/client/FusionAuthClient.java b/src/main/java/io/fusionauth/client/FusionAuthClient.java index 66aea7a3..bba6849a 100644 --- a/src/main/java/io/fusionauth/client/FusionAuthClient.java +++ b/src/main/java/io/fusionauth/client/FusionAuthClient.java @@ -37,6 +37,7 @@ import io.fusionauth.domain.LambdaType; import io.fusionauth.domain.OpenIdConfiguration; import io.fusionauth.domain.api.APIKeyRequest; +import io.fusionauth.client.json.FusionAuthJacksonModule; import io.fusionauth.domain.api.APIKeyResponse; import io.fusionauth.domain.api.ApplicationOAuthScopeRequest; import io.fusionauth.domain.api.ApplicationOAuthScopeResponse; @@ -181,6 +182,12 @@ import io.fusionauth.domain.api.WebhookSearchResponse; import io.fusionauth.domain.api.email.SendRequest; import io.fusionauth.domain.api.email.SendResponse; +import io.fusionauth.domain.api.identity.verify.VerifyCompleteRequest; +import io.fusionauth.domain.api.identity.verify.VerifyCompleteResponse; +import io.fusionauth.domain.api.identity.verify.VerifyRequest; +import io.fusionauth.domain.api.identity.verify.VerifyStartRequest; +import io.fusionauth.domain.api.identity.verify.VerifyStartResponse; +import io.fusionauth.domain.api.identity.verify.VerifySendRequest; import io.fusionauth.domain.api.identityProvider.IdentityProviderLinkRequest; import io.fusionauth.domain.api.identityProvider.IdentityProviderLinkResponse; import io.fusionauth.domain.api.identityProvider.IdentityProviderLoginRequest; @@ -260,7 +267,8 @@ public class FusionAuthClient { .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) .configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, true) .configure(DeserializationFeature.USE_BIG_INTEGER_FOR_INTS, true) - .registerModule(new JacksonModule()); + .registerModule(new JacksonModule()) + .registerModule(new FusionAuthJacksonModule()); private final String apiKey; @@ -578,6 +586,20 @@ public ClientResponse commentOnUser(UserCommentRequ .go(); } + /** + * Completes verification of an identity using verification codes from the Verify Start API. + * + * @param request The identity verify complete request that contains all the information used to verify the identity. + * @return The ClientResponse object. + */ + public ClientResponse completeVerifyIdentity(VerifyCompleteRequest request) { + return start(VerifyCompleteResponse.class, Errors.class) + .uri("/api/identity/verify/complete") + .bodyHandler(new JSONBodyHandler(request, objectMapper())) + .post() + .go(); + } + /** * Complete a WebAuthn authentication ceremony by validating the signature against the previously generated challenge without logging the user in * @@ -4180,6 +4202,22 @@ public ClientResponse retrieveUserByLoginId(String loginId .go(); } + /** + * Retrieves the user for the loginId, using specific loginIdTypes. + * + * @param loginId The email or username of the user. + * @param loginIdTypes the identity types that FusionAuth will compare the loginId to. + * @return The ClientResponse object. + */ + public ClientResponse retrieveUserByLoginIdWithLoginIdTypes(String loginId, List loginIdTypes) { + return start(UserResponse.class, Errors.class) + .uri("/api/user") + .urlParameter("loginId", loginId) + .urlParameter("loginIdTypes", loginIdTypes) + .get() + .go(); + } + /** * Retrieves the user for the given username. * @@ -4383,6 +4421,29 @@ public ClientResponse retrieveUserLoginReportByLogi .go(); } + /** + * Retrieves the login report between the two instants for a particular user by login Id, using specific loginIdTypes. If you specify an application id, it will only return the + * login counts for that application. + * + * @param applicationId (Optional) The application id. + * @param loginId The userId id. + * @param start The start instant as UTC milliseconds since Epoch. + * @param end The end instant as UTC milliseconds since Epoch. + * @param loginIdTypes the identity types that FusionAuth will compare the loginId to. + * @return The ClientResponse object. + */ + public ClientResponse retrieveUserLoginReportByLoginIdAndLoginIdTypes(UUID applicationId, String loginId, long start, long end, List loginIdTypes) { + return start(LoginReportResponse.class, Errors.class) + .uri("/api/report/login") + .urlParameter("applicationId", applicationId) + .urlParameter("loginId", loginId) + .urlParameter("start", start) + .urlParameter("end", end) + .urlParameter("loginIdTypes", loginIdTypes) + .get() + .go(); + } + /** * Retrieves the last number of login records for a user. * @@ -5114,6 +5175,20 @@ public ClientResponse sendTwoFactorCodeForLoginUsingMethod(String .go(); } + /** + * Send a verification code using the appropriate transport for the identity type being verified. + * + * @param request The identity verify send request that contains all the information used send the code. + * @return The ClientResponse object. + */ + public ClientResponse sendVerifyIdentity(VerifySendRequest request) { + return start(Void.TYPE, Errors.class) + .uri("/api/identity/verify/send") + .bodyHandler(new JSONBodyHandler(request, objectMapper())) + .post() + .go(); + } + /** * Begins a login request for a 3rd party login that requires user interaction such as HYPR. * @@ -5163,6 +5238,21 @@ public ClientResponse startTwoFactorLogin(TwoFac .go(); } + /** + * Start a verification of an identity by generating a code. This code can be sent to the User using the Verify Send API + * Verification Code API or using a mechanism outside of FusionAuth. The verification is completed by using the Verify Complete API with this code. + * + * @param request The identity verify start request that contains all the information used to begin the request. + * @return The ClientResponse object. + */ + public ClientResponse startVerifyIdentity(VerifyStartRequest request) { + return start(VerifyStartResponse.class, Errors.class) + .uri("/api/identity/verify/start") + .bodyHandler(new JSONBodyHandler(request, objectMapper())) + .post() + .go(); + } + /** * Start a WebAuthn authentication ceremony by generating a new challenge for the user * @@ -5828,6 +5918,20 @@ public ClientResponse verifyEmailAddressByUserId(VerifyEmailReques .go(); } + /** + * Administratively verify a user identity. + * + * @param request The identity verify request that contains information to verify the identity. + * @return The ClientResponse object. + */ + public ClientResponse verifyIdentity(VerifyRequest request) { + return start(Void.TYPE, Errors.class) + .uri("/api/identity/verify") + .bodyHandler(new JSONBodyHandler(request, objectMapper())) + .post() + .go(); + } + /** * Confirms an application registration. The Id given is usually from an email sent to the user. * diff --git a/src/main/java/io/fusionauth/client/FusionAuthClientException.java b/src/main/java/io/fusionauth/client/FusionAuthClientException.java index f728eebd..b4ef170f 100644 --- a/src/main/java/io/fusionauth/client/FusionAuthClientException.java +++ b/src/main/java/io/fusionauth/client/FusionAuthClientException.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, FusionAuth, All Rights Reserved + * Copyright (c) 2018-2024, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/io/fusionauth/client/JWTManager.java b/src/main/java/io/fusionauth/client/JWTManager.java index d62d867e..5355168d 100644 --- a/src/main/java/io/fusionauth/client/JWTManager.java +++ b/src/main/java/io/fusionauth/client/JWTManager.java @@ -1,5 +1,17 @@ /* - * Copyright (c) 2018-2023, FusionAuth, All Rights Reserved + * Copyright (c) 2018-2024, FusionAuth, All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. */ package io.fusionauth.client; diff --git a/src/main/java/io/fusionauth/client/LambdaDelegate.java b/src/main/java/io/fusionauth/client/LambdaDelegate.java index b8452736..ecc7af87 100644 --- a/src/main/java/io/fusionauth/client/LambdaDelegate.java +++ b/src/main/java/io/fusionauth/client/LambdaDelegate.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, FusionAuth, All Rights Reserved + * Copyright (c) 2018-2024, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/io/fusionauth/client/json/ConnectorJacksonHelper.java b/src/main/java/io/fusionauth/client/json/ConnectorJacksonHelper.java index 1680237b..de3d4e2f 100644 --- a/src/main/java/io/fusionauth/client/json/ConnectorJacksonHelper.java +++ b/src/main/java/io/fusionauth/client/json/ConnectorJacksonHelper.java @@ -1,5 +1,17 @@ /* - * Copyright (c) 2020-2022, FusionAuth, All Rights Reserved + * Copyright (c) 2020-2024, FusionAuth, All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. */ package io.fusionauth.client.json; diff --git a/src/main/java/io/fusionauth/client/json/ConnectorRequestDeserializer.java b/src/main/java/io/fusionauth/client/json/ConnectorRequestDeserializer.java index 7f86314f..08d63cf1 100644 --- a/src/main/java/io/fusionauth/client/json/ConnectorRequestDeserializer.java +++ b/src/main/java/io/fusionauth/client/json/ConnectorRequestDeserializer.java @@ -1,5 +1,17 @@ /* - * Copyright (c) 2019-2023, FusionAuth, All Rights Reserved + * Copyright (c) 2019-2024, FusionAuth, All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. */ package io.fusionauth.client.json; diff --git a/src/main/java/io/fusionauth/client/json/ConnectorResponseDeserializer.java b/src/main/java/io/fusionauth/client/json/ConnectorResponseDeserializer.java index a31475c8..fba3e143 100644 --- a/src/main/java/io/fusionauth/client/json/ConnectorResponseDeserializer.java +++ b/src/main/java/io/fusionauth/client/json/ConnectorResponseDeserializer.java @@ -1,5 +1,17 @@ /* - * Copyright (c) 2019-2022, FusionAuth, All Rights Reserved + * Copyright (c) 2019-2024, FusionAuth, All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. */ package io.fusionauth.client.json; diff --git a/src/main/java/io/fusionauth/client/json/FusionAuthJacksonModule.java b/src/main/java/io/fusionauth/client/json/FusionAuthJacksonModule.java new file mode 100644 index 00000000..5f42e1bd --- /dev/null +++ b/src/main/java/io/fusionauth/client/json/FusionAuthJacksonModule.java @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2024-2025, FusionAuth, All Rights Reserved + */ +package io.fusionauth.client.json; + +import com.fasterxml.jackson.databind.module.SimpleModule; +import io.fusionauth.domain.IdentityType; + +/** + * FusionAuth specific Jackson bindings. This can be used in the public FusionAuth client. + * + * @author Daniel DeGroff + */ +public class FusionAuthJacksonModule extends SimpleModule { + public FusionAuthJacksonModule() { + addSerializer(IdentityType.class, new IdentityTypeSerializer()); + addDeserializer(IdentityType.class, new IdentityTypeDeserializer()); + } +} diff --git a/src/main/java/io/fusionauth/client/json/IdentityProviderJacksonHelper.java b/src/main/java/io/fusionauth/client/json/IdentityProviderJacksonHelper.java index 64abc22f..6c2fa7bc 100644 --- a/src/main/java/io/fusionauth/client/json/IdentityProviderJacksonHelper.java +++ b/src/main/java/io/fusionauth/client/json/IdentityProviderJacksonHelper.java @@ -1,5 +1,17 @@ /* - * Copyright (c) 2019-2022, FusionAuth, All Rights Reserved + * Copyright (c) 2019-2024, FusionAuth, All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. */ package io.fusionauth.client.json; diff --git a/src/main/java/io/fusionauth/client/json/IdentityProviderRequestDeserializer.java b/src/main/java/io/fusionauth/client/json/IdentityProviderRequestDeserializer.java index 9d3e985b..116b1dc3 100644 --- a/src/main/java/io/fusionauth/client/json/IdentityProviderRequestDeserializer.java +++ b/src/main/java/io/fusionauth/client/json/IdentityProviderRequestDeserializer.java @@ -1,5 +1,17 @@ /* - * Copyright (c) 2019-2023, FusionAuth, All Rights Reserved + * Copyright (c) 2019-2024, FusionAuth, All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. */ package io.fusionauth.client.json; diff --git a/src/main/java/io/fusionauth/client/json/IdentityProviderResponseDeserializer.java b/src/main/java/io/fusionauth/client/json/IdentityProviderResponseDeserializer.java index d56f0435..cbd2d81f 100644 --- a/src/main/java/io/fusionauth/client/json/IdentityProviderResponseDeserializer.java +++ b/src/main/java/io/fusionauth/client/json/IdentityProviderResponseDeserializer.java @@ -1,5 +1,17 @@ /* - * Copyright (c) 2019-2022, FusionAuth, All Rights Reserved + * Copyright (c) 2019-2024, FusionAuth, All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. */ package io.fusionauth.client.json; diff --git a/src/main/java/io/fusionauth/client/json/IdentityProviderSearchResponseDeserializer.java b/src/main/java/io/fusionauth/client/json/IdentityProviderSearchResponseDeserializer.java index b844bc4d..105415bd 100644 --- a/src/main/java/io/fusionauth/client/json/IdentityProviderSearchResponseDeserializer.java +++ b/src/main/java/io/fusionauth/client/json/IdentityProviderSearchResponseDeserializer.java @@ -1,5 +1,17 @@ /* - * Copyright (c) 2023, FusionAuth, All Rights Reserved + * Copyright (c) 2023-2024, FusionAuth, All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. */ package io.fusionauth.client.json; diff --git a/src/main/java/io/fusionauth/client/json/IdentityTypeDeserializer.java b/src/main/java/io/fusionauth/client/json/IdentityTypeDeserializer.java new file mode 100644 index 00000000..71db9c3e --- /dev/null +++ b/src/main/java/io/fusionauth/client/json/IdentityTypeDeserializer.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2024, FusionAuth, All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + */ +package io.fusionauth.client.json; + +import java.io.IOException; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import io.fusionauth.domain.IdentityType; + +/** + * Deserialize an IdentityType. + * + * @author Daniel DeGroff + */ +public class IdentityTypeDeserializer extends JsonDeserializer { + @Override + public IdentityType deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + return IdentityType.of(p.getText()); + } +} diff --git a/src/main/java/io/fusionauth/client/json/IdentityTypeSerializer.java b/src/main/java/io/fusionauth/client/json/IdentityTypeSerializer.java new file mode 100644 index 00000000..df8ece24 --- /dev/null +++ b/src/main/java/io/fusionauth/client/json/IdentityTypeSerializer.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2024, FusionAuth, All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + */ +package io.fusionauth.client.json; + +import java.io.IOException; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.jsontype.TypeSerializer; +import io.fusionauth.domain.IdentityType; + +/** + * Serialize an IdentityType + * + * @author Daniel DeGroff + */ +public class IdentityTypeSerializer extends JsonSerializer { + @Override + public void serialize(IdentityType value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + gen.writeString(value.name); + } + + @Override + public void serializeWithType(IdentityType value, JsonGenerator gen, SerializerProvider serializers, TypeSerializer typeSer) throws IOException { + super.serializeWithType(value, gen, serializers, typeSer); + } +} diff --git a/src/main/java/io/fusionauth/client/json/MessageTemplateJacksonHelper.java b/src/main/java/io/fusionauth/client/json/MessageTemplateJacksonHelper.java index 8a90e51f..93cc4a3c 100644 --- a/src/main/java/io/fusionauth/client/json/MessageTemplateJacksonHelper.java +++ b/src/main/java/io/fusionauth/client/json/MessageTemplateJacksonHelper.java @@ -1,5 +1,17 @@ /* - * Copyright (c) 2020-2022, FusionAuth, All Rights Reserved + * Copyright (c) 2020-2024, FusionAuth, All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. */ package io.fusionauth.client.json; @@ -14,6 +26,9 @@ import io.fusionauth.domain.message.MessageType; import io.fusionauth.domain.message.sms.SMSMessageTemplate; +/** + * @author Mikey Sleevi + */ public class MessageTemplateJacksonHelper { public static MessageType extractType(DeserializationContext ctxt, JsonParser p, JsonNode templateNode) diff --git a/src/main/java/io/fusionauth/client/json/MessageTemplateRequestDeserializer.java b/src/main/java/io/fusionauth/client/json/MessageTemplateRequestDeserializer.java index 148c7f8b..e0b7639d 100644 --- a/src/main/java/io/fusionauth/client/json/MessageTemplateRequestDeserializer.java +++ b/src/main/java/io/fusionauth/client/json/MessageTemplateRequestDeserializer.java @@ -1,5 +1,17 @@ /* - * Copyright (c) 2020-2023, FusionAuth, All Rights Reserved + * Copyright (c) 2020-2024, FusionAuth, All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. */ package io.fusionauth.client.json; diff --git a/src/main/java/io/fusionauth/client/json/MessageTemplateResponseDeserializer.java b/src/main/java/io/fusionauth/client/json/MessageTemplateResponseDeserializer.java index 4c8a9394..3f49e931 100644 --- a/src/main/java/io/fusionauth/client/json/MessageTemplateResponseDeserializer.java +++ b/src/main/java/io/fusionauth/client/json/MessageTemplateResponseDeserializer.java @@ -1,5 +1,17 @@ /* - * Copyright (c) 2020-2022, FusionAuth, All Rights Reserved + * Copyright (c) 2020-2024, FusionAuth, All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. */ package io.fusionauth.client.json; diff --git a/src/main/java/io/fusionauth/client/json/MessengerJacksonHelper.java b/src/main/java/io/fusionauth/client/json/MessengerJacksonHelper.java index b7d83f01..a3515b32 100644 --- a/src/main/java/io/fusionauth/client/json/MessengerJacksonHelper.java +++ b/src/main/java/io/fusionauth/client/json/MessengerJacksonHelper.java @@ -1,5 +1,17 @@ /* - * Copyright (c) 2020-2022, FusionAuth, All Rights Reserved + * Copyright (c) 2020-2024, FusionAuth, All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. */ package io.fusionauth.client.json; diff --git a/src/main/java/io/fusionauth/client/json/MessengerRequestDeserializer.java b/src/main/java/io/fusionauth/client/json/MessengerRequestDeserializer.java index 7a3bd5e9..4625808f 100644 --- a/src/main/java/io/fusionauth/client/json/MessengerRequestDeserializer.java +++ b/src/main/java/io/fusionauth/client/json/MessengerRequestDeserializer.java @@ -1,5 +1,17 @@ /* - * Copyright (c) 2020-2023, FusionAuth, All Rights Reserved + * Copyright (c) 2020-2024, FusionAuth, All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. */ package io.fusionauth.client.json; diff --git a/src/main/java/io/fusionauth/client/json/MessengerResponseDeserializer.java b/src/main/java/io/fusionauth/client/json/MessengerResponseDeserializer.java index cd416dc8..6e2a4896 100644 --- a/src/main/java/io/fusionauth/client/json/MessengerResponseDeserializer.java +++ b/src/main/java/io/fusionauth/client/json/MessengerResponseDeserializer.java @@ -1,5 +1,17 @@ /* - * Copyright (c) 2019-2022, FusionAuth, All Rights Reserved + * Copyright (c) 2019-2024, FusionAuth, All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. */ package io.fusionauth.client.json; diff --git a/src/main/java/io/fusionauth/client/json/PreviewMessageTemplateRequestDeserializer.java b/src/main/java/io/fusionauth/client/json/PreviewMessageTemplateRequestDeserializer.java index 22a7183c..37a54854 100644 --- a/src/main/java/io/fusionauth/client/json/PreviewMessageTemplateRequestDeserializer.java +++ b/src/main/java/io/fusionauth/client/json/PreviewMessageTemplateRequestDeserializer.java @@ -1,5 +1,17 @@ /* - * Copyright (c) 2021-2022, FusionAuth, All Rights Reserved + * Copyright (c) 2021-2024, FusionAuth, All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. */ package io.fusionauth.client.json; diff --git a/src/main/java/io/fusionauth/client/json/WebhookEventDeserializer.java b/src/main/java/io/fusionauth/client/json/WebhookEventDeserializer.java index 46e5327e..a52bd8c9 100644 --- a/src/main/java/io/fusionauth/client/json/WebhookEventDeserializer.java +++ b/src/main/java/io/fusionauth/client/json/WebhookEventDeserializer.java @@ -1,9 +1,22 @@ /* * Copyright (c) 2024, FusionAuth, All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. */ package io.fusionauth.client.json; import java.io.IOException; +import java.lang.reflect.Constructor; import java.util.Arrays; import java.util.stream.Collectors; @@ -50,7 +63,10 @@ private BaseEvent extractEventType(DeserializationContext ctxt, JsonParser p, Js // Assuming all of our events following this naming schema '{EventType}Event' String className = BaseEvent.class.getPackage().getName() + "." + eventType.name() + "Event"; try { - return (BaseEvent) Class.forName(className).getConstructor().newInstance(); + Constructor constructor = Class.forName(className).getDeclaredConstructor(); + // the default constructor for an event can be private + constructor.setAccessible(true); + return (BaseEvent) constructor.newInstance(); } catch (Exception e) { throw new IllegalStateException("Unexpected type [" + eventType + "]. This is a FusionAuth bug, could not instantiate class [" + className + "]."); } diff --git a/src/main/java/io/fusionauth/domain/Application.java b/src/main/java/io/fusionauth/domain/Application.java index b1bb38b2..ded5a63f 100644 --- a/src/main/java/io/fusionauth/domain/Application.java +++ b/src/main/java/io/fusionauth/domain/Application.java @@ -86,6 +86,8 @@ public class Application implements Buildable, Tenantable { public PasswordlessConfiguration passwordlessConfiguration = new PasswordlessConfiguration(); + public ApplicationPhoneConfiguration phoneConfiguration = new ApplicationPhoneConfiguration(); + public RegistrationConfiguration registrationConfiguration = new RegistrationConfiguration(); public ApplicationRegistrationDeletePolicy registrationDeletePolicy = new ApplicationRegistrationDeletePolicy(); @@ -141,6 +143,7 @@ public Application(Application other) { this.name = other.name; this.oauthConfiguration = new OAuth2Configuration(other.oauthConfiguration); this.passwordlessConfiguration = new PasswordlessConfiguration(other.passwordlessConfiguration); + this.phoneConfiguration = new ApplicationPhoneConfiguration(other.phoneConfiguration); this.registrationConfiguration = new RegistrationConfiguration(other.registrationConfiguration); this.registrationDeletePolicy = new ApplicationRegistrationDeletePolicy(other.registrationDeletePolicy); this.roles.addAll(other.roles.stream().map(ApplicationRole::new).collect(Collectors.toList())); @@ -208,6 +211,7 @@ public boolean equals(Object o) { Objects.equals(name, that.name) && Objects.equals(oauthConfiguration, that.oauthConfiguration) && Objects.equals(passwordlessConfiguration, that.passwordlessConfiguration) && + Objects.equals(phoneConfiguration, that.phoneConfiguration) && Objects.equals(registrationConfiguration, that.registrationConfiguration) && Objects.equals(registrationDeletePolicy, that.registrationDeletePolicy) && Objects.equals(roles, that.roles) && @@ -260,7 +264,7 @@ public boolean hasDefaultRole() { @Override public int hashCode() { // active is omitted - return Objects.hash(accessControlConfiguration, authenticationTokenConfiguration, cleanSpeakConfiguration, data, emailConfiguration, externalIdentifierConfiguration, formConfiguration, id, insertInstant, jwtConfiguration, lambdaConfiguration, lastUpdateInstant, loginConfiguration, multiFactorConfiguration, name, oauthConfiguration, passwordlessConfiguration, registrationConfiguration, registrationDeletePolicy, roles, samlv2Configuration, scopes, state, tenantId, themeId, universalConfiguration, unverified, verificationEmailTemplateId, verificationStrategy, verifyRegistration, webAuthnConfiguration); + return Objects.hash(accessControlConfiguration, authenticationTokenConfiguration, cleanSpeakConfiguration, data, emailConfiguration, externalIdentifierConfiguration, formConfiguration, id, insertInstant, jwtConfiguration, lambdaConfiguration, lastUpdateInstant, loginConfiguration, multiFactorConfiguration, name, oauthConfiguration, passwordlessConfiguration, phoneConfiguration, registrationConfiguration, registrationDeletePolicy, roles, samlv2Configuration, scopes, state, tenantId, themeId, universalConfiguration, unverified, verificationEmailTemplateId, verificationStrategy, verifyRegistration, webAuthnConfiguration); } public void normalize() { @@ -644,8 +648,11 @@ public String toString() { return ToString.toString(this); } + // Note that this is only used for basic self-service registration to indicate email, phoneNumber, or username. + // This is separate from IdentityType. public enum LoginIdType { email, + phoneNumber, username } diff --git a/src/main/java/io/fusionauth/domain/ApplicationPhoneConfiguration.java b/src/main/java/io/fusionauth/domain/ApplicationPhoneConfiguration.java new file mode 100644 index 00000000..a2df1e25 --- /dev/null +++ b/src/main/java/io/fusionauth/domain/ApplicationPhoneConfiguration.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2024-2025, FusionAuth, All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + */ +package io.fusionauth.domain; + +import java.util.Objects; +import java.util.UUID; + +import com.inversoft.json.JacksonConstructor; + +/** + * Hold application phone configuration for template overrides. + */ +public class ApplicationPhoneConfiguration implements Buildable { + + public UUID forgotPasswordTemplateId; + + public UUID identityUpdateTemplateId; + + public UUID loginIdInUseOnCreateTemplateId; + + public UUID loginIdInUseOnUpdateTemplateId; + + public UUID loginNewDeviceTemplateId; + + public UUID loginSuspiciousTemplateId; + + public UUID passwordResetSuccessTemplateId; + + public UUID passwordUpdateTemplateId; + + public UUID passwordlessTemplateId; + + public UUID setPasswordTemplateId; + + public UUID twoFactorMethodAddTemplateId; + + public UUID twoFactorMethodRemoveTemplateId; + + public UUID verificationCompleteTemplateId; + + public UUID verificationTemplateId; + + @JacksonConstructor + public ApplicationPhoneConfiguration() { + } + + public ApplicationPhoneConfiguration(ApplicationPhoneConfiguration other) { + this.forgotPasswordTemplateId = other.forgotPasswordTemplateId; + this.identityUpdateTemplateId = other.identityUpdateTemplateId; + this.loginIdInUseOnCreateTemplateId = other.loginIdInUseOnCreateTemplateId; + this.loginIdInUseOnUpdateTemplateId = other.loginIdInUseOnUpdateTemplateId; + this.loginNewDeviceTemplateId = other.loginNewDeviceTemplateId; + this.loginSuspiciousTemplateId = other.loginSuspiciousTemplateId; + this.passwordResetSuccessTemplateId = other.passwordResetSuccessTemplateId; + this.passwordUpdateTemplateId = other.passwordUpdateTemplateId; + this.passwordlessTemplateId = other.passwordlessTemplateId; + this.setPasswordTemplateId = other.setPasswordTemplateId; + this.twoFactorMethodAddTemplateId = other.twoFactorMethodAddTemplateId; + this.twoFactorMethodRemoveTemplateId = other.twoFactorMethodRemoveTemplateId; + this.verificationCompleteTemplateId = other.verificationCompleteTemplateId; + this.verificationTemplateId = other.verificationTemplateId; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ApplicationPhoneConfiguration)) { + return false; + } + ApplicationPhoneConfiguration that = (ApplicationPhoneConfiguration) o; + return Objects.equals(forgotPasswordTemplateId, that.forgotPasswordTemplateId) && + Objects.equals(identityUpdateTemplateId, that.identityUpdateTemplateId) && + Objects.equals(loginIdInUseOnCreateTemplateId, that.loginIdInUseOnCreateTemplateId) && + Objects.equals(loginIdInUseOnUpdateTemplateId, that.loginIdInUseOnUpdateTemplateId) && + Objects.equals(loginNewDeviceTemplateId, that.loginNewDeviceTemplateId) && + Objects.equals(loginSuspiciousTemplateId, that.loginSuspiciousTemplateId) && + Objects.equals(passwordResetSuccessTemplateId, that.passwordResetSuccessTemplateId) && + Objects.equals(passwordUpdateTemplateId, that.passwordUpdateTemplateId) && + Objects.equals(passwordlessTemplateId, that.passwordlessTemplateId) && + Objects.equals(setPasswordTemplateId, that.setPasswordTemplateId) && + Objects.equals(twoFactorMethodAddTemplateId, that.twoFactorMethodAddTemplateId) && + Objects.equals(twoFactorMethodRemoveTemplateId, that.twoFactorMethodRemoveTemplateId) && + Objects.equals(verificationCompleteTemplateId, that.verificationCompleteTemplateId) && + Objects.equals(verificationTemplateId, that.verificationTemplateId); + } + + @Override + public int hashCode() { + return Objects.hash(forgotPasswordTemplateId, identityUpdateTemplateId, loginIdInUseOnCreateTemplateId, loginIdInUseOnUpdateTemplateId, loginNewDeviceTemplateId, loginSuspiciousTemplateId, passwordResetSuccessTemplateId, passwordUpdateTemplateId, passwordlessTemplateId, setPasswordTemplateId, twoFactorMethodAddTemplateId, twoFactorMethodRemoveTemplateId, verificationCompleteTemplateId, verificationTemplateId); + } +} diff --git a/src/main/java/io/fusionauth/domain/ContentStatus.java b/src/main/java/io/fusionauth/domain/ContentStatus.java index 8ddcd3e5..9bbf1eb2 100644 --- a/src/main/java/io/fusionauth/domain/ContentStatus.java +++ b/src/main/java/io/fusionauth/domain/ContentStatus.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, FusionAuth, All Rights Reserved + * Copyright (c) 2018-2024, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/io/fusionauth/domain/DisplayableRawLogin.java b/src/main/java/io/fusionauth/domain/DisplayableRawLogin.java index 5df641dd..8aeb8fb0 100644 --- a/src/main/java/io/fusionauth/domain/DisplayableRawLogin.java +++ b/src/main/java/io/fusionauth/domain/DisplayableRawLogin.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, FusionAuth, All Rights Reserved + * Copyright (c) 2018-2024, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,8 +17,6 @@ import java.util.Objects; -import com.inversoft.json.ToString; - /** * A displayable raw login that includes application name and user loginId. * @@ -31,31 +29,25 @@ public class DisplayableRawLogin extends RawLogin implements Buildable { - @JsonIgnore - @SuppressWarnings("unused") - public static List EmailTemplateIdFieldNames = Arrays.stream(EmailConfiguration.class.getDeclaredFields()) - .map(Field::getName) - .filter(name -> name.endsWith("EmailTemplateId")) - .sorted() - .collect(Collectors.toList()); - - @JsonIgnore - @SuppressWarnings("unused") - public static List EmailTemplateIdFields = Arrays.stream(EmailConfiguration.class.getDeclaredFields()) - .filter(f -> f.getName().endsWith("EmailTemplateId")) - .sorted(Comparator.comparing(Field::getName)) - .collect(Collectors.toList()); - public List additionalHeaders = new ArrayList<>(); public boolean debug; @@ -104,7 +84,7 @@ public class EmailConfiguration implements Buildable { public UUID verificationEmailTemplateId; - public VerificationStrategy verificationStrategy; + public VerificationStrategy verificationStrategy = VerificationStrategy.ClickableLink; public boolean verifyEmail; diff --git a/src/main/java/io/fusionauth/domain/ExternalIdentifierConfiguration.java b/src/main/java/io/fusionauth/domain/ExternalIdentifierConfiguration.java index 35dc3301..435ccb62 100644 --- a/src/main/java/io/fusionauth/domain/ExternalIdentifierConfiguration.java +++ b/src/main/java/io/fusionauth/domain/ExternalIdentifierConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2024, FusionAuth, All Rights Reserved + * Copyright (c) 2019-2025, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -48,10 +48,18 @@ public class ExternalIdentifierConfiguration implements Buildable { + public static Map Provided = new HashMap<>(3); + + // type 0 in DB + public static IdentityType email = new IdentityType("email"); + + // type 2 in DB + public static IdentityType phoneNumber = new IdentityType("phoneNumber"); + + // type 1 in DB + public static IdentityType username = new IdentityType("username"); + + public String name; + + public IdentityType(IdentityType other) { + this.name = other != null ? other.name : null; + } + + private IdentityType(String name) { + this.name = name; + } + + public static IdentityType of(String name) { + if (name == null) { + return null; + } + + // returns null if not a Provided type + return Provided.get(name); + } + + @Override + public int compareTo(IdentityType o) { + return name.compareTo(o.name); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + IdentityType that = (IdentityType) o; + return Objects.equals(name, that.name); + } + + @Override + public int hashCode() { + return Objects.hash(name); + } + + public boolean is(IdentityType type) { + return this == type || equals(type); + } + + public boolean is(String name) { + return this.name.equals(name); + } + + public boolean isNot(String name) { + return !is(name); + } + + public boolean isNot(IdentityType type) { + return !is(type); + } + + @Override + public String toString() { + return name; + } + + static { + Provided.put(email.name, email); + Provided.put(username.name, username); + Provided.put(phoneNumber.name, phoneNumber); + } +} diff --git a/src/main/java/io/fusionauth/domain/IdentityVerifiedReason.java b/src/main/java/io/fusionauth/domain/IdentityVerifiedReason.java new file mode 100644 index 00000000..6a020103 --- /dev/null +++ b/src/main/java/io/fusionauth/domain/IdentityVerifiedReason.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2024-2025, FusionAuth, All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + */ +package io.fusionauth.domain; + +/** + * Models the reason that {@link UserIdentity#verified} was set to true or false. + * + * @author Brady Wied + */ +public enum IdentityVerifiedReason { + /** + * Verification was skipped due to the `skipVerification` parameter + */ + Skipped, // 0 + /** + * Identity was created via an identity provider or a connector + */ + Trusted, // 1 + /** + * We don't know how to verify this identity type + */ + Unverifiable, // 2 + /** + * Happened implicitly by sending a set password or passwordless message that can only be + * acted on if the user proves control of the identity. + */ + Implicit, // 3 + /** + * No verification has been performed yet + */ + Pending, // 4 + /** + * Verification was performed by FusionAuth + */ + Completed, // 5 + /** + * Tenant policy did not require verifying this identity + */ + Disabled, // 6 + /** + * Verification was performed by administrative action + */ + Administrative, // 7 + + /** + * User was imported so verification was not performed by FusionAuth. + */ + Import // 8 +} diff --git a/src/main/java/io/fusionauth/domain/OpenIdConfiguration.java b/src/main/java/io/fusionauth/domain/OpenIdConfiguration.java index 398df4db..0377057c 100644 --- a/src/main/java/io/fusionauth/domain/OpenIdConfiguration.java +++ b/src/main/java/io/fusionauth/domain/OpenIdConfiguration.java @@ -35,7 +35,7 @@ public class OpenIdConfiguration implements Buildable { @SuppressWarnings("SpellCheckingInspection") public boolean backchannel_logout_supported = false; - public List claims_supported = new ArrayList<>(Arrays.asList("applicationId", "at_hash", "aud", "authenticationType", "birthdate", "c_hash", "email", "email_verified", "exp", "family_name", "given_name", "iat", "iss", "jti", "middle_name", "name", "nbf", "nonce", "phone_number", "picture", "preferred_username", "roles", "sub")); + public List claims_supported = new ArrayList<>(Arrays.asList("applicationId", "at_hash", "aud", "authenticationType", "birthdate", "c_hash", "email", "email_verified", "exp", "family_name", "given_name", "iat", "iss", "jti", "middle_name", "name", "nbf", "nonce", "phone_number", "phone_number_verified", "picture", "preferred_username", "roles", "sub")); public String device_authorization_endpoint = "%s/oauth2/device_authorize"; diff --git a/src/main/java/io/fusionauth/domain/PasswordlessStrategy.java b/src/main/java/io/fusionauth/domain/PasswordlessStrategy.java new file mode 100644 index 00000000..d925155a --- /dev/null +++ b/src/main/java/io/fusionauth/domain/PasswordlessStrategy.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2021-2024, FusionAuth, All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + */ +package io.fusionauth.domain; + +/** + * @author Daniel DeGroff + */ +public enum PasswordlessStrategy { + ClickableLink, + FormField +} diff --git a/src/main/java/io/fusionauth/domain/PhoneUnverifiedOptions.java b/src/main/java/io/fusionauth/domain/PhoneUnverifiedOptions.java new file mode 100644 index 00000000..304738b8 --- /dev/null +++ b/src/main/java/io/fusionauth/domain/PhoneUnverifiedOptions.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2021-2025, FusionAuth, All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + */ +package io.fusionauth.domain; + +import java.util.Objects; + +import com.inversoft.json.JacksonConstructor; +import com.inversoft.json.ToString; + +/** + * Configuration for unverified phone number identities. + * + * @author Spencer Witt + */ +public class PhoneUnverifiedOptions implements Buildable { + public boolean allowPhoneNumberChangeWhenGated; + + public UnverifiedBehavior behavior = UnverifiedBehavior.Allow; + + @JacksonConstructor + public PhoneUnverifiedOptions() { + } + + public PhoneUnverifiedOptions(PhoneUnverifiedOptions other) { + this.allowPhoneNumberChangeWhenGated = other.allowPhoneNumberChangeWhenGated; + this.behavior = other.behavior; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + PhoneUnverifiedOptions that = (PhoneUnverifiedOptions) o; + return allowPhoneNumberChangeWhenGated == that.allowPhoneNumberChangeWhenGated && behavior == that.behavior; + } + + @Override + public int hashCode() { + return Objects.hash(allowPhoneNumberChangeWhenGated, behavior); + } + + @Override + public String toString() { + return ToString.toString(this); + } +} diff --git a/src/main/java/io/fusionauth/domain/RateLimitedRequestType.java b/src/main/java/io/fusionauth/domain/RateLimitedRequestType.java index 6068de31..187f327a 100644 --- a/src/main/java/io/fusionauth/domain/RateLimitedRequestType.java +++ b/src/main/java/io/fusionauth/domain/RateLimitedRequestType.java @@ -1,5 +1,17 @@ /* - * Copyright (c) 2021, FusionAuth, All Rights Reserved + * Copyright (c) 2021-2025, FusionAuth, All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. */ package io.fusionauth.domain; @@ -7,10 +19,13 @@ * @author Daniel DeGroff */ public enum RateLimitedRequestType { + FailedLogin, ForgotPassword, SendEmailVerification, SendPasswordless, SendRegistrationVerification, - SendTwoFactor + SendTwoFactor, + SendPhoneVerification, + SendPhonePasswordless } diff --git a/src/main/java/io/fusionauth/domain/SecureIdentity.java b/src/main/java/io/fusionauth/domain/SecureIdentity.java index 138e1140..2fa2dfe3 100644 --- a/src/main/java/io/fusionauth/domain/SecureIdentity.java +++ b/src/main/java/io/fusionauth/domain/SecureIdentity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2024, FusionAuth, All Rights Reserved + * Copyright (c) 2018-2025, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,8 @@ package io.fusionauth.domain; import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; import java.util.UUID; @@ -25,10 +27,13 @@ * @author Daniel DeGroff */ public class SecureIdentity { + public final List identities = new ArrayList<>(); + public ZonedDateTime breachedPasswordLastCheckedInstant; public BreachedPasswordStatus breachedPasswordStatus; + // Legacy field, this will represent the connector from the first identity, most likely email. public UUID connectorId = BaseConnectorConfiguration.FUSIONAUTH_CONNECTOR_ID; public String encryptionScheme; @@ -57,8 +62,16 @@ public class SecureIdentity { public ContentStatus usernameStatus = ContentStatus.ACTIVE; + /** + * @deprecated This value is still here for compatibility reasons but starting in FusionAuth 1.59.0, use the verified + * boolean value on the identities collection entry of type `email`. See {@link UserIdentity#verified} + */ + @Deprecated // JDK 8 compatible/client library (since = "1.59.0") public boolean verified; + /** + * The instant when one of a user's identities was first verified. Once this value is set, it will not change. + */ public ZonedDateTime verifiedInstant; @Override @@ -78,6 +91,7 @@ public boolean equals(Object o) { Objects.equals(encryptionScheme, that.encryptionScheme) && Objects.equals(factor, that.factor) && Objects.equals(id, that.id) && + Objects.equals(identities, that.identities) && Objects.equals(lastLoginInstant, that.lastLoginInstant) && Objects.equals(password, that.password) && passwordChangeReason == that.passwordChangeReason && @@ -97,6 +111,7 @@ public int hashCode() { encryptionScheme, factor, id, + identities, lastLoginInstant, password, passwordChangeReason, diff --git a/src/main/java/io/fusionauth/domain/SendSetPasswordIdentityType.java b/src/main/java/io/fusionauth/domain/SendSetPasswordIdentityType.java new file mode 100644 index 00000000..6d6c044e --- /dev/null +++ b/src/main/java/io/fusionauth/domain/SendSetPasswordIdentityType.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2025-2025, FusionAuth, All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + */ +package io.fusionauth.domain; + +/** + * Used to indicate which identity type a password "request" might go to. It could be + * used for send set passwords or send password resets. + */ +public enum SendSetPasswordIdentityType { + email, + phone, + doNotSend +} diff --git a/src/main/java/io/fusionauth/domain/Tenant.java b/src/main/java/io/fusionauth/domain/Tenant.java index 00c0641e..17a7d773 100644 --- a/src/main/java/io/fusionauth/domain/Tenant.java +++ b/src/main/java/io/fusionauth/domain/Tenant.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2024, FusionAuth, All Rights Reserved + * Copyright (c) 2019-2025, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -104,6 +104,8 @@ public class Tenant implements Buildable { public PasswordValidationRules passwordValidationRules = new PasswordValidationRules(); + public TenantPhoneConfiguration phoneConfiguration = new TenantPhoneConfiguration(); + public TenantRateLimitConfiguration rateLimitConfiguration = new TenantRateLimitConfiguration(); public TenantRegistrationConfiguration registrationConfiguration = new TenantRegistrationConfiguration(); @@ -154,6 +156,7 @@ public Tenant(Tenant other) { this.oauthConfiguration = new TenantOAuth2Configuration(other.oauthConfiguration); this.passwordEncryptionConfiguration = new PasswordEncryptionConfiguration(other.passwordEncryptionConfiguration); this.passwordValidationRules = new PasswordValidationRules(other.passwordValidationRules); + this.phoneConfiguration = new TenantPhoneConfiguration(other.phoneConfiguration); this.rateLimitConfiguration = new TenantRateLimitConfiguration(other.rateLimitConfiguration); this.registrationConfiguration = new TenantRegistrationConfiguration(other.registrationConfiguration); this.scimServerConfiguration = new TenantSCIMServerConfiguration(other.scimServerConfiguration); @@ -200,6 +203,7 @@ public boolean equals(Object o) { Objects.equals(name, tenant.name) && Objects.equals(passwordEncryptionConfiguration, tenant.passwordEncryptionConfiguration) && Objects.equals(passwordValidationRules, tenant.passwordValidationRules) && + Objects.equals(phoneConfiguration, tenant.phoneConfiguration) && Objects.equals(rateLimitConfiguration, tenant.rateLimitConfiguration) && Objects.equals(registrationConfiguration, tenant.registrationConfiguration) && Objects.equals(scimServerConfiguration, tenant.scimServerConfiguration) && @@ -243,6 +247,7 @@ public int hashCode() { name, passwordEncryptionConfiguration, passwordValidationRules, + phoneConfiguration, rateLimitConfiguration, registrationConfiguration, scimServerConfiguration, diff --git a/src/main/java/io/fusionauth/domain/TenantPhoneConfiguration.java b/src/main/java/io/fusionauth/domain/TenantPhoneConfiguration.java new file mode 100644 index 00000000..64406e55 --- /dev/null +++ b/src/main/java/io/fusionauth/domain/TenantPhoneConfiguration.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2024-2025, FusionAuth, All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + */ +package io.fusionauth.domain; + +import java.util.Objects; +import java.util.UUID; + +import com.inversoft.json.JacksonConstructor; + +/** + * Hold tenant phone configuration for passwordless and verification cases. + * + * @author Brady Wied + */ +public class TenantPhoneConfiguration implements Buildable { + + public UUID forgotPasswordTemplateId; + + public UUID identityUpdateTemplateId; + + public boolean implicitPhoneVerificationAllowed = true; + + public UUID loginIdInUseOnCreateTemplateId; + + public UUID loginIdInUseOnUpdateTemplateId; + + public UUID loginNewDeviceTemplateId; + + public UUID loginSuspiciousTemplateId; + + public UUID messengerId; + + public UUID passwordResetSuccessTemplateId; + + public UUID passwordUpdateTemplateId; + + public UUID passwordlessTemplateId; + + public UUID setPasswordTemplateId; + + public UUID twoFactorMethodAddTemplateId; + + public UUID twoFactorMethodRemoveTemplateId; + + public PhoneUnverifiedOptions unverified = new PhoneUnverifiedOptions(); + + public UUID verificationCompleteTemplateId; + + public VerificationStrategy verificationStrategy = VerificationStrategy.ClickableLink; + + public UUID verificationTemplateId; + + public boolean verifyPhoneNumber; + + @JacksonConstructor + public TenantPhoneConfiguration() { + } + + public TenantPhoneConfiguration(TenantPhoneConfiguration other) { + this.forgotPasswordTemplateId = other.forgotPasswordTemplateId; + this.identityUpdateTemplateId = other.identityUpdateTemplateId; + this.implicitPhoneVerificationAllowed = other.implicitPhoneVerificationAllowed; + this.loginIdInUseOnCreateTemplateId = other.loginIdInUseOnCreateTemplateId; + this.loginIdInUseOnUpdateTemplateId = other.loginIdInUseOnUpdateTemplateId; + this.loginNewDeviceTemplateId = other.loginNewDeviceTemplateId; + this.loginSuspiciousTemplateId = other.loginSuspiciousTemplateId; + this.messengerId = other.messengerId; + this.passwordResetSuccessTemplateId = other.passwordResetSuccessTemplateId; + this.passwordUpdateTemplateId = other.passwordUpdateTemplateId; + this.passwordlessTemplateId = other.passwordlessTemplateId; + this.setPasswordTemplateId = other.setPasswordTemplateId; + this.twoFactorMethodAddTemplateId = other.twoFactorMethodAddTemplateId; + this.twoFactorMethodRemoveTemplateId = other.twoFactorMethodRemoveTemplateId; + this.unverified = new PhoneUnverifiedOptions(other.unverified); + this.verificationCompleteTemplateId = other.verificationCompleteTemplateId; + this.verificationStrategy = other.verificationStrategy; + this.verificationTemplateId = other.verificationTemplateId; + this.verifyPhoneNumber = other.verifyPhoneNumber; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof TenantPhoneConfiguration)) { + return false; + } + TenantPhoneConfiguration that = (TenantPhoneConfiguration) o; + return implicitPhoneVerificationAllowed == that.implicitPhoneVerificationAllowed && + verifyPhoneNumber == that.verifyPhoneNumber && + Objects.equals(forgotPasswordTemplateId, that.forgotPasswordTemplateId) && + Objects.equals(identityUpdateTemplateId, that.identityUpdateTemplateId) && + Objects.equals(loginIdInUseOnCreateTemplateId, that.loginIdInUseOnCreateTemplateId) && + Objects.equals(loginIdInUseOnUpdateTemplateId, that.loginIdInUseOnUpdateTemplateId) && + Objects.equals(loginNewDeviceTemplateId, that.loginNewDeviceTemplateId) && + Objects.equals(loginSuspiciousTemplateId, that.loginSuspiciousTemplateId) && + Objects.equals(messengerId, that.messengerId) && + Objects.equals(passwordResetSuccessTemplateId, that.passwordResetSuccessTemplateId) && + Objects.equals(passwordUpdateTemplateId, that.passwordUpdateTemplateId) && + Objects.equals(passwordlessTemplateId, that.passwordlessTemplateId) && + Objects.equals(setPasswordTemplateId, that.setPasswordTemplateId) && + Objects.equals(twoFactorMethodAddTemplateId, that.twoFactorMethodAddTemplateId) && + Objects.equals(twoFactorMethodRemoveTemplateId, that.twoFactorMethodRemoveTemplateId) && + Objects.equals(unverified, that.unverified) && + Objects.equals(verificationCompleteTemplateId, that.verificationCompleteTemplateId) && + verificationStrategy == that.verificationStrategy && + Objects.equals(verificationTemplateId, that.verificationTemplateId) && + verifyPhoneNumber == that.verifyPhoneNumber; + } + + @Override + public int hashCode() { + return Objects.hash(forgotPasswordTemplateId, identityUpdateTemplateId, implicitPhoneVerificationAllowed, loginIdInUseOnCreateTemplateId, loginIdInUseOnUpdateTemplateId, loginNewDeviceTemplateId, loginSuspiciousTemplateId, messengerId, passwordResetSuccessTemplateId, passwordUpdateTemplateId, passwordlessTemplateId, setPasswordTemplateId, twoFactorMethodAddTemplateId, twoFactorMethodRemoveTemplateId, unverified, verificationCompleteTemplateId, verificationStrategy, verificationTemplateId, verifyPhoneNumber); + } +} diff --git a/src/main/java/io/fusionauth/domain/TenantRateLimitConfiguration.java b/src/main/java/io/fusionauth/domain/TenantRateLimitConfiguration.java index 6ac9680b..fc893236 100644 --- a/src/main/java/io/fusionauth/domain/TenantRateLimitConfiguration.java +++ b/src/main/java/io/fusionauth/domain/TenantRateLimitConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, FusionAuth, All Rights Reserved + * Copyright (c) 2021-2025, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,6 +33,10 @@ public class TenantRateLimitConfiguration implements Buildable { "passwordComplete", "passwordForgot", "passwordSent", + "phoneComplete", + "phoneSent", + "phoneVerificationRequired", + "phoneVerify", "registrationComplete", "registrationSent", "registrationVerificationRequired", @@ -392,6 +400,14 @@ public static class Templates implements Buildable { public String passwordSent; + public String phoneComplete; + + public String phoneSent; + + public String phoneVerificationRequired; + + public String phoneVerify; + public String registrationComplete; public String registrationSent; @@ -448,6 +464,10 @@ public Templates(Templates other) { this.passwordComplete = other.passwordComplete; this.passwordForgot = other.passwordForgot; this.passwordSent = other.passwordSent; + this.phoneComplete = other.phoneComplete; + this.phoneSent = other.phoneSent; + this.phoneVerificationRequired = other.phoneVerificationRequired; + this.phoneVerify = other.phoneVerify; this.registrationComplete = other.registrationComplete; this.registrationSent = other.registrationSent; this.registrationVerificationRequired = other.registrationVerificationRequired; @@ -505,6 +525,10 @@ public boolean equals(Object o) { Objects.equals(passwordComplete, that.passwordComplete) && Objects.equals(passwordForgot, that.passwordForgot) && Objects.equals(passwordSent, that.passwordSent) && + Objects.equals(phoneComplete, that.phoneComplete) && + Objects.equals(phoneSent, that.phoneSent) && + Objects.equals(phoneVerificationRequired, that.phoneVerificationRequired) && + Objects.equals(phoneVerify, that.phoneVerify) && Objects.equals(registrationComplete, that.registrationComplete) && Objects.equals(registrationSent, that.registrationSent) && Objects.equals(registrationVerificationRequired, that.registrationVerificationRequired) && @@ -576,6 +600,10 @@ public int hashCode() { passwordComplete, passwordForgot, passwordSent, + phoneComplete, + phoneSent, + phoneVerificationRequired, + phoneVerify, registrationComplete, registrationSent, registrationVerificationRequired, @@ -626,6 +654,10 @@ public void normalize() { passwordComplete = lineReturns(trimToNull(passwordComplete)); passwordForgot = lineReturns(trimToNull(passwordForgot)); passwordSent = lineReturns(trimToNull(passwordSent)); + phoneComplete = lineReturns(trimToNull(phoneComplete)); + phoneSent = lineReturns(trimToNull(phoneSent)); + phoneVerificationRequired = lineReturns(trimToNull(phoneVerificationRequired)); + phoneVerify = lineReturns(trimToNull(phoneVerify)); registrationComplete = lineReturns(trimToNull(registrationComplete)); registrationSent = lineReturns(trimToNull(registrationSent)); registrationVerificationRequired = lineReturns(trimToNull(registrationVerificationRequired)); diff --git a/src/main/java/io/fusionauth/domain/User.java b/src/main/java/io/fusionauth/domain/User.java index 4f9499bc..33cf3689 100644 --- a/src/main/java/io/fusionauth/domain/User.java +++ b/src/main/java/io/fusionauth/domain/User.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2024, FusionAuth, All Rights Reserved + * Copyright (c) 2019-2025, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,6 +27,7 @@ import java.util.Locale; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; @@ -37,9 +38,10 @@ import io.fusionauth.domain.util.Normalizer; import static io.fusionauth.domain.util.Normalizer.toLowerCase; import static io.fusionauth.domain.util.Normalizer.trim; +import static io.fusionauth.domain.util.Normalizer.trimToNull; /** - * The global view of a User. This object contains all global information about the user including birthdate, registration information + * The public, global view of a User. This object contains all global information about the user including birthdate, registration information * preferred languages, global attributes, etc. * * @author Seth Musselman @@ -81,6 +83,8 @@ public class User extends SecureIdentity implements Buildable, Tenantable public String parentEmail; + public String phoneNumber; + public UUID tenantId; public ZoneId timezone; @@ -110,7 +114,8 @@ public User(User other) { this.lastLoginInstant = other.lastLoginInstant; this.lastUpdateInstant = other.lastUpdateInstant; this.lastName = other.lastName; - this.memberships.addAll(other.memberships.stream().map(GroupMember::new).collect(Collectors.toList())); + this.identities.addAll(other.identities.stream().map(UserIdentity::new).collect(Collectors.toCollection(ArrayList::new))); + this.memberships.addAll(other.memberships.stream().map(GroupMember::new).collect(Collectors.toCollection(ArrayList::new))); this.middleName = other.middleName; this.mobilePhone = other.mobilePhone; this.parentEmail = other.parentEmail; @@ -118,8 +123,9 @@ public User(User other) { this.passwordChangeReason = other.passwordChangeReason; this.passwordChangeRequired = other.passwordChangeRequired; this.passwordLastUpdateInstant = other.passwordLastUpdateInstant; + this.phoneNumber = other.phoneNumber; this.preferredLanguages.addAll(other.preferredLanguages); - this.registrations.addAll(other.registrations.stream().map(UserRegistration::new).collect(Collectors.toList())); + this.registrations.addAll(other.registrations.stream().map(UserRegistration::new).collect(Collectors.toCollection(ArrayList::new))); this.salt = other.salt; this.tenantId = other.tenantId; this.timezone = other.timezone; @@ -177,6 +183,7 @@ public boolean equals(Object o) { Objects.equals(middleName, user.middleName) && Objects.equals(mobilePhone, user.mobilePhone) && Objects.equals(parentEmail, user.parentEmail) && + Objects.equals(phoneNumber, user.phoneNumber) && Objects.equals(twoFactor, user.twoFactor) && Objects.equals(tenantId, user.tenantId) && Objects.equals(timezone, user.timezone); @@ -191,19 +198,18 @@ public int getAge() { return (int) birthDate.until(LocalDate.now(), ChronoUnit.YEARS); } - public GroupMember getGroupMemberForGroup(UUID id) { - return getMemberships().stream() - .filter(m -> m.id.equals(id)) - .findFirst() - .orElse(null); - } - /** - * @return return a single identity value preferring email over username. + * @return return a single identity value preferring email to username. */ @JsonIgnore public String getLogin() { - return email == null ? uniqueUsername : email; + if (email != null) { + return email; + } + if (uniqueUsername != null) { + return uniqueUsername; + } + return phoneNumber; } public List getMemberships() { @@ -243,6 +249,17 @@ public UUID getTenantId() { return tenantId; } + /** + * Whether the user contains an identity of the type + * + * @param identityType type to check + * @return true if exists, false if not + */ + public boolean hasIdentityType(IdentityType identityType) { + return identities.stream() + .anyMatch(ui -> ui.type.is(identityType)); + } + /** * Return true if user data is provided for this user or any registrations. * @@ -264,7 +281,8 @@ public boolean hasUserData() { @Override public int hashCode() { - return Objects.hash(super.hashCode(), preferredLanguages, memberships, registrations, active, birthDate, cleanSpeakId, data, email, expiry, firstName, fullName, imageUrl, insertInstant, lastName, lastUpdateInstant, middleName, mobilePhone, parentEmail, tenantId, timezone, twoFactor); + return Objects.hash(super.hashCode(), preferredLanguages, memberships, registrations, active, birthDate, cleanSpeakId, data, + email, expiry, firstName, fullName, imageUrl, insertInstant, lastName, lastUpdateInstant, middleName, mobilePhone, parentEmail, phoneNumber, tenantId, timezone, twoFactor); } /** @@ -302,7 +320,8 @@ public Locale lookupPreferredLanguage(UUID applicationId) { */ public void normalize() { Normalizer.removeEmpty(data); - email = toLowerCase(trim(email)); + email = toLowerCase(trimToNull(email)); + identities.forEach(UserIdentity::normalize); encryptionScheme = trim(encryptionScheme); firstName = trim(firstName); fullName = trim(fullName); @@ -310,13 +329,11 @@ public void normalize() { middleName = trim(middleName); mobilePhone = trim(mobilePhone); parentEmail = toLowerCase(trim(parentEmail)); + phoneNumber = trimToNull(phoneNumber); Normalizer.removeEmpty(preferredLanguages); Normalizer.deDuplicate(preferredLanguages); preferredLanguages.removeIf(l -> l.toString().equals("")); - username = trim(username); - if (username != null && username.length() == 0) { - username = null; - } + username = trimToNull(username); // Clear out groups that don't have groupId memberships.removeIf(m -> m.groupId == null); @@ -325,10 +342,91 @@ public void normalize() { getRegistrations().forEach(UserRegistration::normalize); } + /** + * Removes identities of the specified type + * + * @param identityType identity type to remove + */ + public void removeIdentitiesOfType(IdentityType identityType) { + identities.removeIf(i -> i.type.is(identityType)); + } + public void removeMembershipById(UUID groupId) { memberships.removeIf(m -> m.groupId.equals(groupId)); } + /** + * Replaces identities in the identities collection, by type, with the supplied identities. + * Any existing identities on the User that are not of the same type will be left alone + * + * @param replacementIdentities identities that should overwrite what is currently on the user + */ + public void replaceIdentities(List replacementIdentities) { + Set replaceTypes = replacementIdentities.stream() + .map(i -> i.type) + .collect(Collectors.toSet()); + + // first, remove the existing identities that our replacements will cover. we want to do this + // first since we may have multiple replacements of the same type and we don't want to step on each other + identities.removeIf(existing -> replaceTypes.contains(existing.type)); + + // now we can add our replacements + identities.addAll(replacementIdentities); + } + + /** + * @return email identity if it exists, if not, username if it exists, otherwise the first identity in the collection + */ + public UserIdentity resolveFirstIdentity() { + return Optional.ofNullable(resolveLegacyIdentity()) + .orElse(identities.stream() + .findFirst() + .orElse(null)); + } + + /** + * Return all identities that match the supplied type + * + * @param identityType type to check + * @return list of matching identities + */ + public List resolveIdentitiesOfType(IdentityType identityType) { + return identities.stream().filter(i -> i.type.is(identityType)) + .collect(Collectors.toList()); + } + + /** + * @return email identity if it exists, if not, username if it exists, otherwise null + */ + public UserIdentity resolveLegacyIdentity() { + return resolvePrimaryIdentity(IdentityType.email, IdentityType.username); + } + + /** + * Resolves the first (of the types provided) matching primary identity + * + * @param loginIdTypes types to match + * @return First matching or null if none match + */ + public UserIdentity resolvePrimaryIdentity(IdentityType... loginIdTypes) { + for (IdentityType loginIdType : loginIdTypes) { + UserIdentity result = resolvePrimaryIdentity(loginIdType); + if (result != null) { + return result; + } + } + return null; + } + + @JsonIgnore + public UserIdentity resolvePrimaryIdentity(IdentityType loginIdType) { + return identities.stream() + .filter(i -> i.primary) + .filter(i -> i.type.is(loginIdType)) + .findFirst() + .orElse(null); + } + /** * Clear out sensitive data. Password, salt, etc. * @@ -344,6 +442,9 @@ public User secure() { } public User sort() { + this.identities.sort(Comparator.comparing(i -> i.insertInstant, Comparator.nullsLast(Comparator.naturalOrder())) + .thenComparing(i -> i.type, Comparator.nullsLast(Comparator.naturalOrder())) + .thenComparing(i -> i.value, Comparator.nullsLast(Comparator.naturalOrder()))); this.registrations.sort(Comparator.comparing(ur -> ur.applicationId)); return this; } diff --git a/src/main/java/io/fusionauth/domain/UserIdentity.java b/src/main/java/io/fusionauth/domain/UserIdentity.java new file mode 100644 index 00000000..0d69709a --- /dev/null +++ b/src/main/java/io/fusionauth/domain/UserIdentity.java @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2018-2025, FusionAuth, All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + */ +package io.fusionauth.domain; + +import java.time.ZonedDateTime; +import java.util.Objects; +import java.util.UUID; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.inversoft.json.JacksonConstructor; +import com.inversoft.json.ToString; +import static io.fusionauth.domain.util.Normalizer.trim; + +/** + * @author Daniel DeGroff + */ +public class UserIdentity implements Buildable { + public String displayValue; + + // we don't need DB generated IDs in API responses + @JsonIgnore + public Long id; + + public ZonedDateTime insertInstant; + + public ZonedDateTime lastLoginInstant; + + public ZonedDateTime lastUpdateInstant; + + public ContentStatus moderationStatus; + + + // This is a internal only object and is not accessible to fusionauth-java-client. + // Anything in io.fusionauth.api.domain is private and not accessible via fusionauth-java-client. +// public UserIdentityStatus status; + + + public boolean primary = true; + + // technically redundant, since currently always returned on the User Object, but may need this for future Identity API + @JsonIgnore + public UUID tenantId; + + public IdentityType type; + + // technically redundant, since currently always returned on the User Object, but may need this for future Identity API + @JsonIgnore + public UUID userId; + + // When unique usernames are enabled, this value may be different than 'uniqueUsername' and in that case will represent the base username the user selected. + public String value; + + public boolean verified; + + public ZonedDateTime verifiedInstant; + + public IdentityVerifiedReason verifiedReason; + + public UserIdentity(UserIdentity other) { + this.id = other.id; + this.insertInstant = other.insertInstant; + this.lastLoginInstant = other.lastLoginInstant; + this.lastUpdateInstant = other.lastUpdateInstant; + this.moderationStatus = other.moderationStatus; + this.primary = other.primary; + this.type = new IdentityType(other.type); + this.tenantId = other.tenantId; + this.displayValue = other.displayValue; + this.userId = other.userId; + this.value = other.value; + this.verified = other.verified; + this.verifiedReason = other.verifiedReason; + this.verifiedInstant = other.verifiedInstant; + } + + @JacksonConstructor + public UserIdentity() { + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof UserIdentity)) { + return false; + } + UserIdentity that = (UserIdentity) o; + return Objects.equals(id, that.id) && + primary == that.primary && + verified == that.verified && + verifiedReason == that.verifiedReason && + Objects.equals(insertInstant, that.insertInstant) && + Objects.equals(lastLoginInstant, that.lastLoginInstant) && + Objects.equals(lastUpdateInstant, that.lastUpdateInstant) && + moderationStatus == that.moderationStatus && + Objects.equals(tenantId, that.tenantId) && + Objects.equals(type, that.type) && + Objects.equals(displayValue, that.displayValue) && + Objects.equals(userId, that.userId) && + Objects.equals(value, that.value) && + Objects.equals(verifiedInstant, that.verifiedInstant); + } + + @Override + public int hashCode() { + return Objects.hash(id, + insertInstant, + lastLoginInstant, + lastUpdateInstant, + moderationStatus, + primary, + tenantId, + type, + displayValue, + userId, + value, + verified, + verifiedReason, + verifiedInstant); + } + + public void normalize() { + value = trim(value); + if (value != null && value.length() == 0) { + value = null; + } + } + + @Override + public String toString() { + return ToString.toString(this); + } + + /** + * Note that it is ok if the verifiedReason is null, in this case, verification is required when verified is false. + * + * @return false if identity is either verified or verification does not matter. true if verification needs to be performed + */ + public boolean verificationRequired() { + return !verified && !(IdentityVerifiedReason.Disabled.equals(verifiedReason) || + IdentityVerifiedReason.Import.equals(verifiedReason) || + IdentityVerifiedReason.Trusted.equals(verifiedReason) || + IdentityVerifiedReason.Skipped.equals(verifiedReason) || + IdentityVerifiedReason.Unverifiable.equals(verifiedReason)); + } +} diff --git a/src/main/java/io/fusionauth/domain/VerificationStrategy.java b/src/main/java/io/fusionauth/domain/VerificationStrategy.java index d77ab486..b95a5a48 100644 --- a/src/main/java/io/fusionauth/domain/VerificationStrategy.java +++ b/src/main/java/io/fusionauth/domain/VerificationStrategy.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, FusionAuth, All Rights Reserved + * Copyright (c) 2021-2024, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,5 +20,17 @@ */ public enum VerificationStrategy { ClickableLink, - FormField + FormField; + + public static VerificationStrategy safeValueOf(String value) { + if (value == null) { + return null; + } + + try { + return VerificationStrategy.valueOf(value); + } catch (Exception e) { + return null; + } + } } diff --git a/src/main/java/io/fusionauth/domain/WebhookEventLog.java b/src/main/java/io/fusionauth/domain/WebhookEventLog.java index ad1c3148..ea9202a5 100644 --- a/src/main/java/io/fusionauth/domain/WebhookEventLog.java +++ b/src/main/java/io/fusionauth/domain/WebhookEventLog.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, FusionAuth, All Rights Reserved + * Copyright (c) 2024-2025, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/io/fusionauth/domain/api/LoginRequest.java b/src/main/java/io/fusionauth/domain/api/LoginRequest.java index 2b7cd313..3fd42825 100644 --- a/src/main/java/io/fusionauth/domain/api/LoginRequest.java +++ b/src/main/java/io/fusionauth/domain/api/LoginRequest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2019, FusionAuth, All Rights Reserved + * Copyright (c) 2018-2025, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,8 @@ */ package io.fusionauth.domain.api; +import java.util.ArrayList; +import java.util.List; import java.util.UUID; import com.inversoft.json.JacksonConstructor; @@ -29,6 +31,8 @@ public class LoginRequest extends BaseLoginRequest implements Buildable { public String loginId; + public List loginIdTypes = new ArrayList<>(); + public String oneTimePassword; public String password; @@ -63,4 +67,12 @@ public LoginRequest(EventInfo eventInfo, UUID applicationId, String loginId, Str this.loginId = loginId; this.password = password; } -} \ No newline at end of file + + public LoginRequest(EventInfo eventInfo, UUID applicationId, String loginId, List loginIdTypes, String password) { + super(eventInfo); + this.applicationId = applicationId; + this.loginId = loginId; + this.loginIdTypes = loginIdTypes; + this.password = password; + } +} diff --git a/src/main/java/io/fusionauth/domain/api/LoginResponse.java b/src/main/java/io/fusionauth/domain/api/LoginResponse.java index 31d50970..ded9cf61 100644 --- a/src/main/java/io/fusionauth/domain/api/LoginResponse.java +++ b/src/main/java/io/fusionauth/domain/api/LoginResponse.java @@ -1,5 +1,17 @@ /* - * Copyright (c) 2018-2022, FusionAuth, All Rights Reserved + * Copyright (c) 2018-2025, FusionAuth, All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. */ package io.fusionauth.domain.api; @@ -33,6 +45,8 @@ public class LoginResponse implements Buildable { public String emailVerificationId; + public String identityVerificationId; + public List methods; public String pendingIdPLinkId; diff --git a/src/main/java/io/fusionauth/domain/api/UserRequest.java b/src/main/java/io/fusionauth/domain/api/UserRequest.java index 0ec8119b..0686739b 100644 --- a/src/main/java/io/fusionauth/domain/api/UserRequest.java +++ b/src/main/java/io/fusionauth/domain/api/UserRequest.java @@ -1,13 +1,28 @@ /* - * Copyright (c) 2018-2023, FusionAuth, All Rights Reserved + * Copyright (c) 2018-2025, FusionAuth, All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. */ package io.fusionauth.domain.api; +import java.util.ArrayList; +import java.util.List; import java.util.UUID; import com.inversoft.json.JacksonConstructor; import io.fusionauth.domain.Buildable; import io.fusionauth.domain.EventInfo; +import io.fusionauth.domain.SendSetPasswordIdentityType; import io.fusionauth.domain.User; /** @@ -23,12 +38,20 @@ public class UserRequest extends BaseEventRequest implements Buildable verificationIds = new ArrayList<>(); + @JacksonConstructor public UserRequest() { } diff --git a/src/main/java/io/fusionauth/domain/api/UserResponse.java b/src/main/java/io/fusionauth/domain/api/UserResponse.java index 68cef87b..29c5a306 100644 --- a/src/main/java/io/fusionauth/domain/api/UserResponse.java +++ b/src/main/java/io/fusionauth/domain/api/UserResponse.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2024, FusionAuth, All Rights Reserved + * Copyright (c) 2018-2025, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,10 +16,13 @@ package io.fusionauth.domain.api; import java.time.ZonedDateTime; +import java.util.List; import java.util.Map; import java.util.UUID; import com.inversoft.json.JacksonConstructor; +import io.fusionauth.domain.Buildable; +import io.fusionauth.domain.IdentityType; import io.fusionauth.domain.User; /** @@ -42,6 +45,8 @@ public class UserResponse { public User user; + public List verificationIds; + @JacksonConstructor public UserResponse() { } @@ -49,4 +54,14 @@ public UserResponse() { public UserResponse(User user) { this.user = user; } + + public static class VerificationId implements Buildable { + public String id; + + public String oneTimeCode; + + public IdentityType type; + + public String value; + } } diff --git a/src/main/java/io/fusionauth/domain/api/WebAuthnStartRequest.java b/src/main/java/io/fusionauth/domain/api/WebAuthnStartRequest.java index 1d596fa4..db3ab980 100644 --- a/src/main/java/io/fusionauth/domain/api/WebAuthnStartRequest.java +++ b/src/main/java/io/fusionauth/domain/api/WebAuthnStartRequest.java @@ -1,8 +1,22 @@ /* - * Copyright (c) 2022, FusionAuth, All Rights Reserved + * Copyright (c) 2022-2024, FusionAuth, All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. */ package io.fusionauth.domain.api; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.UUID; @@ -27,10 +41,12 @@ public class WebAuthnStartRequest implements Buildable { public UUID credentialId; /** - * The optional login ID for the user. Either this or {@link WebAuthnStartRequest#userId} must be specified. + * The optional login Id for the user. Either this or {@link WebAuthnStartRequest#userId} must be specified. */ public String loginId; + public List loginIdTypes = new ArrayList<>(); + /** * The OAuth2 state */ diff --git a/src/main/java/io/fusionauth/domain/api/identity/verify/VerifyCompleteRequest.java b/src/main/java/io/fusionauth/domain/api/identity/verify/VerifyCompleteRequest.java new file mode 100644 index 00000000..a2212dfd --- /dev/null +++ b/src/main/java/io/fusionauth/domain/api/identity/verify/VerifyCompleteRequest.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2024-2025, FusionAuth, All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + */ +package io.fusionauth.domain.api.identity.verify; + +import io.fusionauth.domain.Buildable; +import io.fusionauth.domain.api.BaseEventRequest; + +/** + * Verify Complete API request object. + */ +public class VerifyCompleteRequest extends BaseEventRequest implements Buildable { + public String oneTimeCode; + + public String verificationId; +} diff --git a/src/main/java/io/fusionauth/domain/api/identity/verify/VerifyCompleteResponse.java b/src/main/java/io/fusionauth/domain/api/identity/verify/VerifyCompleteResponse.java new file mode 100644 index 00000000..c240229f --- /dev/null +++ b/src/main/java/io/fusionauth/domain/api/identity/verify/VerifyCompleteResponse.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2025, FusionAuth, All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + */ +package io.fusionauth.domain.api.identity.verify; + +import java.util.Map; + +import io.fusionauth.domain.Buildable; + +/** + * Verify Complete API response object. + */ +public class VerifyCompleteResponse implements Buildable { + public Map state; +} diff --git a/src/main/java/io/fusionauth/domain/api/identity/verify/VerifyRequest.java b/src/main/java/io/fusionauth/domain/api/identity/verify/VerifyRequest.java new file mode 100644 index 00000000..e7cf10ed --- /dev/null +++ b/src/main/java/io/fusionauth/domain/api/identity/verify/VerifyRequest.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2025, FusionAuth, All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + */ +package io.fusionauth.domain.api.identity.verify; + +import io.fusionauth.domain.Buildable; +import io.fusionauth.domain.api.BaseEventRequest; + +/** + * Identity verify request. Used to administratively verify an identity. + */ +public class VerifyRequest extends BaseEventRequest implements Buildable { + public String loginId; + + public String loginIdType; +} diff --git a/src/main/java/io/fusionauth/domain/api/identity/verify/VerifySendRequest.java b/src/main/java/io/fusionauth/domain/api/identity/verify/VerifySendRequest.java new file mode 100644 index 00000000..9fa6455a --- /dev/null +++ b/src/main/java/io/fusionauth/domain/api/identity/verify/VerifySendRequest.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2024-2025, FusionAuth, All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + */ +package io.fusionauth.domain.api.identity.verify; + +import com.inversoft.json.JacksonConstructor; +import io.fusionauth.domain.Buildable; + +/** + * Verify Send API request object. + */ +public class VerifySendRequest implements Buildable { + public String verificationId; + + public VerifySendRequest(String verificationId) { + this.verificationId = verificationId; + } + + @JacksonConstructor + public VerifySendRequest() { + } +} diff --git a/src/main/java/io/fusionauth/domain/api/identity/verify/VerifyStartRequest.java b/src/main/java/io/fusionauth/domain/api/identity/verify/VerifyStartRequest.java new file mode 100644 index 00000000..9ee6f0ad --- /dev/null +++ b/src/main/java/io/fusionauth/domain/api/identity/verify/VerifyStartRequest.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2024-2025, FusionAuth, All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + */ +package io.fusionauth.domain.api.identity.verify; + +import java.util.Map; +import java.util.UUID; + +import io.fusionauth.domain.Buildable; +import io.fusionauth.domain.VerificationStrategy; + +/** + * @author Brady Wied + */ +public class VerifyStartRequest implements Buildable { + public UUID applicationId; + + public String loginId; + + public String loginIdType; + + public Map state; + + public VerificationStrategy verificationStrategy; +} diff --git a/src/main/java/io/fusionauth/domain/api/identity/verify/VerifyStartResponse.java b/src/main/java/io/fusionauth/domain/api/identity/verify/VerifyStartResponse.java new file mode 100644 index 00000000..5ca064b6 --- /dev/null +++ b/src/main/java/io/fusionauth/domain/api/identity/verify/VerifyStartResponse.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2024-2025, FusionAuth, All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + */ +package io.fusionauth.domain.api.identity.verify; + +import com.inversoft.json.JacksonConstructor; + +/** + * @author Brady Wied + */ +public class VerifyStartResponse { + public String oneTimeCode; + + public String verificationId; + + public VerifyStartResponse(String oneTimeCode, String verificationId) { + this.oneTimeCode = oneTimeCode; + this.verificationId = verificationId; + } + + @JacksonConstructor + private VerifyStartResponse() { + } +} diff --git a/src/main/java/io/fusionauth/domain/api/identityProvider/IdentityProviderStartLoginRequest.java b/src/main/java/io/fusionauth/domain/api/identityProvider/IdentityProviderStartLoginRequest.java index 9f12b1e2..7cec7c8a 100644 --- a/src/main/java/io/fusionauth/domain/api/identityProvider/IdentityProviderStartLoginRequest.java +++ b/src/main/java/io/fusionauth/domain/api/identityProvider/IdentityProviderStartLoginRequest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, FusionAuth, All Rights Reserved + * Copyright (c) 2019-2025, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,8 @@ */ package io.fusionauth.domain.api.identityProvider; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.UUID; @@ -33,6 +35,8 @@ public class IdentityProviderStartLoginRequest extends BaseLoginRequest implemen public String loginId; + public List loginIdTypes = new ArrayList<>(); + public Map state; @JacksonConstructor @@ -68,4 +72,4 @@ public IdentityProviderStartLoginRequest(UUID applicationId, UUID identityProvid eventInfo.ipAddress = ipAddress; } } -} \ No newline at end of file +} diff --git a/src/main/java/io/fusionauth/domain/api/jwt/RefreshRequest.java b/src/main/java/io/fusionauth/domain/api/jwt/RefreshRequest.java index 09c481ce..f8c09506 100644 --- a/src/main/java/io/fusionauth/domain/api/jwt/RefreshRequest.java +++ b/src/main/java/io/fusionauth/domain/api/jwt/RefreshRequest.java @@ -1,5 +1,17 @@ /* - * Copyright (c) 2018-2023, FusionAuth, All Rights Reserved + * Copyright (c) 2018-2025, FusionAuth, All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. */ package io.fusionauth.domain.api.jwt; @@ -14,6 +26,9 @@ public class RefreshRequest extends BaseEventRequest implements Buildable { public String refreshToken; + // Optional reduced TTL + public Integer timeToLiveInSeconds; + public String token; @JacksonConstructor diff --git a/src/main/java/io/fusionauth/domain/api/passwordless/PasswordlessLoginRequest.java b/src/main/java/io/fusionauth/domain/api/passwordless/PasswordlessLoginRequest.java index ecc4bc64..12fc78fd 100644 --- a/src/main/java/io/fusionauth/domain/api/passwordless/PasswordlessLoginRequest.java +++ b/src/main/java/io/fusionauth/domain/api/passwordless/PasswordlessLoginRequest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, FusionAuth, All Rights Reserved + * Copyright (c) 2019-2024, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,8 @@ public class PasswordlessLoginRequest extends BaseLoginRequest implements Buildable { public String code; + public String oneTimeCode; + public String twoFactorTrustId; @JacksonConstructor diff --git a/src/main/java/io/fusionauth/domain/api/passwordless/PasswordlessSendRequest.java b/src/main/java/io/fusionauth/domain/api/passwordless/PasswordlessSendRequest.java index bbb8048a..dfdcce23 100644 --- a/src/main/java/io/fusionauth/domain/api/passwordless/PasswordlessSendRequest.java +++ b/src/main/java/io/fusionauth/domain/api/passwordless/PasswordlessSendRequest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, FusionAuth, All Rights Reserved + * Copyright (c) 2019-2025, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,12 +37,38 @@ public class PasswordlessSendRequest implements Buildable state) { this.applicationId = applicationId; diff --git a/src/main/java/io/fusionauth/domain/api/passwordless/PasswordlessStartRequest.java b/src/main/java/io/fusionauth/domain/api/passwordless/PasswordlessStartRequest.java index b8076969..73ef177a 100644 --- a/src/main/java/io/fusionauth/domain/api/passwordless/PasswordlessStartRequest.java +++ b/src/main/java/io/fusionauth/domain/api/passwordless/PasswordlessStartRequest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, FusionAuth, All Rights Reserved + * Copyright (c) 2019-2025, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,11 +15,14 @@ */ package io.fusionauth.domain.api.passwordless; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.UUID; import com.inversoft.json.JacksonConstructor; import io.fusionauth.domain.Buildable; +import io.fusionauth.domain.PasswordlessStrategy; /** * @author Daniel DeGroff @@ -29,16 +32,20 @@ public class PasswordlessStartRequest implements Buildable loginIdTypes = new ArrayList<>(); + + public PasswordlessStrategy loginStrategy; + public Map state; @JacksonConstructor public PasswordlessStartRequest() { } - @SuppressWarnings("unused") - public PasswordlessStartRequest(UUID applicationId, String loginId, Map state) { + public PasswordlessStartRequest(UUID applicationId, String loginId, List loginIdTypes, Map state) { this.applicationId = applicationId; this.loginId = loginId; + this.loginIdTypes = loginIdTypes; this.state = state; } } diff --git a/src/main/java/io/fusionauth/domain/api/passwordless/PasswordlessStartResponse.java b/src/main/java/io/fusionauth/domain/api/passwordless/PasswordlessStartResponse.java index 7a8b6b5e..f2338ac8 100644 --- a/src/main/java/io/fusionauth/domain/api/passwordless/PasswordlessStartResponse.java +++ b/src/main/java/io/fusionauth/domain/api/passwordless/PasswordlessStartResponse.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, FusionAuth, All Rights Reserved + * Copyright (c) 2019-2024, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +24,8 @@ public class PasswordlessStartResponse implements Buildable { public String code; + public String oneTimeCode; + @JacksonConstructor public PasswordlessStartResponse() { } @@ -31,4 +33,9 @@ public PasswordlessStartResponse() { public PasswordlessStartResponse(String code) { this.code = code; } + + public PasswordlessStartResponse(String code, String oneTimeCode) { + this.code = code; + this.oneTimeCode = oneTimeCode; + } } diff --git a/src/main/java/io/fusionauth/domain/api/twoFactor/TwoFactorStartRequest.java b/src/main/java/io/fusionauth/domain/api/twoFactor/TwoFactorStartRequest.java index 190de2c9..3a8abfe9 100644 --- a/src/main/java/io/fusionauth/domain/api/twoFactor/TwoFactorStartRequest.java +++ b/src/main/java/io/fusionauth/domain/api/twoFactor/TwoFactorStartRequest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, FusionAuth, All Rights Reserved + * Copyright (c) 2021-2025, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,8 @@ */ package io.fusionauth.domain.api.twoFactor; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.UUID; @@ -31,6 +33,8 @@ public class TwoFactorStartRequest implements Buildable { public String loginId; + public List loginIdTypes = new ArrayList<>(); + public Map state; public String trustChallenge; diff --git a/src/main/java/io/fusionauth/domain/api/user/ChangePasswordRequest.java b/src/main/java/io/fusionauth/domain/api/user/ChangePasswordRequest.java index 8da4de9b..71e25d8c 100644 --- a/src/main/java/io/fusionauth/domain/api/user/ChangePasswordRequest.java +++ b/src/main/java/io/fusionauth/domain/api/user/ChangePasswordRequest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2019, FusionAuth, All Rights Reserved + * Copyright (c) 2018-2025, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,7 @@ */ package io.fusionauth.domain.api.user; +import java.util.List; import java.util.UUID; import com.inversoft.json.JacksonConstructor; @@ -35,6 +36,8 @@ public class ChangePasswordRequest extends BaseEventRequest implements Buildable public String loginId; + public List loginIdTypes; + public String password; public String refreshToken; diff --git a/src/main/java/io/fusionauth/domain/api/user/ForgotPasswordRequest.java b/src/main/java/io/fusionauth/domain/api/user/ForgotPasswordRequest.java index cf0fd7d0..2f2b68c4 100644 --- a/src/main/java/io/fusionauth/domain/api/user/ForgotPasswordRequest.java +++ b/src/main/java/io/fusionauth/domain/api/user/ForgotPasswordRequest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2019, FusionAuth, All Rights Reserved + * Copyright (c) 2018-2025, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,10 +15,12 @@ */ package io.fusionauth.domain.api.user; +import java.util.List; import java.util.Map; import java.util.UUID; import com.inversoft.json.JacksonConstructor; +import io.fusionauth.domain.Buildable; import io.fusionauth.domain.EventInfo; import io.fusionauth.domain.api.BaseEventRequest; @@ -28,15 +30,20 @@ * @author Brian Pontarelli */ @SuppressWarnings("unused") -public class ForgotPasswordRequest extends BaseEventRequest { +public class ForgotPasswordRequest extends BaseEventRequest implements Buildable { public UUID applicationId; public String changePasswordId; public String loginId; + public List loginIdTypes; + + @Deprecated public boolean sendForgotPasswordEmail = true; + public Boolean sendForgotPasswordMessage; + public Map state; @JacksonConstructor @@ -57,9 +64,9 @@ public ForgotPasswordRequest(UUID applicationId, String loginId) { this.loginId = loginId; } - public ForgotPasswordRequest(String loginId, boolean sendForgotPasswordEmail) { + public ForgotPasswordRequest(String loginId, boolean sendForgotPasswordMessage) { this.loginId = loginId; - this.sendForgotPasswordEmail = sendForgotPasswordEmail; + this.sendForgotPasswordMessage = sendForgotPasswordMessage; } public ForgotPasswordRequest(EventInfo eventInfo, UUID applicationId, String loginId) { diff --git a/src/main/java/io/fusionauth/domain/api/user/RegistrationRequest.java b/src/main/java/io/fusionauth/domain/api/user/RegistrationRequest.java index b971e19f..f7e79ae1 100644 --- a/src/main/java/io/fusionauth/domain/api/user/RegistrationRequest.java +++ b/src/main/java/io/fusionauth/domain/api/user/RegistrationRequest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, FusionAuth, All Rights Reserved + * Copyright (c) 2018-2025, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ import com.inversoft.json.JacksonConstructor; import io.fusionauth.domain.EventInfo; +import io.fusionauth.domain.SendSetPasswordIdentityType; import io.fusionauth.domain.User; import io.fusionauth.domain.UserRegistration; import io.fusionauth.domain.api.BaseEventRequest; @@ -33,8 +34,14 @@ public class RegistrationRequest extends BaseEventRequest { public UserRegistration registration; + /** + * @deprecated This field is deprecated since 1.59.0 and will be removed in a future version. Use {@link #sendSetPasswordIdentityType} instead. + */ + @Deprecated public boolean sendSetPasswordEmail; + public SendSetPasswordIdentityType sendSetPasswordIdentityType; + public boolean skipRegistrationVerification; public boolean skipVerification; diff --git a/src/main/java/io/fusionauth/domain/connector/ConnectorType.java b/src/main/java/io/fusionauth/domain/connector/ConnectorType.java index 4e4ddc68..d7ae5fe7 100644 --- a/src/main/java/io/fusionauth/domain/connector/ConnectorType.java +++ b/src/main/java/io/fusionauth/domain/connector/ConnectorType.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, FusionAuth, All Rights Reserved + * Copyright (c) 2020-2024, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,7 +26,7 @@ public enum ConnectorType { LDAP; /** - * Return the connector type from a a string name returning null if the value is unknown. + * Return the connector type from a string name returning null if the value is unknown. * * @param value the string name of the connector type * @return the connector type or null if it is not one of the possible values. diff --git a/src/main/java/io/fusionauth/domain/event/BaseUserEvent.java b/src/main/java/io/fusionauth/domain/event/BaseUserEvent.java index f4abb8c9..c01ebdf6 100644 --- a/src/main/java/io/fusionauth/domain/event/BaseUserEvent.java +++ b/src/main/java/io/fusionauth/domain/event/BaseUserEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, FusionAuth, All Rights Reserved + * Copyright (c) 2024-2025, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ import java.util.Objects; import java.util.UUID; +import com.inversoft.json.JacksonConstructor; import io.fusionauth.domain.EventInfo; import io.fusionauth.domain.User; @@ -27,14 +28,16 @@ * @author Spencer Witt */ public abstract class BaseUserEvent extends BaseEvent implements ObjectIdentifiable { - public User user; + public final User user; + @JacksonConstructor public BaseUserEvent() { + user = null; } public BaseUserEvent(EventInfo info, User user) { super(info); - this.user = user; + this.user = user != null ? new User(user).secure().sort() : null; } @Override @@ -60,4 +63,39 @@ public void setLinkedObjectId(UUID linkedObjectId) { public int hashCode() { return Objects.hash(super.hashCode(), user); } + + public static class IdentityInfo { + public final String type; + + public final String value; + + @JacksonConstructor + public IdentityInfo() { + // Jackson will set values for these, but final fields are still good + this.type = null; + this.value = null; + } + + public IdentityInfo(String type, String value) { + this.type = type; + this.value = value; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof IdentityInfo)) { + return false; + } + IdentityInfo that = (IdentityInfo) o; + return Objects.equals(type, that.type) && Objects.equals(value, that.value); + } + + @Override + public int hashCode() { + return Objects.hash(type, value); + } + } } diff --git a/src/main/java/io/fusionauth/domain/event/EventRequest.java b/src/main/java/io/fusionauth/domain/event/EventRequest.java index 4f7f46e0..dcf0de71 100644 --- a/src/main/java/io/fusionauth/domain/event/EventRequest.java +++ b/src/main/java/io/fusionauth/domain/event/EventRequest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2024, FusionAuth, All Rights Reserved + * Copyright (c) 2018-2025, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/io/fusionauth/domain/event/EventType.java b/src/main/java/io/fusionauth/domain/event/EventType.java index 5500cc21..c40c411c 100644 --- a/src/main/java/io/fusionauth/domain/event/EventType.java +++ b/src/main/java/io/fusionauth/domain/event/EventType.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2024, FusionAuth, All Rights Reserved + * Copyright (c) 2018-2025, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,6 +29,8 @@ * @author Brian Pontarelli */ public enum EventType { + // Be careful, these, despite the eventName, are ordinal based in the DB. Always add to the end. + JWTPublicKeyUpdate("jwt.public-key.update"), // TODO : 2.0 : Rename -> refresh-token.revoke @@ -134,7 +136,11 @@ public enum EventType { UserUpdateComplete("user.update.complete"), - Test("test"); + Test("test"), + + UserIdentityVerified("user.identity.verified"), + + UserIdentityUpdate("user.identity.update"); private static final Map nameMap = new HashMap<>(EventType.values().length); @@ -177,6 +183,8 @@ public static List allTypes() { EventType.UserEmailVerified, EventType.UserIdentityProviderLink, EventType.UserIdentityProviderUnlink, + EventType.UserIdentityUpdate, + EventType.UserIdentityVerified, EventType.UserLoginIdDuplicateOnCreate, EventType.UserLoginIdDuplicateOnUpdate, EventType.UserLoginFailed, diff --git a/src/main/java/io/fusionauth/domain/event/JWTRefreshTokenRevokeEvent.java b/src/main/java/io/fusionauth/domain/event/JWTRefreshTokenRevokeEvent.java index aefe4512..ed8cfa2a 100644 --- a/src/main/java/io/fusionauth/domain/event/JWTRefreshTokenRevokeEvent.java +++ b/src/main/java/io/fusionauth/domain/event/JWTRefreshTokenRevokeEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2024, FusionAuth, All Rights Reserved + * Copyright (c) 2018-2025, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,35 +35,36 @@ * @author Brian Pontarelli */ public class JWTRefreshTokenRevokeEvent extends BaseEvent implements Buildable, ObjectIdentifiable { + public final User user; + public UUID applicationId; public Map applicationTimeToLiveInSeconds = new TreeMap<>(); public RefreshToken refreshToken; - public User user; - public UUID userId; - @JacksonConstructor - public JWTRefreshTokenRevokeEvent() { - } - public JWTRefreshTokenRevokeEvent(EventInfo info, User user, UUID applicationId, int timeToLiveInSeconds) { super(info); this.applicationId = applicationId; this.applicationTimeToLiveInSeconds.put(applicationId, timeToLiveInSeconds); - this.user = user; + this.user = user != null ? new User(user).secure().sort() : null; this.userId = user == null ? null : user.id; } public JWTRefreshTokenRevokeEvent(EventInfo info, User user, Map applicationTimeToLiveInSeconds) { super(info); this.applicationTimeToLiveInSeconds.putAll(applicationTimeToLiveInSeconds); - this.user = user; + this.user = user != null ? new User(user).secure().sort() : null; this.userId = user == null ? null : user.id; } + @JacksonConstructor + public JWTRefreshTokenRevokeEvent() { + user = null; + } + public List applicationIds() { return new ArrayList<>(applicationTimeToLiveInSeconds.keySet()); } diff --git a/src/main/java/io/fusionauth/domain/event/UserIdentityUpdateEvent.java b/src/main/java/io/fusionauth/domain/event/UserIdentityUpdateEvent.java new file mode 100644 index 00000000..52d0f1e2 --- /dev/null +++ b/src/main/java/io/fusionauth/domain/event/UserIdentityUpdateEvent.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2019-2025, FusionAuth, All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + */ +package io.fusionauth.domain.event; + +import java.util.Objects; +import java.util.UUID; + +import com.inversoft.json.JacksonConstructor; +import io.fusionauth.domain.Buildable; +import io.fusionauth.domain.EventInfo; +import io.fusionauth.domain.User; + +/** + * Models the user identity update event + * + * @author Brent Halsey + */ +public class UserIdentityUpdateEvent extends BaseUserEvent implements Buildable { + public final String loginIdType; + + public final String newLoginId; + + public final String previousLoginId; + + /** + * Creates an event + * + * @param info The event info object containing IP information, and possibly location from the request. + * @param previousLoginId The previous login Id that was updated + * @param newLoginId The updated login Id + * @param loginIdType The type of previousLoginId and newLoginId (e.g., "email", "phoneNumber", "username"). + * @param user User whose identity was updated. + */ + public UserIdentityUpdateEvent(EventInfo info, String previousLoginId, String newLoginId, String loginIdType, User user) { + super(info, user); + this.previousLoginId = previousLoginId; + this.newLoginId = newLoginId; + this.loginIdType = loginIdType; + } + + @JacksonConstructor + private UserIdentityUpdateEvent() { + // Jackson will set values for these, but final fields are still good + this.previousLoginId = null; + this.newLoginId = null; + this.loginIdType = null; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + UserIdentityUpdateEvent that = (UserIdentityUpdateEvent) o; + return + Objects.equals(previousLoginId, that.previousLoginId) && + Objects.equals(newLoginId, that.newLoginId) && + Objects.equals(loginIdType, that.loginIdType); + } + + @Override + public UUID getLinkedObjectId() { + // BaseUserEvent expects a user to exist and identities can get created before users exist + return user != null ? super.getLinkedObjectId() : null; + } + + @Override + public EventType getType() { + return EventType.UserIdentityUpdate; + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), previousLoginId, newLoginId, loginIdType); + } +} diff --git a/src/main/java/io/fusionauth/domain/event/UserIdentityVerifiedEvent.java b/src/main/java/io/fusionauth/domain/event/UserIdentityVerifiedEvent.java new file mode 100644 index 00000000..489edaae --- /dev/null +++ b/src/main/java/io/fusionauth/domain/event/UserIdentityVerifiedEvent.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2019-2025, FusionAuth, All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the License. + */ +package io.fusionauth.domain.event; + +import java.util.Objects; +import java.util.UUID; + +import com.inversoft.json.JacksonConstructor; +import io.fusionauth.domain.Buildable; +import io.fusionauth.domain.EventInfo; +import io.fusionauth.domain.User; + +/** + * Models the user identity verified event + * + * @author Brady Wied + */ +public class UserIdentityVerifiedEvent extends BaseUserEvent implements Buildable { + public final String loginId; + + public final String loginIdType; + + /** + * Creates an event + * + * @param info The event info object containing IP information, and possibly location from the request. + * @param loginId the login ID that was verified + * @param loginIdType describes what loginId is + * @param user (Optional) user the identity was verified for. + */ + public UserIdentityVerifiedEvent(EventInfo info, String loginId, String loginIdType, User user) { + super(info, user); + this.loginId = loginId; + this.loginIdType = loginIdType; + } + + @JacksonConstructor + private UserIdentityVerifiedEvent() { + // Jackson will set values for these, but final field are still good + this.loginId = null; + this.loginIdType = null; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + UserIdentityVerifiedEvent that = (UserIdentityVerifiedEvent) o; + return Objects.equals(loginId, that.loginId) && Objects.equals(loginIdType, that.loginIdType); + } + + @Override + public UUID getLinkedObjectId() { + // BaseUserEvent expects a user to exist and identities can get created before users exist + return user != null ? super.getLinkedObjectId() : null; + } + + @Override + public EventType getType() { + return EventType.UserIdentityVerified; + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), loginId, loginIdType); + } +} diff --git a/src/main/java/io/fusionauth/domain/event/UserLoginFailedEvent.java b/src/main/java/io/fusionauth/domain/event/UserLoginFailedEvent.java index 75813169..182a1275 100644 --- a/src/main/java/io/fusionauth/domain/event/UserLoginFailedEvent.java +++ b/src/main/java/io/fusionauth/domain/event/UserLoginFailedEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2024, FusionAuth, All Rights Reserved + * Copyright (c) 2019-2025, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -60,7 +60,6 @@ public UserLoginFailedEvent(EventInfo info, UUID applicationId, String authentic super(info, user); this.applicationId = applicationId; this.authenticationType = authenticationType; - this.user = user; // Maintain the old JSON format if (info != null && info.ipAddress != null) { diff --git a/src/main/java/io/fusionauth/domain/event/UserLoginIdDuplicateOnCreateEvent.java b/src/main/java/io/fusionauth/domain/event/UserLoginIdDuplicateOnCreateEvent.java index c1a74ce6..02714aff 100644 --- a/src/main/java/io/fusionauth/domain/event/UserLoginIdDuplicateOnCreateEvent.java +++ b/src/main/java/io/fusionauth/domain/event/UserLoginIdDuplicateOnCreateEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2024, FusionAuth, All Rights Reserved + * Copyright (c) 2021-2025, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,7 @@ */ package io.fusionauth.domain.event; +import java.util.List; import java.util.Objects; import com.inversoft.json.JacksonConstructor; @@ -23,26 +24,35 @@ import io.fusionauth.domain.User; /** - * Models an event where a user is being created with an "in-use" login Id (email or username). + * Models an event where a user is being created with an "in-use" login Id (email, username, or other identities). * * @author Daniel DeGroff */ public class UserLoginIdDuplicateOnCreateEvent extends BaseUserEvent implements Buildable, NonTransactionalEvent { + public final User existing; + public String duplicateEmail; - public String duplicateUsername; + public List duplicateIdentities; - public User existing; + public String duplicatePhoneNumber; + + public String duplicateUsername; @JacksonConstructor public UserLoginIdDuplicateOnCreateEvent() { + this.existing = null; } - public UserLoginIdDuplicateOnCreateEvent(EventInfo info, String duplicateEmail, String duplicateUsername, User existing, User user) { + public UserLoginIdDuplicateOnCreateEvent(EventInfo info, String duplicateEmail, String duplicateUsername, String duplicatePhoneNumber, + List duplicateIdentities, + User existing, User user) { super(info, user); this.duplicateEmail = duplicateEmail; this.duplicateUsername = duplicateUsername; - this.existing = existing; + this.duplicatePhoneNumber = duplicatePhoneNumber; + this.duplicateIdentities = duplicateIdentities; + this.existing = new User(existing).secure().sort(); } @Override @@ -52,6 +62,8 @@ public boolean equals(Object o) { } UserLoginIdDuplicateOnCreateEvent that = (UserLoginIdDuplicateOnCreateEvent) o; return Objects.equals(duplicateEmail, that.duplicateEmail) && + Objects.equals(duplicateIdentities, that.duplicateIdentities) && + Objects.equals(duplicatePhoneNumber, that.duplicatePhoneNumber) && Objects.equals(duplicateUsername, that.duplicateUsername) && Objects.equals(existing, that.existing); } @@ -63,6 +75,6 @@ public EventType getType() { @Override public int hashCode() { - return Objects.hash(super.hashCode(), duplicateEmail, duplicateUsername, existing); + return Objects.hash(super.hashCode(), duplicateEmail, duplicateIdentities, duplicatePhoneNumber, duplicateUsername, existing); } } diff --git a/src/main/java/io/fusionauth/domain/event/UserLoginIdDuplicateOnUpdateEvent.java b/src/main/java/io/fusionauth/domain/event/UserLoginIdDuplicateOnUpdateEvent.java index cb982c93..bd3ad388 100644 --- a/src/main/java/io/fusionauth/domain/event/UserLoginIdDuplicateOnUpdateEvent.java +++ b/src/main/java/io/fusionauth/domain/event/UserLoginIdDuplicateOnUpdateEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, FusionAuth, All Rights Reserved + * Copyright (c) 2021-2025, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,8 @@ */ package io.fusionauth.domain.event; +import java.util.List; + import com.inversoft.json.JacksonConstructor; import io.fusionauth.domain.EventInfo; import io.fusionauth.domain.User; @@ -29,8 +31,9 @@ public class UserLoginIdDuplicateOnUpdateEvent extends UserLoginIdDuplicateOnCre public UserLoginIdDuplicateOnUpdateEvent() { } - public UserLoginIdDuplicateOnUpdateEvent(EventInfo info, String duplicateEmail, String duplicateUsername, User existing, User user) { - super(info, duplicateEmail, duplicateUsername, existing, user); + public UserLoginIdDuplicateOnUpdateEvent(EventInfo info, String duplicateEmail, String duplicateUsername, String duplicatePhoneNumber, + List duplicateIdentities, User existing, User user) { + super(info, duplicateEmail, duplicateUsername, duplicatePhoneNumber, duplicateIdentities, existing, user); } @Override diff --git a/src/main/java/io/fusionauth/domain/event/UserRegistrationCreateCompleteEvent.java b/src/main/java/io/fusionauth/domain/event/UserRegistrationCreateCompleteEvent.java index c64a0e50..4d086e52 100644 --- a/src/main/java/io/fusionauth/domain/event/UserRegistrationCreateCompleteEvent.java +++ b/src/main/java/io/fusionauth/domain/event/UserRegistrationCreateCompleteEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2024, FusionAuth, All Rights Reserved + * Copyright (c) 2021-2025, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,16 +37,25 @@ public class UserRegistrationCreateCompleteEvent extends BaseUserEvent implement public UserRegistration registration; - @JacksonConstructor - public UserRegistrationCreateCompleteEvent() { - } - + /** + * Construct a new event, indicating that registration creation is complete + * + * @param info event info + * @param applicationId application the registration is for + * @param registration registration that was completed + * @param user user affected. This user will be copied and all of its registrations will be removed + */ public UserRegistrationCreateCompleteEvent(EventInfo info, UUID applicationId, UserRegistration registration, User user) { super(info, user); + this.user.getRegistrations().clear(); this.applicationId = applicationId; this.registration = registration; } + @JacksonConstructor + private UserRegistrationCreateCompleteEvent() { + } + @Override public boolean equals(Object o) { if (!super.equals(o)) { diff --git a/src/main/java/io/fusionauth/domain/event/UserRegistrationCreateEvent.java b/src/main/java/io/fusionauth/domain/event/UserRegistrationCreateEvent.java index 4b0bead1..1fb4e86d 100644 --- a/src/main/java/io/fusionauth/domain/event/UserRegistrationCreateEvent.java +++ b/src/main/java/io/fusionauth/domain/event/UserRegistrationCreateEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2024, FusionAuth, All Rights Reserved + * Copyright (c) 2019-2025, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,16 +34,25 @@ public class UserRegistrationCreateEvent extends BaseUserEvent implements Builda public UserRegistration registration; - @JacksonConstructor - public UserRegistrationCreateEvent() { - } - + /** + * Construct a new event, indicating that registration creation is about to be committed to the DB + * + * @param info event info + * @param applicationId application the registration is for + * @param registration registration that is being created + * @param user user affected. This user will be copied and all of its registrations will be removed + */ public UserRegistrationCreateEvent(EventInfo info, UUID applicationId, UserRegistration registration, User user) { super(info, user); + this.user.getRegistrations().clear(); this.applicationId = applicationId; this.registration = registration; } + @JacksonConstructor + private UserRegistrationCreateEvent() { + } + @Override public boolean equals(Object o) { if (!super.equals(o)) { diff --git a/src/main/java/io/fusionauth/domain/event/UserRegistrationDeleteCompleteEvent.java b/src/main/java/io/fusionauth/domain/event/UserRegistrationDeleteCompleteEvent.java index 1398c5e0..30867569 100644 --- a/src/main/java/io/fusionauth/domain/event/UserRegistrationDeleteCompleteEvent.java +++ b/src/main/java/io/fusionauth/domain/event/UserRegistrationDeleteCompleteEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2024, FusionAuth, All Rights Reserved + * Copyright (c) 2021-2025, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,16 +36,25 @@ public class UserRegistrationDeleteCompleteEvent extends BaseUserEvent implement public UserRegistration registration; - @JacksonConstructor - public UserRegistrationDeleteCompleteEvent() { - } - + /** + * Construct a new event, indicating that registration deletion is complete + * + * @param info event info + * @param applicationId application the registration is for + * @param registration registration that was deleted + * @param user user affected. This user will be copied and all of its registrations will be removed + */ public UserRegistrationDeleteCompleteEvent(EventInfo info, UUID applicationId, UserRegistration registration, User user) { super(info, user); + this.user.getRegistrations().clear(); this.applicationId = applicationId; this.registration = registration; } + @JacksonConstructor + private UserRegistrationDeleteCompleteEvent() { + } + @Override public boolean equals(Object o) { if (!super.equals(o)) { diff --git a/src/main/java/io/fusionauth/domain/event/UserRegistrationDeleteEvent.java b/src/main/java/io/fusionauth/domain/event/UserRegistrationDeleteEvent.java index c3f5bb04..f97b78c2 100644 --- a/src/main/java/io/fusionauth/domain/event/UserRegistrationDeleteEvent.java +++ b/src/main/java/io/fusionauth/domain/event/UserRegistrationDeleteEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2024, FusionAuth, All Rights Reserved + * Copyright (c) 2019-2025, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,16 +34,25 @@ public class UserRegistrationDeleteEvent extends BaseUserEvent implements Builda public UserRegistration registration; - @JacksonConstructor - public UserRegistrationDeleteEvent() { - } - + /** + * Construct a new event, indicating that registration deletion is about to be committed to the DB + * + * @param info event info + * @param applicationId application the registration is for + * @param registration registration that was completed + * @param user user affected. This user will be copied and all of its registrations will be removed + */ public UserRegistrationDeleteEvent(EventInfo info, UUID applicationId, UserRegistration registration, User user) { super(info, user); + this.user.getRegistrations().clear(); this.applicationId = applicationId; this.registration = registration; } + @JacksonConstructor + private UserRegistrationDeleteEvent() { + } + @Override public boolean equals(Object o) { if (!super.equals(o)) { diff --git a/src/main/java/io/fusionauth/domain/event/UserRegistrationUpdateCompleteEvent.java b/src/main/java/io/fusionauth/domain/event/UserRegistrationUpdateCompleteEvent.java index 67b46f61..1057d9d0 100644 --- a/src/main/java/io/fusionauth/domain/event/UserRegistrationUpdateCompleteEvent.java +++ b/src/main/java/io/fusionauth/domain/event/UserRegistrationUpdateCompleteEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2024, FusionAuth, All Rights Reserved + * Copyright (c) 2021-2025, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,18 +38,27 @@ public class UserRegistrationUpdateCompleteEvent extends BaseUserEvent implement public UserRegistration registration; - @JacksonConstructor - public UserRegistrationUpdateCompleteEvent() { - } - + /** + * Construct a new event, indicating that a registration update is complete + * + * @param info event info + * @param applicationId application the registration is for + * @param registration registration that was updated + * @param user user affected. This user will be copied and all of its registrations will be removed + */ public UserRegistrationUpdateCompleteEvent(EventInfo info, UUID applicationId, UserRegistration original, UserRegistration registration, User user) { super(info, user); + this.user.getRegistrations().clear(); this.applicationId = applicationId; this.original = original; this.registration = registration; } + @JacksonConstructor + private UserRegistrationUpdateCompleteEvent() { + } + @Override public boolean equals(Object o) { if (!super.equals(o)) { diff --git a/src/main/java/io/fusionauth/domain/event/UserRegistrationUpdateEvent.java b/src/main/java/io/fusionauth/domain/event/UserRegistrationUpdateEvent.java index 32aa75bb..792fa5d3 100644 --- a/src/main/java/io/fusionauth/domain/event/UserRegistrationUpdateEvent.java +++ b/src/main/java/io/fusionauth/domain/event/UserRegistrationUpdateEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2024, FusionAuth, All Rights Reserved + * Copyright (c) 2019-2025, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,17 +36,27 @@ public class UserRegistrationUpdateEvent extends BaseUserEvent implements Builda public UserRegistration registration; - @JacksonConstructor - public UserRegistrationUpdateEvent() { - } - + /** + * Construct a new event, with only the original/current registration being updated + * + * @param info event info + * @param applicationId application the registration is for + * @param original original registration before updates + * @param registration updated registration + * @param user user affected. This user will be copied and all of its registrations will be removed + */ public UserRegistrationUpdateEvent(EventInfo info, UUID applicationId, UserRegistration original, UserRegistration registration, User user) { super(info, user); + this.user.getRegistrations().clear(); this.applicationId = applicationId; this.original = original; this.registration = registration; } + @JacksonConstructor + private UserRegistrationUpdateEvent() { + } + @Override public boolean equals(Object o) { if (!super.equals(o)) { diff --git a/src/main/java/io/fusionauth/domain/event/UserRegistrationVerifiedEvent.java b/src/main/java/io/fusionauth/domain/event/UserRegistrationVerifiedEvent.java index e2190e96..3113ab7d 100644 --- a/src/main/java/io/fusionauth/domain/event/UserRegistrationVerifiedEvent.java +++ b/src/main/java/io/fusionauth/domain/event/UserRegistrationVerifiedEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2024, FusionAuth, All Rights Reserved + * Copyright (c) 2019-2025, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,16 +34,25 @@ public class UserRegistrationVerifiedEvent extends BaseUserEvent implements Buil public UserRegistration registration; - @JacksonConstructor - public UserRegistrationVerifiedEvent() { - } - + /** + * Construct a new event, indicating that a registration verification is about to be committed to the DB + * + * @param info event info + * @param applicationId application the registration is for + * @param registration registration that was verified + * @param user user affected. This user will be copied and all of its registrations will be removed + */ public UserRegistrationVerifiedEvent(EventInfo info, UUID applicationId, UserRegistration registration, User user) { super(info, user); + this.user.getRegistrations().clear(); this.applicationId = applicationId; this.registration = registration; } + @JacksonConstructor + private UserRegistrationVerifiedEvent() { + } + @Override public boolean equals(Object o) { if (!super.equals(o)) { diff --git a/src/main/java/io/fusionauth/domain/event/UserUpdateCompleteEvent.java b/src/main/java/io/fusionauth/domain/event/UserUpdateCompleteEvent.java index 2b948d9b..401f0f73 100644 --- a/src/main/java/io/fusionauth/domain/event/UserUpdateCompleteEvent.java +++ b/src/main/java/io/fusionauth/domain/event/UserUpdateCompleteEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2024, FusionAuth, All Rights Reserved + * Copyright (c) 2021-2025, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,15 +28,16 @@ * @author Daniel DeGroff */ public class UserUpdateCompleteEvent extends BaseUserEvent implements Buildable, NonTransactionalEvent { - public User original; - - @JacksonConstructor - public UserUpdateCompleteEvent() { - } + public final User original; public UserUpdateCompleteEvent(EventInfo info, User original, User user) { super(info, user); - this.original = original; + this.original = new User(original).secure().sort(); + } + + @JacksonConstructor + private UserUpdateCompleteEvent() { + this.original = null; } @Override diff --git a/src/main/java/io/fusionauth/domain/event/UserUpdateEvent.java b/src/main/java/io/fusionauth/domain/event/UserUpdateEvent.java index 5f48da32..5faa408b 100644 --- a/src/main/java/io/fusionauth/domain/event/UserUpdateEvent.java +++ b/src/main/java/io/fusionauth/domain/event/UserUpdateEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2024, FusionAuth, All Rights Reserved + * Copyright (c) 2018-2025, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,15 +28,16 @@ * @author Brian Pontarelli */ public class UserUpdateEvent extends BaseUserEvent implements Buildable { - public User original; - - @JacksonConstructor - public UserUpdateEvent() { - } + public final User original; public UserUpdateEvent(EventInfo info, User original, User user) { super(info, user); - this.original = original; + this.original = new User(original).secure().sort(); + } + + @JacksonConstructor + private UserUpdateEvent() { + this.original = null; } @Override diff --git a/src/main/java/io/fusionauth/domain/form/FormDataType.java b/src/main/java/io/fusionauth/domain/form/FormDataType.java index 6bfe2ac1..7e8731ec 100644 --- a/src/main/java/io/fusionauth/domain/form/FormDataType.java +++ b/src/main/java/io/fusionauth/domain/form/FormDataType.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, FusionAuth, All Rights Reserved + * Copyright (c) 2020-2024, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,5 +24,6 @@ public enum FormDataType { date, email, number, + phoneNumber, string } diff --git a/src/main/java/io/fusionauth/domain/form/ManagedFields.java b/src/main/java/io/fusionauth/domain/form/ManagedFields.java index 4384d437..ab4e8e6e 100644 --- a/src/main/java/io/fusionauth/domain/form/ManagedFields.java +++ b/src/main/java/io/fusionauth/domain/form/ManagedFields.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, FusionAuth, All Rights Reserved + * Copyright (c) 2021-2025, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -85,6 +85,15 @@ public final class ManagedFields { .with(f -> f.data.put("leftAddon", "user")) .with(f -> f.name = "[Admin User] email")); + map.put("user.phoneNumber", + new FormField().with(f -> f.key = "user.phoneNumber") + .with(f -> f.confirm = false) + .with(f -> f.control = FormControl.text) + .with(f -> f.required = false) + .with(f -> f.type = FormDataType.phoneNumber) + .with(f -> f.data.put("leftAddon", "mobile")) + .with(f -> f.name = "[Admin User] phone number")); + map.put("user.firstName", new FormField().with(f -> f.key = "user.firstName") .with(f -> f.confirm = false) diff --git a/src/main/java/io/fusionauth/domain/oauth2/OAuthError.java b/src/main/java/io/fusionauth/domain/oauth2/OAuthError.java index 1f6cd3e6..4aeef652 100644 --- a/src/main/java/io/fusionauth/domain/oauth2/OAuthError.java +++ b/src/main/java/io/fusionauth/domain/oauth2/OAuthError.java @@ -81,6 +81,7 @@ public enum OAuthErrorReason { // Invalid request parameters invalid_client_id, + invalid_expires_in, invalid_user_credentials, invalid_grant_type, invalid_origin, diff --git a/src/main/java/io/fusionauth/domain/oauth2/UserState.java b/src/main/java/io/fusionauth/domain/oauth2/UserState.java index 166acbdf..3377f02b 100644 --- a/src/main/java/io/fusionauth/domain/oauth2/UserState.java +++ b/src/main/java/io/fusionauth/domain/oauth2/UserState.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, FusionAuth, All Rights Reserved + * Copyright (c) 2018-2025, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -47,6 +47,10 @@ public static UserState fromStatus(int status) { throw new IllegalArgumentException("Invalid status code for UserState [" + status + "]"); } + /** + * This method only applies to legacy user identities before 1.59.0 and should no longer be used. + */ + @Deprecated public static UserState fromUserAndRegistration(User user, UserRegistration registration) { if (registration == null) { return AuthenticatedNotRegistered; diff --git a/src/main/java/io/fusionauth/domain/search/UserSearchCriteria.java b/src/main/java/io/fusionauth/domain/search/UserSearchCriteria.java index 3e60a325..243a1feb 100644 --- a/src/main/java/io/fusionauth/domain/search/UserSearchCriteria.java +++ b/src/main/java/io/fusionauth/domain/search/UserSearchCriteria.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2024, FusionAuth, All Rights Reserved + * Copyright (c) 2018-2025, FusionAuth, All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,6 +35,7 @@ public class UserSearchCriteria extends BaseElasticSearchCriteria implements Bui "insertInstant", "lastLoginInstant", "login", + "phoneNumber", "tenantId", "username", "registrations.applicationId", diff --git a/src/main/java/io/fusionauth/domain/util/Normalizer.java b/src/main/java/io/fusionauth/domain/util/Normalizer.java index ddbd163b..f3112a86 100644 --- a/src/main/java/io/fusionauth/domain/util/Normalizer.java +++ b/src/main/java/io/fusionauth/domain/util/Normalizer.java @@ -149,6 +149,20 @@ public static String toLowerCase(String str) { return str.toLowerCase(); } + /** + * Uppercases the String in a null-safe manner. + * + * @param str The String to uppercase. + * @return The String or null. + */ + public static String toUpperCase(String str) { + if (str == null) { + return null; + } + + return str.toUpperCase(); + } + /** * Trims the String in a null safe manner. * From 360f8ad7300239bf8ce2df6376deeffac0054b3b Mon Sep 17 00:00:00 2001 From: Daniel DeGroff Date: Mon, 25 Aug 2025 14:07:23 -0600 Subject: [PATCH 4/4] sync domain builds --- src/main/java/io/fusionauth/domain/oauth2/OAuthError.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/io/fusionauth/domain/oauth2/OAuthError.java b/src/main/java/io/fusionauth/domain/oauth2/OAuthError.java index 4aeef652..94f7e25d 100644 --- a/src/main/java/io/fusionauth/domain/oauth2/OAuthError.java +++ b/src/main/java/io/fusionauth/domain/oauth2/OAuthError.java @@ -151,6 +151,7 @@ public enum OAuthErrorReason { authentication_required, email_verification_required, multi_factor_challenge_required, + phone_verification_required, registration_missing_requirement, registration_required, registration_verification_required,