diff --git a/src/main/java/io/fusionauth/client/FusionAuthClient.java b/src/main/java/io/fusionauth/client/FusionAuthClient.java index a77ab4f5..5fb9fb60 100644 --- a/src/main/java/io/fusionauth/client/FusionAuthClient.java +++ b/src/main/java/io/fusionauth/client/FusionAuthClient.java @@ -218,6 +218,7 @@ import io.fusionauth.domain.api.twoFactor.TwoFactorStartRequest; import io.fusionauth.domain.api.twoFactor.TwoFactorStartResponse; import io.fusionauth.domain.api.twoFactor.TwoFactorStatusResponse; +import io.fusionauth.domain.api.twoFactor.TwoFactorStatusRequest; import io.fusionauth.domain.api.user.ActionRequest; import io.fusionauth.domain.api.user.ActionResponse; import io.fusionauth.domain.api.user.ChangePasswordRequest; @@ -513,6 +514,26 @@ public ClientResponse checkChangePasswordUsingId(String changePass .go(); } + /** + * Check to see if the user must obtain a Trust Token Id in order to complete a change password request. + * When a user has enabled Two-Factor authentication, before you are allowed to use the Change Password API to change + * your password, you must obtain a Trust Token by completing a Two-Factor Step-Up authentication. + *

+ * An HTTP status code of 400 with a general error code of [TrustTokenRequired] indicates that a Trust Token is required to make a POST request to this API. + * + * @param changePasswordId The change password Id used to find the user. This value is generated by FusionAuth once the change password workflow has been initiated. + * @param ipAddress (Optional) IP address of the user changing their password. This is used for MFA risk assessment. + * @return The ClientResponse object. + */ + public ClientResponse checkChangePasswordUsingIdAndIPAddress(String changePasswordId, String ipAddress) { + return startAnonymous(Void.TYPE, Errors.class) + .uri("/api/user/change-password") + .urlSegment(changePasswordId) + .urlParameter("ipAddress", ipAddress) + .get() + .go(); + } + /** * Check to see if the user must obtain a Trust Token Id in order to complete a change password request. * When a user has enabled Two-Factor authentication, before you are allowed to use the Change Password API to change @@ -531,6 +552,26 @@ public ClientResponse checkChangePasswordUsingJWT(String encodedJW .go(); } + /** + * Check to see if the user must obtain a Trust Token Id in order to complete a change password request. + * When a user has enabled Two-Factor authentication, before you are allowed to use the Change Password API to change + * your password, you must obtain a Trust Token by completing a Two-Factor Step-Up authentication. + *

+ * An HTTP status code of 400 with a general error code of [TrustTokenRequired] indicates that a Trust Token is required to make a POST request to this API. + * + * @param encodedJWT The encoded JWT (access token). + * @param ipAddress (Optional) IP address of the user changing their password. This is used for MFA risk assessment. + * @return The ClientResponse object. + */ + public ClientResponse checkChangePasswordUsingJWTAndIPAddress(String encodedJWT, String ipAddress) { + return startAnonymous(Void.TYPE, Errors.class) + .uri("/api/user/change-password") + .authorization("Bearer " + encodedJWT) + .urlParameter("ipAddress", ipAddress) + .get() + .go(); + } + /** * Check to see if the user must obtain a Trust Request Id in order to complete a change password request. * When a user has enabled Two-Factor authentication, before you are allowed to use the Change Password API to change @@ -549,6 +590,26 @@ public ClientResponse checkChangePasswordUsingLoginId(String login .go(); } + /** + * Check to see if the user must obtain a Trust Request Id in order to complete a change password request. + * When a user has enabled Two-Factor authentication, before you are allowed to use the Change Password API to change + * your password, you must obtain a Trust Request Id by completing a Two-Factor Step-Up authentication. + *

+ * An HTTP status code of 400 with a general error code of [TrustTokenRequired] indicates that a Trust Token is required to make a POST request to this API. + * + * @param loginId The loginId (email or username) of the User that you intend to change the password for. + * @param ipAddress (Optional) IP address of the user changing their password. This is used for MFA risk assessment. + * @return The ClientResponse object. + */ + public ClientResponse checkChangePasswordUsingLoginIdAndIPAddress(String loginId, String ipAddress) { + return start(Void.TYPE, Errors.class) + .uri("/api/user/change-password") + .urlParameter("loginId", loginId) + .urlParameter("ipAddress", ipAddress) + .get() + .go(); + } + /** * Check to see if the user must obtain a Trust Request Id in order to complete a change password request. * When a user has enabled Two-Factor authentication, before you are allowed to use the Change Password API to change @@ -569,6 +630,28 @@ public ClientResponse checkChangePasswordUsingLoginIdAndLoginIdTyp .go(); } + /** + * Check to see if the user must obtain a Trust Request Id in order to complete a change password request. + * When a user has enabled Two-Factor authentication, before you are allowed to use the Change Password API to change + * your password, you must obtain a Trust Request Id by completing a Two-Factor Step-Up authentication. + *

+ * An HTTP status code of 400 with a general error code of [TrustTokenRequired] indicates that a Trust Token is required to make a POST request to this API. + * + * @param loginId The loginId of the User that you intend to change the password for. + * @param loginIdTypes The identity types that FusionAuth will compare the loginId to. + * @param ipAddress (Optional) IP address of the user changing their password. This is used for MFA risk assessment. + * @return The ClientResponse object. + */ + public ClientResponse checkChangePasswordUsingLoginIdAndLoginIdTypesAndIPAddress(String loginId, List loginIdTypes, String ipAddress) { + return start(Void.TYPE, Errors.class) + .uri("/api/user/change-password") + .urlParameter("loginId", loginId) + .urlParameter("loginIdTypes", loginIdTypes) + .urlParameter("ipAddress", ipAddress) + .get() + .go(); + } + /** * Make a Client Credentials grant request to obtain an access token. * @@ -4125,6 +4208,24 @@ public ClientResponse retrieveTwoFactorStatus(U .go(); } + /** + * Retrieve a user's two-factor status. + *

+ * This can be used to see if a user will need to complete a two-factor challenge to complete a login, + * and optionally identify the state of the two-factor trust across various applications. This operation + * provides more payload options than retrieveTwoFactorStatus. + * + * @param request The request object that contains all the information used to check the status. + * @return The ClientResponse object. + */ + public ClientResponse retrieveTwoFactorStatusWithRequest(TwoFactorStatusRequest request) { + return start(TwoFactorStatusResponse.class, Errors.class) + .uri("/api/two-factor/status") + .bodyHandler(new JSONBodyHandler(request, objectMapper())) + .post() + .go(); + } + /** * Retrieves the user for the given Id. * diff --git a/src/main/java/io/fusionauth/domain/Application.java b/src/main/java/io/fusionauth/domain/Application.java index ded5a63f..8d9ab271 100644 --- a/src/main/java/io/fusionauth/domain/Application.java +++ b/src/main/java/io/fusionauth/domain/Application.java @@ -454,6 +454,8 @@ public static class LambdaConfiguration { public UUID idTokenPopulateId; + public UUID multiFactorRequirementId; + public UUID samlv2PopulateId; public UUID selfServiceRegistrationValidationId; @@ -467,6 +469,7 @@ public LambdaConfiguration() { public LambdaConfiguration(LambdaConfiguration other) { this.accessTokenPopulateId = other.accessTokenPopulateId; this.idTokenPopulateId = other.idTokenPopulateId; + this.multiFactorRequirementId = other.multiFactorRequirementId; this.samlv2PopulateId = other.samlv2PopulateId; this.selfServiceRegistrationValidationId = other.selfServiceRegistrationValidationId; this.userinfoPopulateId = other.userinfoPopulateId; @@ -483,6 +486,7 @@ public boolean equals(Object o) { LambdaConfiguration that = (LambdaConfiguration) o; return Objects.equals(accessTokenPopulateId, that.accessTokenPopulateId) && Objects.equals(idTokenPopulateId, that.idTokenPopulateId) && + Objects.equals(multiFactorRequirementId, that.multiFactorRequirementId) && Objects.equals(samlv2PopulateId, that.samlv2PopulateId) && Objects.equals(selfServiceRegistrationValidationId, that.selfServiceRegistrationValidationId) && Objects.equals(userinfoPopulateId, that.userinfoPopulateId); @@ -490,7 +494,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash(accessTokenPopulateId, idTokenPopulateId, samlv2PopulateId, selfServiceRegistrationValidationId, userinfoPopulateId); + return Objects.hash(accessTokenPopulateId, idTokenPopulateId, multiFactorRequirementId, samlv2PopulateId, selfServiceRegistrationValidationId, userinfoPopulateId); } @Override diff --git a/src/main/java/io/fusionauth/domain/LambdaType.java b/src/main/java/io/fusionauth/domain/LambdaType.java index 1afe274e..91595bb5 100644 --- a/src/main/java/io/fusionauth/domain/LambdaType.java +++ b/src/main/java/io/fusionauth/domain/LambdaType.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. @@ -22,6 +22,7 @@ */ @SuppressWarnings("ALL") public enum LambdaType { + // This is an ordinal enum, so make sure new values are added to the end // @formatter:off JWTPopulate("populate", "" + //language=JavaScript @@ -496,7 +497,24 @@ public enum LambdaType { "\n" + " console.info('Hello World!');" + "\n" + - "}\n"); + "}\n"), + MFARequirement("checkRequired", "" + + //language=JavaScript + "// Check whether MFA is required, for a particular action, user, and application, in a given context.\n" + + "function checkRequired(result, user, registration, context) {\n" + + " // When writing a lambda we've added a few helpers to make life easier.\n" + + " // console.info('Hello World'); # This will create an EventLog of type Information\n" + + " // console.error('Not good.'); # This will create an EventLog of type Error\n" + + " // console.debug('Step 42 completed.'); # This will create an EventLog of type Debug\n" + + " // \n" + + " // To dump an entire object to the EventLog you can use JSON.stringify, for example: \n" + + " // console.info(JSON.stringify(user)); \n" + + "\n" + + " // Happy coding! Perform your MFA requirement check here.\n" + + "\n" + + " console.info('Hello World!');" + + "\n" + + "}\n"); // @formatter:on private final String example; diff --git a/src/main/java/io/fusionauth/domain/MultiFactorAction.java b/src/main/java/io/fusionauth/domain/MultiFactorAction.java new file mode 100644 index 00000000..e920cfe4 --- /dev/null +++ b/src/main/java/io/fusionauth/domain/MultiFactorAction.java @@ -0,0 +1,25 @@ +/* + * 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; + +/** + * Communicate various actions/contexts in which multi-factor authentication can be used. + */ +public enum MultiFactorAction { + changePassword, + login, + stepUp +} diff --git a/src/main/java/io/fusionauth/domain/Tenant.java b/src/main/java/io/fusionauth/domain/Tenant.java index 17a7d773..a8123f18 100644 --- a/src/main/java/io/fusionauth/domain/Tenant.java +++ b/src/main/java/io/fusionauth/domain/Tenant.java @@ -267,6 +267,12 @@ public JWTConfiguration lookupJWTConfiguration(Application application) { return jwtConfiguration; } + /** + * Lookup the login MFA policy + * + * @param application application to examine + * @return policy in effect + */ @JsonIgnore public MultiFactorLoginPolicy lookupMultiFactorLoginPolicy(Application application) { if (application != null && application.multiFactorConfiguration.loginPolicy != null) { diff --git a/src/main/java/io/fusionauth/domain/TenantLambdaConfiguration.java b/src/main/java/io/fusionauth/domain/TenantLambdaConfiguration.java index 68468ddd..2f56218b 100644 --- a/src/main/java/io/fusionauth/domain/TenantLambdaConfiguration.java +++ b/src/main/java/io/fusionauth/domain/TenantLambdaConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022-2024, FusionAuth, All Rights Reserved + * Copyright (c) 2022-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,8 @@ public class TenantLambdaConfiguration implements Buildable { public UUID loginValidationId; + public UUID multiFactorRequirementId; + public UUID scimEnterpriseUserRequestConverterId; public UUID scimEnterpriseUserResponseConverterId; @@ -45,6 +47,7 @@ public TenantLambdaConfiguration() { public TenantLambdaConfiguration(TenantLambdaConfiguration other) { this.loginValidationId = other.loginValidationId; + this.multiFactorRequirementId = other.multiFactorRequirementId; this.scimEnterpriseUserRequestConverterId = other.scimEnterpriseUserRequestConverterId; this.scimEnterpriseUserResponseConverterId = other.scimEnterpriseUserResponseConverterId; this.scimGroupRequestConverterId = other.scimGroupRequestConverterId; @@ -63,6 +66,7 @@ public boolean equals(Object o) { } TenantLambdaConfiguration that = (TenantLambdaConfiguration) o; return Objects.equals(loginValidationId, that.loginValidationId) && + Objects.equals(multiFactorRequirementId, that.multiFactorRequirementId) && Objects.equals(scimEnterpriseUserRequestConverterId, that.scimEnterpriseUserRequestConverterId) && Objects.equals(scimEnterpriseUserResponseConverterId, that.scimEnterpriseUserResponseConverterId) && Objects.equals(scimGroupRequestConverterId, that.scimGroupRequestConverterId) && @@ -74,6 +78,7 @@ public boolean equals(Object o) { @Override public int hashCode() { return Objects.hash(loginValidationId, + multiFactorRequirementId, scimEnterpriseUserRequestConverterId, scimEnterpriseUserResponseConverterId, scimGroupRequestConverterId, diff --git a/src/main/java/io/fusionauth/domain/api/twoFactor/TwoFactorStatusRequest.java b/src/main/java/io/fusionauth/domain/api/twoFactor/TwoFactorStatusRequest.java new file mode 100644 index 00000000..33082fa9 --- /dev/null +++ b/src/main/java/io/fusionauth/domain/api/twoFactor/TwoFactorStatusRequest.java @@ -0,0 +1,48 @@ +/* + * 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.twoFactor; + +import java.util.UUID; + +import com.inversoft.json.JacksonConstructor; +import io.fusionauth.domain.MultiFactorAction; +import io.fusionauth.domain.api.BaseEventRequest; + +/** + * Check the status of two-factor authentication for a user, with more options than on a GET request. + */ +public class TwoFactorStatusRequest extends BaseEventRequest { + // required + public final UUID userId; + + public String accessToken; + + public MultiFactorAction action = MultiFactorAction.login; + + public UUID applicationId; + + public String twoFactorTrustId; + + public TwoFactorStatusRequest(UUID userId) { + this.userId = userId; + } + + @JacksonConstructor + private TwoFactorStatusRequest() { + // will be overridden by Jackson + this.userId = null; + } +} diff --git a/src/main/java/io/fusionauth/domain/lambda/parameters/mfa/Context.java b/src/main/java/io/fusionauth/domain/lambda/parameters/mfa/Context.java new file mode 100644 index 00000000..36ea26a2 --- /dev/null +++ b/src/main/java/io/fusionauth/domain/lambda/parameters/mfa/Context.java @@ -0,0 +1,54 @@ +/* + * 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.lambda.parameters.mfa; + +import java.util.Set; + +import io.fusionauth.domain.Application; +import io.fusionauth.domain.AuthenticationThreats; +import io.fusionauth.domain.EventInfo; +import io.fusionauth.domain.MultiFactorAction; + +/** + * Represents the inbound lambda parameter 'context' for MFA Required lambdas. + */ +public class Context { + public final String accessToken; + + public final MultiFactorAction action; + + public final Application application; + + public final Set authenticationThreats; + + public final EventInfo eventInfo; + + public final Trust mfaTrust; + + public final Policies policies; + + public Context(EventInfo eventInfo, Set authenticationThreats, + Trust mfaTrust, String accessToken, + Policies policies, MultiFactorAction action, Application application) { + this.eventInfo = eventInfo; + this.authenticationThreats = authenticationThreats; + this.mfaTrust = mfaTrust; + this.accessToken = accessToken; + this.policies = policies; + this.action = action; + this.application = application; + } +} diff --git a/src/main/java/io/fusionauth/domain/lambda/parameters/mfa/Policies.java b/src/main/java/io/fusionauth/domain/lambda/parameters/mfa/Policies.java new file mode 100644 index 00000000..596cafc5 --- /dev/null +++ b/src/main/java/io/fusionauth/domain/lambda/parameters/mfa/Policies.java @@ -0,0 +1,38 @@ +/* + * 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.lambda.parameters.mfa; + +import io.fusionauth.domain.ApplicationMultiFactorTrustPolicy; +import io.fusionauth.domain.MultiFactorLoginPolicy; + +/** + * Represents the inbound lambda parameter 'policies' for MFA Required lambdas. + */ +public class Policies { + public final MultiFactorLoginPolicy applicationLoginPolicy; + + public final ApplicationMultiFactorTrustPolicy applicationMultiFactorTrustPolicy; + + public final MultiFactorLoginPolicy tenantLoginPolicy; + + public Policies(MultiFactorLoginPolicy applicationLoginPolicy, + ApplicationMultiFactorTrustPolicy applicationMultiFactorTrustPolicy, + MultiFactorLoginPolicy tenantLoginPolicy) { + this.applicationLoginPolicy = applicationLoginPolicy; + this.tenantLoginPolicy = tenantLoginPolicy; + this.applicationMultiFactorTrustPolicy = applicationMultiFactorTrustPolicy; + } +} diff --git a/src/main/java/io/fusionauth/domain/lambda/parameters/mfa/RequiredLambdaResult.java b/src/main/java/io/fusionauth/domain/lambda/parameters/mfa/RequiredLambdaResult.java new file mode 100644 index 00000000..f373bb64 --- /dev/null +++ b/src/main/java/io/fusionauth/domain/lambda/parameters/mfa/RequiredLambdaResult.java @@ -0,0 +1,29 @@ +/* + * 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.lambda.parameters.mfa; + +/** + * Represents the inbound lambda parameter 'result' for MFA Required lambdas. + */ +public class RequiredLambdaResult { + public boolean required; + + public boolean sendSuspiciousLoginEvent; + + public RequiredLambdaResult(boolean required) { + this.required = required; + } +} diff --git a/src/main/java/io/fusionauth/domain/lambda/parameters/mfa/Trust.java b/src/main/java/io/fusionauth/domain/lambda/parameters/mfa/Trust.java new file mode 100644 index 00000000..20b0e3ba --- /dev/null +++ b/src/main/java/io/fusionauth/domain/lambda/parameters/mfa/Trust.java @@ -0,0 +1,67 @@ +/* + * 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.lambda.parameters.mfa; + +import java.time.ZonedDateTime; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.UUID; + +/** + * Represents the inbound lambda parameter 'mfaTrust' inside the 'context' parameter for MFA Required lambdas. + */ +public class Trust { + public final UUID applicationId; + + public final Map attributes = new LinkedHashMap<>(); + + public final ZonedDateTime expirationInstant; + + public final String id; + + public final ZonedDateTime insertInstant; + + public final StartInstant startInstants; + + public final Map state; + + public final UUID tenantId; + + public final UUID userId; + + public Trust(UUID applicationId, ZonedDateTime expirationInstant, String id, ZonedDateTime insertInstant, + StartInstant startInstants, Map state, UUID tenantId, UUID userId) { + this.applicationId = applicationId; + this.expirationInstant = expirationInstant; + this.id = id; + this.insertInstant = insertInstant; + this.startInstants = startInstants; + this.state = state; + this.tenantId = tenantId; + this.userId = userId; + } + + public static class StartInstant { + public final Map applications; + + public final ZonedDateTime tenant; + + public StartInstant(Map applications, ZonedDateTime tenant) { + this.applications = applications; + this.tenant = tenant; + } + } +} diff --git a/src/main/java/io/fusionauth/domain/reactor/ReactorStatus.java b/src/main/java/io/fusionauth/domain/reactor/ReactorStatus.java index 91a14a17..9f3d0fce 100644 --- a/src/main/java/io/fusionauth/domain/reactor/ReactorStatus.java +++ b/src/main/java/io/fusionauth/domain/reactor/ReactorStatus.java @@ -57,6 +57,8 @@ public class ReactorStatus { public boolean licensed; + public ReactorFeatureStatus multiFactorLambdas = ReactorFeatureStatus.UNKNOWN; + public ReactorFeatureStatus scimServer = ReactorFeatureStatus.UNKNOWN; public ReactorFeatureStatus tenantManagerApplication = ReactorFeatureStatus.UNKNOWN; @@ -79,6 +81,7 @@ public ReactorStatus(ReactorStatus other) { advancedIdentityProviders = other.advancedIdentityProviders; advancedLambdas = other.advancedLambdas; advancedMultiFactorAuthentication = other.advancedMultiFactorAuthentication; + multiFactorLambdas = other.multiFactorLambdas; advancedRegistration = other.advancedRegistration; applicationMultiFactorAuthentication = other.applicationMultiFactorAuthentication; applicationThemes = other.applicationThemes; @@ -112,6 +115,7 @@ public boolean equals(Object o) { return advancedIdentityProviders == that.advancedIdentityProviders && advancedLambdas == that.advancedLambdas && advancedMultiFactorAuthentication == that.advancedMultiFactorAuthentication && + multiFactorLambdas == that.multiFactorLambdas && advancedRegistration == that.advancedRegistration && applicationMultiFactorAuthentication == that.applicationMultiFactorAuthentication && applicationThemes == that.applicationThemes && @@ -138,6 +142,7 @@ public int hashCode() { return Objects.hash(advancedIdentityProviders, advancedLambdas, advancedMultiFactorAuthentication, + multiFactorLambdas, advancedRegistration, applicationMultiFactorAuthentication, applicationThemes, @@ -163,4 +168,4 @@ public int hashCode() { public String toString() { return ToString.toString(this); } -} \ No newline at end of file +}