From f54f4bdf19a605d6c4eef3e1d37cbe4b3274af8c Mon Sep 17 00:00:00 2001 From: oleghasjanov Date: Mon, 10 Aug 2026 11:26:05 +0300 Subject: [PATCH] Add a button to cancel a pending registrant change A registrant change that went pending could not be called back from the portal: the registrar had to wait 48 hours for it to expire or ask the registry to clear it by hand. The registry now exposes DELETE /repp/v1/domains/:domain_id/pending_update (internetee/registry#2939). Wire it up and show the button on the domain page whenever the domain carries pendingUpdate, next to the existing gate that hides Delete for pendingDelete domains. While here, fix the pendingUpdate status label. The key carried a stray quote, so it parsed as `pendingUpdate"` and the status chip rendered an empty title while its tooltip worked. The new button sits right next to that chip. The same defect affects the server-side status keys, and statuses.en.yml declares an `et:` root; both are left for a separate cleanup. --- app/controllers/domains_controller.rb | 9 ++++ .../domains/pending_update_canceller.rb | 23 ++++++++ app/views/domains/show.html.erb | 9 ++++ config/locales/en.yml | 4 +- config/locales/et.yml | 4 +- config/locales/statuses.en.yml | 2 +- config/locales/statuses.et.yml | 2 +- config/routes.rb | 2 + spec/controllers/domains_controller_spec.rb | 7 +++ .../cancel_pending_update-auth-fail.yml | 53 +++++++++++++++++++ .../cancel_pending_update.yml | 53 +++++++++++++++++++ 11 files changed, 164 insertions(+), 4 deletions(-) create mode 100644 app/services/api_connector/domains/pending_update_canceller.rb create mode 100644 spec/fixtures/vcr_cassettes/controllers/domains_controller/cancel_pending_update-auth-fail.yml create mode 100644 spec/fixtures/vcr_cassettes/controllers/domains_controller/cancel_pending_update.yml diff --git a/app/controllers/domains_controller.rb b/app/controllers/domains_controller.rb index c9d6cc92..925ea168 100644 --- a/app/controllers/domains_controller.rb +++ b/app/controllers/domains_controller.rb @@ -104,6 +104,15 @@ def regenerate_transfer_code redirect_to domain_path(domain_name: @response.domain[:name]) end + def cancel_pending_update + conn = ApiConnector::Domains::PendingUpdateCanceller.new(**auth_info) + result = conn.call_action(payload: { name: params[:domain_name] }) + handle_response(result); return if performed? + + flash.notice = @message + redirect_to domain_path(domain_name: @response.domain[:name]) + end + def delete authorize! :delete, 'Epp::Domain' end diff --git a/app/services/api_connector/domains/pending_update_canceller.rb b/app/services/api_connector/domains/pending_update_canceller.rb new file mode 100644 index 00000000..b28954dd --- /dev/null +++ b/app/services/api_connector/domains/pending_update_canceller.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +class ApiConnector + module Domains + class PendingUpdateCanceller < ApiConnector + ACTION = 'cancel_pending_update' + ENDPOINT = { + method: 'delete', + endpoint: '/domains', + }.freeze + + def cancel_pending_update(payload: nil) + request(url: url_with_id(CGI.escape(payload[:name])), method: method) + end + + private + + def url_with_id(domain_name) + "#{endpoint_url}/#{domain_name}/pending_update" + end + end + end +end diff --git a/app/views/domains/show.html.erb b/app/views/domains/show.html.erb index f772ef7b..fdb82a36 100644 --- a/app/views/domains/show.html.erb +++ b/app/views/domains/show.html.erb @@ -20,6 +20,15 @@ <%= link_to t(:delete), delete_domain_path(domain_name: @domain[:name]), class: 'button button--danger' %> <% end %> + <% if @domain[:statuses].key?('pendingUpdate') %> +
+ <%= link_to t('.cancel_pending_update'), + domain_cancel_pending_update_path(domain_name: @domain[:name]), + class: 'button button--danger', + data: { 'turbo-method': 'delete', + turbo_confirm: t('.cancel_pending_update_confirm') } %> +
+ <% end %> diff --git a/config/locales/en.yml b/config/locales/en.yml index 7a969f68..92a9c48b 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -231,7 +231,9 @@ en: flag: "Flag" public_key: "Public key" regenerate: "Regenerate transfer code" - + cancel_pending_update: "Cancel registrant change" + cancel_pending_update_confirm: "Cancel the pending registrant change of this domain?" + edit: title: "Edit domain" diff --git a/config/locales/et.yml b/config/locales/et.yml index ba2c2863..cc880241 100644 --- a/config/locales/et.yml +++ b/config/locales/et.yml @@ -238,7 +238,9 @@ et: flag: "Lipp" public_key: "Avalik võti" regenerate: "Regenerate transfer code" - + cancel_pending_update: "Tühista registreerija vahetus" + cancel_pending_update_confirm: "Kas tühistada selle domeeni ootel registreerija vahetus?" + edit: title: "Muuda domeeni" diff --git a/config/locales/statuses.en.yml b/config/locales/statuses.en.yml index 5181f7d7..fd3c2ca9 100644 --- a/config/locales/statuses.en.yml +++ b/config/locales/statuses.en.yml @@ -32,7 +32,7 @@ et: pendingRenew_text: "Renew command has been processed for the domain registration, but the action has not been completed by the server." pendingTransfer: "pendingTransfer" pendingTransfer_text: "Transfer command has been processed for the domain registration, but the action has not been completed by the server." - pendingUpdate": "pendingUpdate" + pendingUpdate: "pendingUpdate" pendingUpdate_text: "Update command has been processed for the domain registration, but the action has not been completed by the server." serverAdminChangeProhibited: "serverAdminChangeProhibited" serverAdminChangeProhibited_text: "Requests to replace admin contacts are rejected." diff --git a/config/locales/statuses.et.yml b/config/locales/statuses.et.yml index bbd30ec5..260a2ca0 100644 --- a/config/locales/statuses.et.yml +++ b/config/locales/statuses.et.yml @@ -32,7 +32,7 @@ et: pendingRenew_text: "Domeeni registreeringu pikendamine on ootel." pendingTransfer: "Registripidaja vahetus ootel" pendingTransfer_text: "Registripidaja vahetus on ootel." - pendingUpdate": "Andmete muutmine ootel" + pendingUpdate: "Andmete muutmine ootel" pendingUpdate_text: "Oodatakse registreerija kinnitust registreerija vahetuse päringule" serverAdminChangeProhibited: "Halduskontakti vahetus keelatud" serverAdminChangeProhibited_text: "Domeeni halduskontakti(de) muutmine on keelatud" diff --git a/config/routes.rb b/config/routes.rb index 59b162ff..4fecd41c 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -31,6 +31,8 @@ post 'domains/transfer', to: 'domains#transfer', as: :domain_transfer post 'domains/renew', to: 'domains#renew', as: :domain_renew delete 'domains/destroy', to: 'domains#destroy', as: :destroy_domain + delete 'domain/cancel_pending_update', to: 'domains#cancel_pending_update', + as: :domain_cancel_pending_update resources :domains, except: %i[destroy update show edit] resources :bulk_change, only: %i[show update], controller: 'steps_controllers/bulk_change' diff --git a/spec/controllers/domains_controller_spec.rb b/spec/controllers/domains_controller_spec.rb index 6b95ff36..a7c779c6 100644 --- a/spec/controllers/domains_controller_spec.rb +++ b/spec/controllers/domains_controller_spec.rb @@ -129,6 +129,13 @@ params: { domain_name: domain, }, + }, + { + method: :cancel_pending_update, + http_method: :delete, + params: { + domain_name: domain, + }, } ] diff --git a/spec/fixtures/vcr_cassettes/controllers/domains_controller/cancel_pending_update-auth-fail.yml b/spec/fixtures/vcr_cassettes/controllers/domains_controller/cancel_pending_update-auth-fail.yml new file mode 100644 index 00000000..30510890 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/controllers/domains_controller/cancel_pending_update-auth-fail.yml @@ -0,0 +1,53 @@ +--- +http_interactions: +- request: + method: delete + uri: http://registry:3000/repp/v1/domains/example092022.ee/pending_update + body: + encoding: UTF-8 + string: '' + headers: + Authorization: + - "" + User-Agent: + - Faraday v2.3.0 + Content-Type: + - application/json + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 401 + message: Unauthorized + headers: + X-Frame-Options: + - SAMEORIGIN + X-Xss-Protection: + - 1; mode=block + X-Content-Type-Options: + - nosniff + X-Download-Options: + - noopen + X-Permitted-Cross-Domain-Policies: + - none + Referrer-Policy: + - strict-origin-when-cross-origin + Content-Type: + - application/json; charset=utf-8 + Vary: + - Accept + Cache-Control: + - no-cache + X-Request-Id: + - 3c9d5b81-7a44-4e02-8f6d-95ab2c0e13da + X-Runtime: + - '0.012884' + Transfer-Encoding: + - chunked + body: + encoding: UTF-8 + string: '{"code":2202,"message":"Invalid authorization information","data":{"username":"ullam","password":"HcH9cT6k7O79","active":null}}' + recorded_at: Mon, 10 Aug 2026 08:30:00 GMT +recorded_with: VCR 6.1.0 diff --git a/spec/fixtures/vcr_cassettes/controllers/domains_controller/cancel_pending_update.yml b/spec/fixtures/vcr_cassettes/controllers/domains_controller/cancel_pending_update.yml new file mode 100644 index 00000000..5db3519c --- /dev/null +++ b/spec/fixtures/vcr_cassettes/controllers/domains_controller/cancel_pending_update.yml @@ -0,0 +1,53 @@ +--- +http_interactions: +- request: + method: delete + uri: http://registry:3000/repp/v1/domains/example092022.ee/pending_update + body: + encoding: UTF-8 + string: '' + headers: + Authorization: + - "" + User-Agent: + - Faraday v2.3.0 + Content-Type: + - application/json + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + X-Frame-Options: + - SAMEORIGIN + X-Xss-Protection: + - 1; mode=block + X-Content-Type-Options: + - nosniff + X-Download-Options: + - noopen + X-Permitted-Cross-Domain-Policies: + - none + Referrer-Policy: + - strict-origin-when-cross-origin + Content-Type: + - application/json; charset=utf-8 + Vary: + - Accept + Cache-Control: + - no-cache + X-Request-Id: + - 8f2a1c47-2d5e-4f19-9c3b-1b6e0d7a5f42 + X-Runtime: + - '0.041207' + Transfer-Encoding: + - chunked + body: + encoding: UTF-8 + string: '{"code":1000,"message":"Command completed successfully","data":{"domain":{"name":"example092022.ee"}}}' + recorded_at: Mon, 10 Aug 2026 08:30:00 GMT +recorded_with: VCR 6.1.0