diff --git a/core/src/main/java/google/registry/module/RequestComponent.java b/core/src/main/java/google/registry/module/RequestComponent.java index 7f74c8768..0f4f1ff72 100644 --- a/core/src/main/java/google/registry/module/RequestComponent.java +++ b/core/src/main/java/google/registry/module/RequestComponent.java @@ -114,6 +114,7 @@ import google.registry.ui.server.console.ConsoleDomainListAction; import google.registry.ui.server.console.ConsoleDumDownloadAction; import google.registry.ui.server.console.ConsoleEppPasswordAction; import google.registry.ui.server.console.ConsoleRegistryLockAction; +import google.registry.ui.server.console.ConsoleRegistryLockVerifyAction; import google.registry.ui.server.console.ConsoleUpdateRegistrarAction; import google.registry.ui.server.console.ConsoleUserDataAction; import google.registry.ui.server.console.RegistrarsAction; @@ -191,6 +192,8 @@ interface RequestComponent { ConsoleRegistryLockAction consoleRegistryLockAction(); + ConsoleRegistryLockVerifyAction consoleRegistryLockVerifyAction(); + ConsoleUiAction consoleUiAction(); ConsoleUpdateRegistrarAction consoleUpdateRegistrarAction(); diff --git a/core/src/main/java/google/registry/module/frontend/FrontendRequestComponent.java b/core/src/main/java/google/registry/module/frontend/FrontendRequestComponent.java index 15a403e76..850fddc81 100644 --- a/core/src/main/java/google/registry/module/frontend/FrontendRequestComponent.java +++ b/core/src/main/java/google/registry/module/frontend/FrontendRequestComponent.java @@ -30,6 +30,7 @@ import google.registry.ui.server.console.ConsoleDomainListAction; import google.registry.ui.server.console.ConsoleDumDownloadAction; import google.registry.ui.server.console.ConsoleEppPasswordAction; import google.registry.ui.server.console.ConsoleRegistryLockAction; +import google.registry.ui.server.console.ConsoleRegistryLockVerifyAction; import google.registry.ui.server.console.ConsoleUpdateRegistrarAction; import google.registry.ui.server.console.ConsoleUserDataAction; import google.registry.ui.server.console.RegistrarsAction; @@ -69,6 +70,8 @@ public interface FrontendRequestComponent { ConsoleRegistryLockAction consoleRegistryLockAction(); + ConsoleRegistryLockVerifyAction consoleRegistryLockVerifyAction(); + ConsoleUiAction consoleUiAction(); ConsoleUpdateRegistrarAction consoleUpdateRegistrarAction(); diff --git a/core/src/main/java/google/registry/ui/server/console/ConsoleRegistryLockAction.java b/core/src/main/java/google/registry/ui/server/console/ConsoleRegistryLockAction.java index c426c91fa..c0ae8c0ed 100644 --- a/core/src/main/java/google/registry/ui/server/console/ConsoleRegistryLockAction.java +++ b/core/src/main/java/google/registry/ui/server/console/ConsoleRegistryLockAction.java @@ -47,10 +47,8 @@ import google.registry.util.EmailMessage; import jakarta.mail.internet.AddressException; import jakarta.mail.internet.InternetAddress; import jakarta.servlet.http.HttpServletRequest; -import java.net.URISyntaxException; import java.util.Optional; import javax.inject.Inject; -import org.apache.http.client.utils.URIBuilder; import org.joda.time.Duration; /** @@ -153,14 +151,9 @@ public class ConsoleRegistryLockAction extends ConsoleApiAction { private void sendVerificationEmail(RegistryLock lock, String userEmail, boolean isLock) { try { String url = - new URIBuilder() - .setScheme("https") - .setHost(consoleApiParams.request().getServerName()) - // TODO: replace this with the PATH in ConsoleRegistryLockVerifyAction once it exists - .setPath("/console-api/registry-lock-verify") - .setParameter("lockVerificationCode", lock.getVerificationCode()) - .build() - .toString(); + String.format( + "https://%s/console/#/registry-lock-verify?lockVerificationCode=%s", + consoleApiParams.request().getServerName(), lock.getVerificationCode()); String body = String.format(VERIFICATION_EMAIL_TEMPLATE, lock.getDomainName(), url); ImmutableList recipients = ImmutableList.of(new InternetAddress(userEmail, true)); @@ -171,7 +164,7 @@ public class ConsoleRegistryLockAction extends ConsoleApiAction { .setSubject(String.format("Registry %s verification", action)) .setRecipients(recipients) .build()); - } catch (AddressException | URISyntaxException e) { + } catch (AddressException e) { throw new RuntimeException(e); // caught above -- this is so we can run in a transaction } } diff --git a/core/src/main/java/google/registry/ui/server/console/ConsoleRegistryLockVerifyAction.java b/core/src/main/java/google/registry/ui/server/console/ConsoleRegistryLockVerifyAction.java new file mode 100644 index 000000000..239bd51ed --- /dev/null +++ b/core/src/main/java/google/registry/ui/server/console/ConsoleRegistryLockVerifyAction.java @@ -0,0 +1,80 @@ +// Copyright 2024 The Nomulus Authors. 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 google.registry.ui.server.console; + +import static google.registry.request.Action.Method.GET; + +import com.google.common.base.Ascii; +import com.google.gson.Gson; +import com.google.gson.annotations.Expose; +import google.registry.model.console.User; +import google.registry.model.domain.RegistryLock; +import google.registry.request.Action; +import google.registry.request.Parameter; +import google.registry.request.auth.Auth; +import google.registry.tools.DomainLockUtils; +import google.registry.ui.server.registrar.ConsoleApiParams; +import jakarta.servlet.http.HttpServletResponse; +import javax.inject.Inject; + +/** Handler for verifying registry lock requests, a form of 2FA. */ +@Action( + service = Action.Service.DEFAULT, + path = ConsoleRegistryLockVerifyAction.PATH, + method = {GET}, + auth = Auth.AUTH_PUBLIC_LOGGED_IN) +public class ConsoleRegistryLockVerifyAction extends ConsoleApiAction { + + static final String PATH = "/console-api/registry-lock-verify"; + + private final DomainLockUtils domainLockUtils; + private final Gson gson; + private final String lockVerificationCode; + + @Inject + public ConsoleRegistryLockVerifyAction( + ConsoleApiParams consoleApiParams, + DomainLockUtils domainLockUtils, + Gson gson, + @Parameter("lockVerificationCode") String lockVerificationCode) { + super(consoleApiParams); + this.domainLockUtils = domainLockUtils; + this.gson = gson; + this.lockVerificationCode = lockVerificationCode; + } + + @Override + protected void getHandler(User user) { + RegistryLock lock = + domainLockUtils.verifyVerificationCode(lockVerificationCode, user.getUserRoles().isAdmin()); + RegistryLockAction action = + lock.getLockCompletionTime().isPresent() + ? RegistryLockAction.UNLOCKED + : RegistryLockAction.LOCKED; + RegistryLockVerificationResponse lockResponse = + new RegistryLockVerificationResponse( + Ascii.toLowerCase(action.toString()), lock.getDomainName(), lock.getRegistrarId()); + consoleApiParams.response().setPayload(gson.toJson(lockResponse)); + consoleApiParams.response().setStatus(HttpServletResponse.SC_OK); + } + + private enum RegistryLockAction { + LOCKED, + UNLOCKED + } + + private record RegistryLockVerificationResponse( + @Expose String action, @Expose String domainName, @Expose String registrarId) {} +} diff --git a/core/src/test/java/google/registry/testing/FakeResponse.java b/core/src/test/java/google/registry/testing/FakeResponse.java index eda47b205..42ab89b85 100644 --- a/core/src/test/java/google/registry/testing/FakeResponse.java +++ b/core/src/test/java/google/registry/testing/FakeResponse.java @@ -23,6 +23,7 @@ import com.google.common.base.Throwables; import com.google.common.net.MediaType; import google.registry.request.Response; import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; @@ -69,7 +70,7 @@ public final class FakeResponse implements Response { @Override public void sendRedirect(String url) throws IOException { - status = 302; + status = HttpServletResponse.SC_FOUND; this.payload = String.format("Redirected to %s", url); } diff --git a/core/src/test/java/google/registry/ui/server/console/ConsoleRegistryLockActionTest.java b/core/src/test/java/google/registry/ui/server/console/ConsoleRegistryLockActionTest.java index a9da34165..dc973e350 100644 --- a/core/src/test/java/google/registry/ui/server/console/ConsoleRegistryLockActionTest.java +++ b/core/src/test/java/google/registry/ui/server/console/ConsoleRegistryLockActionTest.java @@ -80,7 +80,7 @@ public class ConsoleRegistryLockActionTest { Please click the link below to perform the lock / unlock action on domain example.test. \ Note: this code will expire in one hour. - https://registrarconsole.tld/console-api/registry-lock-verify?lockVerificationCode=\ + https://registrarconsole.tld/console/#/registry-lock-verify?lockVerificationCode=\ 123456789ABCDEFGHJKLMNPQRSTUVWXY"""; private static final Gson GSON = RequestModule.provideGson(); @@ -122,15 +122,7 @@ public class ConsoleRegistryLockActionTest { @Test void testGet_simpleLock() { - saveRegistryLock( - new RegistryLock.Builder() - .setRepoId("repoId") - .setDomainName("example.test") - .setRegistrarId("TheRegistrar") - .setVerificationCode("123456789ABCDEFGHJKLMNPQRSTUVWXY") - .setRegistrarPocId("johndoe@theregistrar.com") - .setLockCompletionTime(fakeClock.nowUtc()) - .build()); + saveRegistryLock(createDefaultLockBuilder().setLockCompletionTime(fakeClock.nowUtc()).build()); action.run(); assertThat(response.getStatus()).isEqualTo(SC_OK); assertThat(response.getPayload()) diff --git a/core/src/test/java/google/registry/ui/server/console/ConsoleRegistryLockVerifyActionTest.java b/core/src/test/java/google/registry/ui/server/console/ConsoleRegistryLockVerifyActionTest.java new file mode 100644 index 000000000..8ff207bb7 --- /dev/null +++ b/core/src/test/java/google/registry/ui/server/console/ConsoleRegistryLockVerifyActionTest.java @@ -0,0 +1,220 @@ +// Copyright 2024 The Nomulus Authors. 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 google.registry.ui.server.console; + +import static com.google.common.truth.Truth.assertThat; +import static google.registry.testing.DatabaseHelper.createTld; +import static google.registry.testing.DatabaseHelper.loadByEntity; +import static google.registry.testing.DatabaseHelper.persistActiveDomain; +import static google.registry.testing.DatabaseHelper.persistResource; +import static google.registry.testing.SqlHelper.saveRegistryLock; +import static google.registry.tools.LockOrUnlockDomainCommand.REGISTRY_LOCK_STATUSES; +import static org.mockito.Mockito.when; + +import com.google.common.collect.ImmutableMap; +import com.google.gson.Gson; +import google.registry.model.console.RegistrarRole; +import google.registry.model.console.User; +import google.registry.model.console.UserRoles; +import google.registry.model.domain.Domain; +import google.registry.model.domain.RegistryLock; +import google.registry.model.eppcommon.StatusValue; +import google.registry.persistence.transaction.JpaTestExtensions; +import google.registry.request.RequestModule; +import google.registry.request.auth.AuthResult; +import google.registry.testing.CloudTasksHelper; +import google.registry.testing.ConsoleApiParamsUtils; +import google.registry.testing.DeterministicStringGenerator; +import google.registry.testing.FakeClock; +import google.registry.testing.FakeResponse; +import google.registry.tools.DomainLockUtils; +import google.registry.ui.server.registrar.ConsoleApiParams; +import google.registry.util.StringGenerator; +import jakarta.servlet.http.HttpServletResponse; +import org.joda.time.Duration; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +/** Tests for {@link ConsoleRegistryLockVerifyAction}. */ +public class ConsoleRegistryLockVerifyActionTest { + + private static final String DEFAULT_CODE = "123456789ABCDEFGHJKLMNPQRSTUUUUU"; + private static final Gson GSON = RequestModule.provideGson(); + private final FakeClock fakeClock = new FakeClock(); + + @RegisterExtension + final JpaTestExtensions.JpaIntegrationTestExtension jpa = + new JpaTestExtensions.Builder().withClock(fakeClock).buildIntegrationTestExtension(); + + private FakeResponse response; + private Domain defaultDomain; + private User user; + + private ConsoleRegistryLockVerifyAction action; + + @BeforeEach + void beforeEach() { + createTld("test"); + defaultDomain = persistActiveDomain("example.test"); + user = + new User.Builder() + .setEmailAddress("user@theregistrar.com") + .setRegistryLockEmailAddress("registrylock@theregistrar.com") + .setUserRoles( + new UserRoles.Builder() + .setRegistrarRoles( + ImmutableMap.of("TheRegistrar", RegistrarRole.PRIMARY_CONTACT)) + .build()) + .setRegistryLockPassword("registryLockPassword") + .build(); + action = createAction(DEFAULT_CODE); + } + + @Test + void testSuccess_lock() { + saveRegistryLock(createDefaultLockBuilder().build()); + action.run(); + assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); + assertThat(response.getPayload()) + .isEqualTo( + "{\"action\":\"unlocked\",\"domainName\":\"example.test\",\"registrarId\":\"TheRegistrar\"}"); + assertThat(loadByEntity(defaultDomain).getStatusValues()) + .containsAtLeastElementsIn(REGISTRY_LOCK_STATUSES); + } + + @Test + void testSuccess_unlock() { + persistResource(defaultDomain.asBuilder().setStatusValues(REGISTRY_LOCK_STATUSES).build()); + saveRegistryLock( + createDefaultLockBuilder() + .setLockCompletionTime(fakeClock.nowUtc()) + .setUnlockRequestTime(fakeClock.nowUtc()) + .build()); + action.run(); + assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); + assertThat(response.getPayload()) + .isEqualTo( + "{\"action\":\"unlocked\",\"domainName\":\"example.test\",\"registrarId\":\"TheRegistrar\"}"); + assertThat(loadByEntity(defaultDomain).getStatusValues()).containsExactly(StatusValue.INACTIVE); + } + + @Test + void testSuccess_admin_lock() { + saveRegistryLock(createDefaultLockBuilder().isSuperuser(true).build()); + user = + user.asBuilder() + .setUserRoles(user.getUserRoles().asBuilder().setIsAdmin(true).build()) + .build(); + action = createAction(DEFAULT_CODE); + action.run(); + assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); + assertThat(response.getPayload()) + .isEqualTo( + "{\"action\":\"unlocked\",\"domainName\":\"example.test\",\"registrarId\":\"TheRegistrar\"}"); + assertThat(loadByEntity(defaultDomain).getStatusValues()) + .containsAtLeastElementsIn(REGISTRY_LOCK_STATUSES); + } + + @Test + void testSuccess_admin_unlock() { + persistResource(defaultDomain.asBuilder().setStatusValues(REGISTRY_LOCK_STATUSES).build()); + saveRegistryLock( + createDefaultLockBuilder() + .isSuperuser(true) + .setLockCompletionTime(fakeClock.nowUtc()) + .setUnlockRequestTime(fakeClock.nowUtc()) + .build()); + user = + user.asBuilder() + .setUserRoles(user.getUserRoles().asBuilder().setIsAdmin(true).build()) + .build(); + action = createAction(DEFAULT_CODE); + action.run(); + assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); + assertThat(response.getPayload()) + .isEqualTo( + "{\"action\":\"unlocked\",\"domainName\":\"example.test\",\"registrarId\":\"TheRegistrar\"}"); + assertThat(loadByEntity(defaultDomain).getStatusValues()).containsExactly(StatusValue.INACTIVE); + } + + @Test + void testFailure_invalidCode() { + saveRegistryLock(createDefaultLockBuilder().setVerificationCode("foobar").build()); + action.run(); + assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_BAD_REQUEST); + assertThat(response.getPayload()) + .isEqualTo("Invalid verification code 123456789ABCDEFGHJKLMNPQRSTUUUUU"); + assertThat(loadByEntity(defaultDomain).getStatusValues()).containsExactly(StatusValue.INACTIVE); + } + + @Test + void testFailure_expiredLock() { + saveRegistryLock(createDefaultLockBuilder().build()); + fakeClock.advanceBy(Duration.standardDays(1)); + action.run(); + assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_BAD_REQUEST); + assertThat(response.getPayload()).isEqualTo("The pending lock has expired; please try again"); + assertThat(loadByEntity(defaultDomain).getStatusValues()).containsExactly(StatusValue.INACTIVE); + } + + @Test + void testFailure_nonAdmin_lock() { + saveRegistryLock(createDefaultLockBuilder().isSuperuser(true).build()); + action.run(); + assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_BAD_REQUEST); + assertThat(response.getPayload()).isEqualTo("Non-admin user cannot complete admin lock"); + assertThat(loadByEntity(defaultDomain).getStatusValues()).containsExactly(StatusValue.INACTIVE); + } + + @Test + void testFailure_nonAdmin_unlock() { + persistResource(defaultDomain.asBuilder().setStatusValues(REGISTRY_LOCK_STATUSES).build()); + saveRegistryLock( + createDefaultLockBuilder() + .isSuperuser(true) + .setLockCompletionTime(fakeClock.nowUtc()) + .setUnlockRequestTime(fakeClock.nowUtc()) + .build()); + action.run(); + assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_BAD_REQUEST); + assertThat(response.getPayload()).isEqualTo("Non-admin user cannot complete admin unlock"); + assertThat(loadByEntity(defaultDomain).getStatusValues()) + .containsAtLeastElementsIn(REGISTRY_LOCK_STATUSES); + } + + private RegistryLock.Builder createDefaultLockBuilder() { + return new RegistryLock.Builder() + .setRepoId(defaultDomain.getRepoId()) + .setDomainName(defaultDomain.getDomainName()) + .setRegistrarId(defaultDomain.getCurrentSponsorRegistrarId()) + .setRegistrarPocId("johndoe@theregistrar.com") + .setVerificationCode(DEFAULT_CODE); + } + + private ConsoleRegistryLockVerifyAction createAction(String verificationCode) { + AuthResult authResult = AuthResult.createUser(user); + ConsoleApiParams params = ConsoleApiParamsUtils.createFake(authResult); + when(params.request().getMethod()).thenReturn("GET"); + when(params.request().getServerName()).thenReturn("registrarconsole.tld"); + DomainLockUtils domainLockUtils = + new DomainLockUtils( + new DeterministicStringGenerator(StringGenerator.Alphabets.BASE_58), + "adminreg", + new CloudTasksHelper(fakeClock).getTestCloudTasksUtils()); + response = (FakeResponse) params.response(); + return new ConsoleRegistryLockVerifyAction(params, domainLockUtils, GSON, verificationCode); + } +} diff --git a/core/src/test/resources/google/registry/module/frontend/frontend_routing.txt b/core/src/test/resources/google/registry/module/frontend/frontend_routing.txt index 0d6fabd8f..687dffec6 100644 --- a/core/src/test/resources/google/registry/module/frontend/frontend_routing.txt +++ b/core/src/test/resources/google/registry/module/frontend/frontend_routing.txt @@ -1,21 +1,22 @@ -PATH CLASS METHODS OK MIN USER_POLICY -/_dr/epp EppTlsAction POST n APP ADMIN -/console-api/domain ConsoleDomainGetAction GET n USER PUBLIC -/console-api/domain-list ConsoleDomainListAction GET n USER PUBLIC -/console-api/dum-download ConsoleDumDownloadAction GET n USER PUBLIC -/console-api/eppPassword ConsoleEppPasswordAction POST n USER PUBLIC -/console-api/registrar ConsoleUpdateRegistrarAction POST n USER PUBLIC -/console-api/registrars RegistrarsAction GET,POST n USER PUBLIC -/console-api/registry-lock ConsoleRegistryLockAction GET,POST n USER PUBLIC -/console-api/settings/contacts ContactAction GET,POST n USER PUBLIC -/console-api/settings/security SecurityAction POST n USER PUBLIC -/console-api/settings/whois-fields WhoisRegistrarFieldsAction POST n USER PUBLIC -/console-api/userdata ConsoleUserDataAction GET n USER PUBLIC -/registrar ConsoleUiAction GET n USER PUBLIC -/registrar-create ConsoleRegistrarCreatorAction POST,GET n USER PUBLIC -/registrar-ote-setup ConsoleOteSetupAction POST,GET n USER PUBLIC -/registrar-ote-status OteStatusAction POST n USER PUBLIC -/registrar-settings RegistrarSettingsAction POST n USER PUBLIC -/registry-lock-get RegistryLockGetAction GET n USER PUBLIC -/registry-lock-post RegistryLockPostAction POST n USER PUBLIC -/registry-lock-verify RegistryLockVerifyAction GET n USER PUBLIC +PATH CLASS METHODS OK MIN USER_POLICY +/_dr/epp EppTlsAction POST n APP ADMIN +/console-api/domain ConsoleDomainGetAction GET n USER PUBLIC +/console-api/domain-list ConsoleDomainListAction GET n USER PUBLIC +/console-api/dum-download ConsoleDumDownloadAction GET n USER PUBLIC +/console-api/eppPassword ConsoleEppPasswordAction POST n USER PUBLIC +/console-api/registrar ConsoleUpdateRegistrarAction POST n USER PUBLIC +/console-api/registrars RegistrarsAction GET,POST n USER PUBLIC +/console-api/registry-lock ConsoleRegistryLockAction GET,POST n USER PUBLIC +/console-api/registry-lock-verify ConsoleRegistryLockVerifyAction GET n USER PUBLIC +/console-api/settings/contacts ContactAction GET,POST n USER PUBLIC +/console-api/settings/security SecurityAction POST n USER PUBLIC +/console-api/settings/whois-fields WhoisRegistrarFieldsAction POST n USER PUBLIC +/console-api/userdata ConsoleUserDataAction GET n USER PUBLIC +/registrar ConsoleUiAction GET n USER PUBLIC +/registrar-create ConsoleRegistrarCreatorAction POST,GET n USER PUBLIC +/registrar-ote-setup ConsoleOteSetupAction POST,GET n USER PUBLIC +/registrar-ote-status OteStatusAction POST n USER PUBLIC +/registrar-settings RegistrarSettingsAction POST n USER PUBLIC +/registry-lock-get RegistryLockGetAction GET n USER PUBLIC +/registry-lock-post RegistryLockPostAction POST n USER PUBLIC +/registry-lock-verify RegistryLockVerifyAction GET n USER PUBLIC diff --git a/core/src/test/resources/google/registry/module/routing.txt b/core/src/test/resources/google/registry/module/routing.txt index a59e95878..fe99b44b3 100644 --- a/core/src/test/resources/google/registry/module/routing.txt +++ b/core/src/test/resources/google/registry/module/routing.txt @@ -63,6 +63,7 @@ PATH CLASS /console-api/registrar ConsoleUpdateRegistrarAction POST n USER PUBLIC /console-api/registrars RegistrarsAction GET,POST n USER PUBLIC /console-api/registry-lock ConsoleRegistryLockAction GET,POST n USER PUBLIC +/console-api/registry-lock-verify ConsoleRegistryLockVerifyAction GET n USER PUBLIC /console-api/settings/contacts ContactAction GET,POST n USER PUBLIC /console-api/settings/security SecurityAction POST n USER PUBLIC /console-api/settings/whois-fields WhoisRegistrarFieldsAction POST n USER PUBLIC