Skip to content

Commit

Permalink
Refactor recovery codes to switch everything to stimulus clipboard
Browse files Browse the repository at this point in the history
  • Loading branch information
martinemde authored and simi committed Sep 30, 2024
1 parent bd5a7f3 commit 4ae91b5
Show file tree
Hide file tree
Showing 28 changed files with 142 additions and 381 deletions.
29 changes: 0 additions & 29 deletions app/assets/stylesheets/modules/gem.css
Original file line number Diff line number Diff line change
Expand Up @@ -187,35 +187,6 @@
.gem__code__icon.static {
position: static; }

.gem__code__tooltip--copy,
.gem__code__tooltip--copied {
display: none; }

.clipboard-is-hover,
.clipboard-is-active {
display: block;
position: absolute;
top: 45px;
right: 0;
width: auto;
padding-left: 10px;
padding-right: 10px;
z-index: 1;
border-radius: 6px;
background-color: #141c22;
text-transform: none;
line-height: 30px;
text-align: center;
color: #ffffff; }
.clipboard-is-hover:before,
.clipboard-is-active:before {
content: "";
position: absolute;
top: -15px;
right: 8px;
border: 8px solid transparent;
border-bottom: 8px solid #141c22; }

.gem__link:before {
margin-right: 16px; }

Expand Down
14 changes: 14 additions & 0 deletions app/assets/stylesheets/modules/shared.css
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,20 @@ span.github-btn {
margin-top: 5px;
}

.recovery-code-list {
border: none;
padding: 10px 20px;
font-size: 1.2em;
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
resize: none;
background: none;
}

.recovery-code-list:focus {
background: none;
outline: none;
}

.recovery-code-list__item {
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
}
Expand Down
30 changes: 30 additions & 0 deletions app/helpers/rubygems_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -188,4 +188,34 @@ def github_params(rubygem)
size: "large"
}
end

def copy_field_tag(name, value)
field = text_field_tag(
name,
value,
id: name,
class: "gem__code",
readonly: "readonly",
data: { clipboard_target: "source" }
)

button = tag.span(
"=",
class: "gem__code__icon",
title: t("copy_to_clipboard"),
data: {
action: "click->clipboard#copy",
clipboard_target: "button"
}
)

tag.div(
field + button,
class: "gem__code-wrap",
data: {
controller: "clipboard",
clipboard_success_content_value: "✔"
}
)
end
end
2 changes: 0 additions & 2 deletions app/javascript/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ LocalTime.start()

import "controllers"

import "src/clipboard_buttons";
import "src/multifactor_auths";
import "src/oidc_api_key_role_form";
import "src/pages";
import "src/search";
Expand Down
4 changes: 4 additions & 0 deletions app/javascript/controllers/application.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { Application } from "@hotwired/stimulus"
import Clipboard from '@stimulus-components/clipboard'

const application = Application.start()

// Add vendored controllers
application.register('clipboard', Clipboard)

// Configure Stimulus development experience
application.debug = false
window.Stimulus = application
Expand Down
39 changes: 39 additions & 0 deletions app/javascript/controllers/recovery_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
static values = {
confirm: { type: String, default: "Leave without copying recovery codes?" }
}

connect() {
this.copied = false;
window.addEventListener("beforeunload", this.popUp);
}

popUp(e) {
e.preventDefault();
e.returnValue = "";
}

copy() {
if (!this.copied) {
this.copied = true;
window.removeEventListener("beforeunload", this.popUp);
}
}

submit(e) {
e.preventDefault();

if (!this.element.checkValidity()) {
this.element.reportValidity();
return;
}

if (this.copied || confirm(this.confirmValue)) {
window.removeEventListener("beforeunload", this.popUp);
// Don't include the form data in the URL.
window.location.href = this.element.action;
}
}
}
38 changes: 0 additions & 38 deletions app/javascript/src/clipboard_buttons.js

This file was deleted.

43 changes: 0 additions & 43 deletions app/javascript/src/multifactor_auths.js

This file was deleted.

33 changes: 20 additions & 13 deletions app/views/multifactor_auths/recovery.html.erb
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
<% @title = t(".title") %>
<div class="t-body">
<%= tag.div(
class: "t-body",
data: {
controller: "clipboard",
clipboard_success_content_value: t('copied')
}
) do %>
<p><%= t ".note_html" %></p>

<ul id="recovery-code-list">
<% @mfa_recovery_codes.each do |code| %>
<li class="recovery-code-list__item"><%= code %></li>
<% end %>
</ul>
<%# This tag contains the recovery codes and should not be a part of the form %>
<%= text_area_tag "source", "#{@mfa_recovery_codes.join("\n")}\n", class: "recovery-code-list", rows: @mfa_recovery_codes.size + 1, cols: @mfa_recovery_codes.first.length + 1, readonly: true, data: { clipboard_target: "source" } %>
<p><%= link_to t(".copy"), "#/", class: "t-link--bold recovery__copy__icon", data: { "clipboard-target": "#recovery-code-list" } %></p>
<div class = "form__checkbox__item">
<%= check_box_tag "checked", "ack", false, class: "form__checkbox__input" %>
<%= label_tag "checked", t(".saved"), class: "form__checkbox__label" %>
</div>
<%= button_to t(".continue"), @continue_path, method: "get", class: "form__submit form__submit--no-hover", disabled: true %>
</div>
<%= form_tag(@continue_path, method: "get", class: "form", data: { controller: "recovery", recovery_confirm_value: t(".confirm_dialog"), action: "recovery#submit" }) do %>
<p><%= link_to t("copy_to_clipboard"), "#/", class: "t-link--bold recovery__copy__icon", data: { action: "clipboard#copy recovery#copy", clipboard_target: "button" } %></p>

<div class = "form__checkbox__item">
<%= check_box_tag "checked", "ack", false, required: true, class: "form__checkbox__input" %>
<%= label_tag "checked", t(".saved"), class: "form__checkbox__label" %>
</div>

<%= button_tag t(".continue"), class: "form__submit form__submit--no-hover" %>
<% end %>
<% end %>
12 changes: 7 additions & 5 deletions app/views/oidc/api_key_roles/github_actions_workflow_view.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def view_template

return if not_configured

div(class: "t-body") do
div(class: "t-body", data: { controller: "clipboard", clipboard_success_content_value: "✔" }) do
p do
t(".configured_for_html", link_html:
single_gem_role? ? helpers.link_to(gem_name, rubygem_path(gem_name)) : t(".a_gem"))
Expand All @@ -30,12 +30,14 @@ def view_template

header(class: "gem__code__header") do
h3(class: "t-list__heading l-mb-0") { code { ".github/workflows/push_gem.yml" } }
button(class: "gem__code__icon", data: { "clipboard-target": "#workflow_yaml" }) { "=" }
span(class: "gem__code__tooltip--copy") { t("copy_to_clipboard") }
span(class: "gem__code__tooltip--copied") { t("copied") }
button(
class: "gem__code__icon",
title: t("copy_to_clipboard"),
data: { action: "click->clipboard#copy", clipboard_target: "button" }
) { "=" }
end
pre(class: "gem__code multiline") do
code(class: "multiline", id: "workflow_yaml") do
code(class: "multiline", id: "workflow_yaml", data: { clipboard_target: "source" }) do
plain workflow_yaml
end
end
Expand Down
7 changes: 1 addition & 6 deletions app/views/rubygems/_gem_members.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,7 @@
<% if latest_version.sha256.present? %>
<h3 class="t-list__heading"><%= t '.sha_256_checksum' %>:</h3>
<div class="gem__code-wrap">
<input type="text" class="gem__code" id="gem_sha_256_checksum" value="<%= latest_version.sha256_hex %>" readonly="readonly">
<span class="gem__code__icon" id="js-gem__code--gemfile" data-clipboard-target="#gem_sha_256_checksum">=</span>
<span class="gem__code__tooltip--copy"><%= t('copy_to_clipboard') %></span>
<span class="gem__code__tooltip--copied"><%= t('copied') %></span>
</div>
<%= copy_field_tag("gem_sha_256_checksum", latest_version.sha256_hex) %>
<% end %>
<% if latest_version.cert_chain.present? %>
Expand Down
12 changes: 2 additions & 10 deletions app/views/rubygems/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -31,19 +31,11 @@
<div class="gem__install">
<h2 class="gem__ruby-version__heading t-list__heading">
<%= t '.bundler_header' %>:
<div class="gem__code-wrap">
<input type="text" class="gem__code" id="gemfile_text" value="<%= @latest_version.to_bundler(locked_version: @on_version_page) %>" readonly="readonly">
<span class="gem__code__icon" id="js-gem__code--gemfile" data-clipboard-target="#gemfile_text">=</span>
<span class="gem__code__tooltip--copy"><%= t('copy_to_clipboard') %></span>
<span class="gem__code__tooltip--copied"><%= t('copied') %></span>
</div>
<%= copy_field_tag("gemfile_text", @latest_version.to_bundler(locked_version: @on_version_page)) %>
</h2>
<h2 class="gem__ruby-version__heading t-list__heading">
<%= t '.install' %>:
<div class="gem__code-wrap">
<input type="text" class="gem__code" id="install_text" value="<%= @latest_version.to_install %>" readonly="readonly">
<span class="gem__code__icon" id="js-gem__code--install" data-clipboard-target="#install_text">=</span>
</div>
<%= copy_field_tag("install_text", @latest_version.to_install) %>
</h2>
</div>
<% else %>
Expand Down
2 changes: 1 addition & 1 deletion config/importmap.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
pin "application"
pin_all_from "app/javascript/src", under: "src"

pin "clipboard" # @2.0.11
# stimulus.min.js is a compiled asset from stimulus-rails gem
pin "@hotwired/stimulus", to: "stimulus.min.js"
# stimulus-loading.js is a compiled asset only available from stimulus-rails gem
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js"
pin_all_from "app/javascript/controllers", under: "controllers"
pin "@stimulus-components/clipboard", to: "@stimulus-components--clipboard.js" # @5.0.0

# vendored and adapted from https://github.com/mdo/github-buttons/blob/master/src/js.js
pin "github-buttons"
Expand Down
5 changes: 1 addition & 4 deletions config/locales/de.yml
Original file line number Diff line number Diff line change
Expand Up @@ -537,11 +537,10 @@ de:
mfa_recommended_not_yet_enabled:
mfa_recommended_weak_level_enabled:
recovery:
copied: "[ kopiert ]"
continue: Weiter
title: Wiederherstellungscodes
copy: "[ kopieren ]"
saved: Ich bestätige, dass ich meine Wiederherstellungscodes gespeichert habe.
confirm_dialog:
note_html: Bitte <strong class='recovery__bold'>kopieren und speichern</strong>
Sie diese Wiederherstellungscodes. Sie können diese Codes verwenden, um sich
anzumelden und Ihre MFA zurückzusetzen, wenn Sie Ihr Authentifizierungsgerät
Expand Down Expand Up @@ -896,8 +895,6 @@ de:
continue:
title:
notice_html:
copied:
copy:
saved:
webauthn_credential:
confirm_delete:
Expand Down
5 changes: 1 addition & 4 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -463,11 +463,10 @@ en:
[WARNING] For protection of your account and gems, we encourage you to change your multi-factor authentication level to 'UI and gem signin' or 'UI and API' at https://rubygems.org/settings/edit.
Your account will be required to have MFA enabled on one of these levels in the future.
recovery:
copied: "[ copied ]"
continue: Continue
title: Recovery codes
copy: "[ copy ]"
saved: I acknowledge that I have saved my recovery codes.
confirm_dialog: Leave without copying recovery codes?
note_html: "Please <strong class='recovery__bold'>copy and save</strong> these recovery codes. You can use these codes to login and reset your MFA if your lose your authentication device. Each code can be used once."
already_generated: You should have already saved your recovery codes.
update:
Expand Down Expand Up @@ -809,8 +808,6 @@ en:
continue: Continue
title: You have successfully added a security device
notice_html: 'Please <strong class="recovery__bold">copy and paste</strong> these recovery codes. You can use these codes to login if you lose your security device. Each code can be used once.'
copied: "[ copied ]"
copy: "[ copy ]"
saved: I acknowledge that I have saved my recovery codes.
webauthn_credential:
confirm_delete: "Credential deleted"
Expand Down
Loading

0 comments on commit 4ae91b5

Please sign in to comment.