diff --git a/console-webapp/src/app/app-routing.module.ts b/console-webapp/src/app/app-routing.module.ts
index 3b0265666..d14bc2cd5 100644
--- a/console-webapp/src/app/app-routing.module.ts
+++ b/console-webapp/src/app/app-routing.module.ts
@@ -26,6 +26,7 @@ import { SettingsComponent } from './settings/settings.component';
import UsersComponent from './settings/users/users.component';
import WhoisComponent from './settings/whois/whois.component';
import { SupportComponent } from './support/support.component';
+import { RegistryLockVerifyComponent } from './lock/registryLockVerify.component';
export interface RouteWithIcon extends Route {
iconName?: string;
@@ -33,6 +34,10 @@ export interface RouteWithIcon extends Route {
export const routes: RouteWithIcon[] = [
{ path: '', redirectTo: '/home', pathMatch: 'full' },
+ {
+ path: RegistryLockVerifyComponent.PATH,
+ component: RegistryLockVerifyComponent,
+ },
{ path: 'registrars', component: RegistrarComponent },
{
path: 'home',
diff --git a/console-webapp/src/app/app.module.ts b/console-webapp/src/app/app.module.ts
index 50776868b..40ddeeee7 100644
--- a/console-webapp/src/app/app.module.ts
+++ b/console-webapp/src/app/app.module.ts
@@ -53,6 +53,7 @@ import { UserDataService } from './shared/services/userData.service';
import { SnackBarModule } from './snackbar.module';
import { SupportComponent } from './support/support.component';
import { TldsComponent } from './tlds/tlds.component';
+import { RegistryLockVerifyComponent } from './lock/registryLockVerify.component';
@NgModule({
declarations: [
@@ -71,6 +72,7 @@ import { TldsComponent } from './tlds/tlds.component';
RegistrarComponent,
RegistrarDetailsComponent,
RegistrarSelectorComponent,
+ RegistryLockVerifyComponent,
ResourcesComponent,
SecurityComponent,
SecurityEditComponent,
diff --git a/console-webapp/src/app/lock/registryLockVerify.component.html b/console-webapp/src/app/lock/registryLockVerify.component.html
new file mode 100644
index 000000000..dcad70b0e
--- /dev/null
+++ b/console-webapp/src/app/lock/registryLockVerify.component.html
@@ -0,0 +1,28 @@
+@if (isLoading) {
+
+
+
+} @else if (domainName) {
+Success!
+
+
+ The domain {{ domainName }} has been successfully {{ action }}ed.
+
+
+
+} @else {
+Failure
+
+
+ An error occurred: {{ errorMessage }}.
Please double-check the
+ verification code and try again.
+
+
+}
diff --git a/console-webapp/src/app/lock/registryLockVerify.component.scss b/console-webapp/src/app/lock/registryLockVerify.component.scss
new file mode 100644
index 000000000..064823060
--- /dev/null
+++ b/console-webapp/src/app/lock/registryLockVerify.component.scss
@@ -0,0 +1,9 @@
+.console-app__registry-lock {
+ &-content {
+ margin-top: 30px;
+ }
+ &-subhead {
+ font-size: 20px;
+ margin-bottom: 20px;
+ }
+}
diff --git a/console-webapp/src/app/lock/registryLockVerify.component.ts b/console-webapp/src/app/lock/registryLockVerify.component.ts
new file mode 100644
index 000000000..192383897
--- /dev/null
+++ b/console-webapp/src/app/lock/registryLockVerify.component.ts
@@ -0,0 +1,65 @@
+// 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.
+
+import { Component } from '@angular/core';
+import { RegistrarService } from '../registrar/registrar.service';
+import { ActivatedRoute, ParamMap } from '@angular/router';
+import { RegistryLockVerifyService } from './registryLockVerify.service';
+import { HttpErrorResponse } from '@angular/common/http';
+import { take } from 'rxjs';
+import { DomainListComponent } from '../domains/domainList.component';
+
+@Component({
+ selector: 'app-registry-lock-verify',
+ templateUrl: './registryLockVerify.component.html',
+ styleUrls: ['./registryLockVerify.component.scss'],
+ providers: [RegistryLockVerifyService],
+})
+export class RegistryLockVerifyComponent {
+ public static PATH = 'registry-lock-verify';
+
+ readonly DOMAIN_LIST_COMPONENT_PATH = `/${DomainListComponent.PATH}`;
+
+ isLoading = true;
+ domainName?: string;
+ action?: string;
+ errorMessage?: string;
+
+ constructor(
+ protected registrarService: RegistrarService,
+ protected registryLockVerifyService: RegistryLockVerifyService,
+ private route: ActivatedRoute
+ ) {}
+
+ ngOnInit() {
+ this.route.queryParamMap.pipe(take(1)).subscribe((params: ParamMap) => {
+ this.registryLockVerifyService
+ .verifyRequest(params.get('lockVerificationCode') || '')
+ .subscribe({
+ error: (err: HttpErrorResponse) => {
+ this.isLoading = false;
+ this.errorMessage = err.error;
+ },
+ next: (verificationResponse) => {
+ this.domainName = verificationResponse.domainName;
+ this.action = verificationResponse.action;
+ this.registrarService.registrarId.set(
+ verificationResponse.registrarId
+ );
+ this.isLoading = false;
+ },
+ });
+ });
+ }
+}
diff --git a/console-webapp/src/app/lock/registryLockVerify.service.ts b/console-webapp/src/app/lock/registryLockVerify.service.ts
new file mode 100644
index 000000000..3b34258de
--- /dev/null
+++ b/console-webapp/src/app/lock/registryLockVerify.service.ts
@@ -0,0 +1,31 @@
+// 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.
+
+import { Injectable } from '@angular/core';
+import { BackendService } from '../shared/services/backend.service';
+
+export interface RegistryLockVerificationResponse {
+ action: string;
+ domainName: string;
+ registrarId: string;
+}
+
+@Injectable()
+export class RegistryLockVerifyService {
+ constructor(private backendService: BackendService) {}
+
+ verifyRequest(lockVerificationCode: string) {
+ return this.backendService.verifyRegistryLockRequest(lockVerificationCode);
+ }
+}
diff --git a/console-webapp/src/app/shared/services/backend.service.ts b/console-webapp/src/app/shared/services/backend.service.ts
index 698cbbb04..26bbeb895 100644
--- a/console-webapp/src/app/shared/services/backend.service.ts
+++ b/console-webapp/src/app/shared/services/backend.service.ts
@@ -25,6 +25,7 @@ import {
import { Contact } from '../../settings/contact/contact.service';
import { EppPasswordBackendModel } from '../../settings/security/security.service';
import { UserData } from './userData.service';
+import { RegistryLockVerificationResponse } from 'src/app/lock/registryLockVerify.service';
@Injectable()
export class BackendService {
@@ -169,4 +170,12 @@ export class BackendService {
whoisRegistrarFields
);
}
+
+ verifyRegistryLockRequest(
+ lockVerificationCode: string
+ ): Observable {
+ return this.http.get(
+ `/console-api/registry-lock-verify?lockVerificationCode=${lockVerificationCode}`
+ );
+ }
}
diff --git a/core/src/main/java/google/registry/tools/DomainLockUtils.java b/core/src/main/java/google/registry/tools/DomainLockUtils.java
index 6c34ac94c..76ddff8db 100644
--- a/core/src/main/java/google/registry/tools/DomainLockUtils.java
+++ b/core/src/main/java/google/registry/tools/DomainLockUtils.java
@@ -343,7 +343,7 @@ public final class DomainLockUtils {
.orElseThrow(
() ->
new IllegalArgumentException(
- String.format("Invalid verification code %s", verificationCode)));
+ String.format("Invalid verification code \"%s\"", verificationCode)));
}
private void applyLockStatuses(RegistryLock lock, DateTime lockTime, boolean isAdmin) {
diff --git a/core/src/main/java/google/registry/ui/server/console/ConsoleApiAction.java b/core/src/main/java/google/registry/ui/server/console/ConsoleApiAction.java
index d4d3a3bc6..53d2725d6 100644
--- a/core/src/main/java/google/registry/ui/server/console/ConsoleApiAction.java
+++ b/core/src/main/java/google/registry/ui/server/console/ConsoleApiAction.java
@@ -161,7 +161,7 @@ public abstract class ConsoleApiAction implements Runnable {
Map registrarDiffMap = registrar.toDiffableFieldMap();
Stream.of("passwordHash", "salt") // fields to remove from final diff
- .forEach(fieldToBeRemoved -> registrarDiffMap.remove(fieldToBeRemoved));
+ .forEach(registrarDiffMap::remove);
// Use LinkedHashMap here to preserve ordering; null values mean we can't use ImmutableMap.
LinkedHashMap result = new LinkedHashMap<>(registrarDiffMap);
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
index 8ff207bb7..803083731 100644
--- a/core/src/test/java/google/registry/ui/server/console/ConsoleRegistryLockVerifyActionTest.java
+++ b/core/src/test/java/google/registry/ui/server/console/ConsoleRegistryLockVerifyActionTest.java
@@ -156,7 +156,7 @@ public class ConsoleRegistryLockVerifyActionTest {
action.run();
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_BAD_REQUEST);
assertThat(response.getPayload())
- .isEqualTo("Invalid verification code 123456789ABCDEFGHJKLMNPQRSTUUUUU");
+ .isEqualTo("Invalid verification code \"123456789ABCDEFGHJKLMNPQRSTUUUUU\"");
assertThat(loadByEntity(defaultDomain).getStatusValues()).containsExactly(StatusValue.INACTIVE);
}
diff --git a/core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_registryLockVerify_unknownLock_page.png b/core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_registryLockVerify_unknownLock_page.png
index 765127351..725c4249c 100644
Binary files a/core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_registryLockVerify_unknownLock_page.png and b/core/src/test/resources/google/registry/webdriver/goldens/chrome-linux/RegistrarConsoleScreenshotTest_registryLockVerify_unknownLock_page.png differ