Skip to content

Commit

Permalink
user secrets page!
Browse files Browse the repository at this point in the history
  • Loading branch information
timcowlishaw committed Dec 12, 2024
1 parent e50f3e1 commit ea05990
Show file tree
Hide file tree
Showing 18 changed files with 189 additions and 19 deletions.
5 changes: 5 additions & 0 deletions app/assets/images/clipboard_icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions app/assets/javascripts/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,7 @@
//= require popper
//= require bootstrap-sprockets
//= require_tree .

$(function() {
setupCopyableInputs();
})
14 changes: 14 additions & 0 deletions app/assets/javascripts/components/copyable_input.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
function setupCopyableInputs() {
$(".copyable-input").each(function(ix, element) {
let input = $(element).find("input");
let button = $(element).find("button");
input.focus(function(event) {
input.select();
});
button.click(function(event) {
input.select();
navigator.clipboard.writeText(input.val());
});
console.log(input, button);
});
}
2 changes: 1 addition & 1 deletion app/assets/stylesheets/application.scss
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@
@import "nav";
@import "footer";
@import "user_profile";

@import "components/copyable_input";
15 changes: 15 additions & 0 deletions app/assets/stylesheets/components/copyable_input.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
.copyable-input {
input {
color: $black;
font-family: $font-family-monospace;
&:active, &:focus, &:focus-visible {
outline-offset: -5px;
border-width: 2px;
}
}
svg {
vertical-align: top;
height: 1rem;
margin-top: 3px;
}
}
20 changes: 19 additions & 1 deletion app/assets/stylesheets/global.scss
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ body{
flex-direction: column;
}


::selection {
background-color: $yellow;
color: $black;
}

body > .container {
flex-grow: 1;
}
Expand Down Expand Up @@ -42,6 +48,16 @@ body > .container {
color: $white;
border: 3px solid $white;
}
&.btn-right {
border-radius: 0 $btn-border-radius $btn-border-radius 0;
border: 2px solid $black;
border-left: none;
padding-left: 1rem;
&:hover, &:active, &:focus, &:focus-visible {
border-left: none !important;
border-width: 2px !important;
}
}
&:hover, &:active, &:focus, &:focus-visible {
background-color: $primary !important;
color: $black !important;
Expand All @@ -61,6 +77,9 @@ body > .container {

.form-control {
border-radius: 2rem;
.input-group-lg & {
border-radius: 2rem;
}
padding: 0.75rem 1.5rem;
border-width: 2px;
&:focus {
Expand Down Expand Up @@ -155,4 +174,3 @@ input[type="file"] {
color: $yellow !important;
}
}

22 changes: 16 additions & 6 deletions app/controllers/ui/users_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,24 @@ def index

def show
@user = User.friendly.find(params[:id])
@title = I18n.t(:users_show_title, username: @user.username)
@title = I18n.t(:show_user_title, username: @user.username)
render "show", layout: "base"
end

def secrets
@user = User.friendly.find(params[:id])
unless authorize? @user, :show_secrets?
flash[:alert] = I18n.t(:edit_user_forbidden)
redirect_to current_user ? ui_user_path(@user.username) : login_path
return
end
@title = I18n.t(:secrets_user_title, username: @user.username)
end

def new
if current_user
flash[:alert] = I18n.t(:new_user_not_allowed_for_logged_in_users)
redirect_to ui_users_path
redirect_to ui_user_path(current_user.username)
return
end
@title = I18n.t(:new_user_title)
Expand All @@ -25,7 +35,7 @@ def new
def create
if current_user
flash[:alert] = I18n.t(:new_user_not_allowed_for_logged_in_users)
redirect_to ui_users_path
redirect_to ui_user_path(current_user.username)
return
end
@user = User.new(params.require(:user).permit(
Expand All @@ -39,7 +49,7 @@ def create
@user.save
session[:user_id] = @user.id
flash[:success] = I18n.t(:new_user_success)
redirect_to ui_users_path
redirect_to ui_user_path(@user.username)
else
flash[:alert] = I18n.t(:new_user_failure)
render :new, status: :unprocessable_entity
Expand Down Expand Up @@ -85,7 +95,7 @@ def delete
@user = User.friendly.find(params[:id])
unless authorize? @user, :destroy?
flash[:alert] = I18n.t(:delete_user_forbidden)
redirect_to current_user ? ui_users_path : login_path
redirect_to current_user ? ui_user_path(@user) : login_path
return
end
@title = I18n.t(:delete_user_title)
Expand All @@ -95,7 +105,7 @@ def destroy
@user = User.friendly.find(params[:id])
unless authorize? @user, :destroy?
flash[:alert] = I18n.t(:delete_user_forbidden)
redirect_to current_user ? ui_users_path : login_path
redirect_to current_user ? ui_user_path(@user) : login_path
return
end
if @user.username != params[:username]
Expand Down
5 changes: 5 additions & 0 deletions app/helpers/application_helper.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
module ApplicationHelper
def show_svg(path)
File.open("app/assets/images/#{path}", "rb") do |file|
raw file.read
end
end
def flash_class(level)
case level.to_sym
when :success then "alert alert-success"
Expand Down
4 changes: 4 additions & 0 deletions app/policies/user_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,8 @@ def request_password_reset?
def update_password?
create?
end

def show_secrets?
user == record
end
end
9 changes: 9 additions & 0 deletions app/views/ui/shared/_copyable_input.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<label class="form-label" for="copyable-input-<%= id %>"><%= name %></label>
<div class="copyable-input input-group input-group-lg mb-3">
<input type="text" class="form-control" placeholder="<%= name %>" value="<%= value %>" id="copyable-input-<%= id%>">
<div class="input-group-append">
<button class="btn btn-primary btn-right" title="<%= t(:copyable_input_button_title) %>" id="api-key-copy-button" type="button" aria-controls="copyable-input-<%= id %>">
<%= show_svg("clipboard_icon.svg") %>
</button>
</div>
</div>
23 changes: 23 additions & 0 deletions app/views/ui/users/secrets.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<p><%= t :secrets_user_blurb_html %></p>
<%= render partial: "ui/shared/copyable_input", locals: {
name: t(:secrets_user_api_key_label),
value: @user.access_token.token,
id: "api-key"
} %>

<% if @user.forward_device_readings? %>
<div class="mt-5">
<h1 class="mb-4"><%= t :secrets_user_forwarding_heading %></h1>
<p><%= t :secrets_user_forwarding_blurb_html %></p>
<%= render partial: "ui/shared/copyable_input", locals: {
name: t(:secrets_user_forwarding_token_label),
value: @user.forwarding_token,
id: "forwarding-token"
} %>
<%= render partial: "ui/shared/copyable_input", locals: {
name: t(:secrets_user_forwarding_username_label),
value: @user.forwarding_username,
id: "forwarding-username"
} %>
</div>
<% end %>
16 changes: 11 additions & 5 deletions app/views/ui/users/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
) %>
</div>
</div>
<div class="col-12 col-sm-6 col-md-9 col-lg-10 mt-4 mt-sm-0 mx-2 mx-sm-0 mb-2 mb-sm-0 px-2 px-sm-0 ps-sm-3 ps-md-4 justify-content-between d-flex g-0 flex-column">
<div class="col-12 col-sm-6 col-md-9 col-lg-10 mt-4 mt-sm-0 mb-2 mb-sm-0 px-3 justify-content-between d-flex g-0 flex-column">
<div class="meta">
<h1 class="mb-2"><%= t(:show_user_headline, username: @user.username) %></h1>
<p class="mb-0 small">
Expand All @@ -30,12 +30,18 @@
</p>
<% end %>
</div>
<div class="actions">
<% if authorize? @user, :update? %>
<div class="actions mt-3">
<p class="mb-0">
<%= link_to(t(:show_user_edit_cta), edit_ui_user_path(@user), class: "btn btn-dark mt-3 mt-sm-0") %>
<% if authorize? @user, :update? %>
<%= link_to(t(:show_user_edit_cta), edit_ui_user_path(@user), class: "btn btn-dark me-md-2 w-100 w-md-auto") %>
<% end %>
<% if authorize? @user, :show_secrets? %>
<%= link_to(t(:show_user_secrets_cta), secrets_ui_user_path(@user), class: "btn btn-dark me-md-2 mt-3 mt-md-0 w-100 w-md-auto") %>
<% end %>
<% if @user %>
<%= link_to(t(:show_user_log_out_cta), logout_path, class: "btn btn-dark mt-3 mt-lg-0 w-100 w-md-auto") %>
<% end %>
</p>
<% end %>
</div>
</div>
</header>
Expand Down
3 changes: 2 additions & 1 deletion config/locales/controllers/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ en:
password_reset_invalid: "Your reset code might be too old or have been used before."
destroy_session_success: "Logged out!"
users_index_title: "User information"
users_show_title: "%{username}'s profile"
show_user_title: "%{username}'s profile"
secrets_user_title: "Your API keys"
new_user_title: "Sign up"
new_user_success: "Thanks for signing up! You are now logged in."
new_user_failure: "Some errors prevented us from creating your account. Please check below and try again!"
Expand Down
2 changes: 2 additions & 0 deletions config/locales/views/shared/copyable_input/en.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
en:
copyable_input_button_title: Copy to clipboard
10 changes: 10 additions & 0 deletions config/locales/views/users/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@ en:
show_user_headline: "User: %{username}"
show_user_profile_pic_alt: "Profile picture for user %{username}"
show_user_edit_cta: "Edit your profile"
show_user_secrets_cta: "Show your API keys"
show_user_log_out_cta: "Sign out"
secrets_user_blurb_html: This key gives you access to your data in the SmartCitizen platform using the <a href="https://developer.smartcitizen.me/" target="_blank">REST API</a>. <strong>Please keep it safe and do not share with anyone, just as you would with a password</strong>.


secrets_user_api_key_label: API key
secrets_user_forwarding_heading: Your MQTT Forwarding Keys
secrets_user_forwarding_blurb_html: Your account has MQTT forwarding enabled, so you can connect directly to our MQTT broker to receive new readings from your devices as they are posted. You will need the following username and token to authenticate. <strong>As above, please treat these credentials as if they were your password, and do not share them with anyone.</strong>.
secrets_user_forwarding_token_label: MQTT forwarding token
secrets_user_forwarding_username_label: MQTT forwarding username
new_user_submit: "Sign up"
new_user_ts_and_cs_label_html: "I accept the <a target=\"_blank\" href=\"https://smartcitizen.me/policy\">terms and conditions</a>"
new_user_login_heading: "Already have an account?"
Expand Down
1 change: 1 addition & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
member do
get :delete
get :edit
get :secrets
end
collection do
get :post_delete
Expand Down
37 changes: 32 additions & 5 deletions spec/controllers/ui/users_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
context "when a user is logged in" do
it "displays an error message and redirects to the ui users path" do
get :new, session: { user_id: user.id }
expect(response).to redirect_to(ui_users_path)
expect(response).to redirect_to(ui_user_path(user.username))
expect(flash[:alert]).to be_present
end
end
Expand All @@ -52,7 +52,7 @@
it "displays an error message and redirects to the ui users path, without creating a user" do
expect_any_instance_of(User).not_to receive(:save)
post :create, params: { user: user_params }, session: { user_id: user.id }
expect(response).to redirect_to(ui_users_path)
expect(response).to redirect_to(ui_user_path(user.username))
expect(flash[:alert]).to be_present
end
end
Expand All @@ -62,7 +62,7 @@
it "creates a user, logs them in, and redirects to the ui user path" do
expect_any_instance_of(User).to receive(:save)
post :create, params: { user: user_params }, session: { user_id: nil }
expect(response).to redirect_to(ui_users_path)
expect(response).to redirect_to(ui_user_path(user_params[:username]))
expect(flash[:success]).to be_present
end
end
Expand Down Expand Up @@ -90,6 +90,33 @@
end


describe "secrets" do
context "when the correct user is logged in" do
it "displays the edit user form" do
get :secrets, params: { id: user.username }, session: { user_id: user.id }
expect(response).to have_http_status(:success)
expect(response).to render_template(:secrets)
end
end

context "when an different user is logged in" do
let(:other_user) { create(:user) }
it "redirects to the ui users page" do
get :secrets, params: { id: user.username }, session: { user_id: other_user.id }
expect(response).to redirect_to(ui_user_path(user.username))
expect(flash[:alert]).to be_present
end
end

context "when no user is logged in" do
it "redirects to the login page" do
get :secrets, params: { id: user.username }, session: { user_id: nil }
expect(response).to redirect_to(login_path)
expect(flash[:alert]).to be_present
end
end
end

describe "edit" do
context "when the correct user is logged in" do
it "displays the edit user form" do
Expand Down Expand Up @@ -186,7 +213,7 @@
let(:other_user) { create(:user) }
it "redirects to the ui users page" do
get :delete, params: { id: user.username }, session: { user_id: other_user.id }
expect(response).to redirect_to(ui_users_path)
expect(response).to redirect_to(ui_user_path(user.username))
expect(flash[:alert]).to be_present
end
end
Expand Down Expand Up @@ -235,7 +262,7 @@
delete :destroy,
params: { id: user.username, username: user.username },
session: { user_id: other_user.id }
expect(response).to redirect_to(ui_users_path)
expect(response).to redirect_to(ui_user_path(user.username))
expect(flash[:alert]).to be_present
expect(session[:user_id]).to eq(other_user.id)
end
Expand Down
16 changes: 16 additions & 0 deletions spec/features/user_management_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -177,4 +177,20 @@
expect(page).to have_content("https://example.com")
expect(page).to have_content("Your profile has been updated!")
end

scenario "User views their secrets" do
password = "password123"
username = "username"
user = create(:user, username: username, password: password, password_confirmation: password)
visit "/login"
fill_in "Username or email", with: user.email
fill_in "Password", with: password
click_on "Sign into your account"
expect(page).to have_current_path(ui_users_path)
click_on "Your profile"
expect(page).to have_current_path(ui_user_path(user.username))
click_on "Show your API keys"
expect(page).to have_current_path(secrets_ui_user_path(user.username))
expect(page).to have_content(user.access_token.token)
end
end

0 comments on commit ea05990

Please sign in to comment.