diff --git a/app/controllers/api/encryptables_controller.rb b/app/controllers/api/encryptables_controller.rb
index 371b7c3aa..bf8d8eaf5 100644
--- a/app/controllers/api/encryptables_controller.rb
+++ b/app/controllers/api/encryptables_controller.rb
@@ -72,6 +72,16 @@ def model_class
end
# rubocop:enable Metrics/MethodLength
+ def fetch_entries
+ if encryptable_file?
+ super
+ elsif tag_param.present?
+ user_encryptables.find_by(tag: tag_param)
+ else
+ Encryptables::FilteredList.new(current_user, params).fetch_entries
+ end
+ end
+
def build_entry
return build_encryptable_file if encryptable_file?
diff --git a/app/presenters/encryptables/filtered_list.rb b/app/presenters/encryptables/filtered_list.rb
new file mode 100644
index 000000000..785dd0f47
--- /dev/null
+++ b/app/presenters/encryptables/filtered_list.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+module ::Encryptables
+ class FilteredList < ::FilteredList
+
+ def fetch_entries
+ filtered_encryptables = encryptables
+
+ filtered_encryptables = filter_by_recent if recent?
+ filtered_encryptables = filter_by_query(filtered_encryptables) if query
+
+
+ filtered_encryptables
+ end
+
+ private
+
+ def query
+ @params[:q]&.strip&.downcase
+ end
+
+ def recent?
+ true?(@params[:recent])
+ end
+
+ def encryptables
+ @current_user.encryptables
+ end
+
+ def filter_by_query(encryptables)
+ encryptables.where(
+ 'lower(encryptables.description) LIKE :query
+ OR lower(encryptables.name) LIKE :query',
+ query: "%#{query}%"
+ )
+ end
+
+ def filter_by_recent
+ Version
+ .includes(:encryptable, encryptable: [:folder])
+ .where(whodunnit: @current_user)
+ .order(created_at: :desc)
+ .group(:item_id, :item_type)
+ .select(:item_id, :item_type)
+ .limit(5)
+ .map(&:encryptable)
+ end
+ end
+end
diff --git a/app/presenters/filtered_list.rb b/app/presenters/filtered_list.rb
index 92b3a2b62..fb3dce893 100644
--- a/app/presenters/filtered_list.rb
+++ b/app/presenters/filtered_list.rb
@@ -15,4 +15,8 @@ def fetch_entries
def list_param(key)
@params[key].to_a.map(&:to_i)
end
+
+ def true?(value)
+ %w[1 yes true].include?(value.to_s.downcase)
+ end
end
diff --git a/frontend/app/routes/dashboard.js b/frontend/app/routes/dashboard.js
index 3094f7e35..a09592dba 100644
--- a/frontend/app/routes/dashboard.js
+++ b/frontend/app/routes/dashboard.js
@@ -12,22 +12,31 @@ export default class DashboardRoute extends BaseRoute {
};
async model(params) {
- params["limit"] = 20;
const favouriteTeams = await this.getFavouriteTeams(params);
- const teams = this.getTeams(params);
+ const teams = await this.getTeams(params);
+ const recentCredentials = await this.getRecentCredentials(params);
+
return RSVP.hash({
favouriteTeams,
- teams
+ teams,
+ recentCredentials
});
}
async getFavouriteTeams(params) {
+ params["limit"] = 20;
params["favourite"] = true;
return await this.store.query("team", params);
}
async getTeams(params) {
+ params["limit"] = 20;
params["favourite"] = false;
return await this.store.query("team", params);
}
+
+ async getRecentCredentials(params) {
+ params["recent"] = true;
+ return await this.store.query("encryptable", params);
+ }
}
diff --git a/frontend/app/templates/components/dashboard-card.hbs b/frontend/app/templates/components/dashboard-card.hbs
index 1c13f91bc..be8342ea0 100644
--- a/frontend/app/templates/components/dashboard-card.hbs
+++ b/frontend/app/templates/components/dashboard-card.hbs
@@ -7,5 +7,5 @@
-
-
\ No newline at end of file
+
+
diff --git a/frontend/app/templates/dashboard.hbs b/frontend/app/templates/dashboard.hbs
index 1046fa61a..bfc3e46c8 100644
--- a/frontend/app/templates/dashboard.hbs
+++ b/frontend/app/templates/dashboard.hbs
@@ -1,20 +1,32 @@
+ {{#if @model.recentCredentials}}
+
+
+ {{#each this.model.recentCredentials as |encryptable|}}
+
+
+
+ {{/each}}
+
+ {{/if}}
+
{{#if @model.favouriteTeams}}
{{#each this.model.favouriteTeams as |team|}}
-
+
{{/each}}
{{/if}}
+
{{#if this.model.teams}}
{{#each this.model.teams as |team|}}
-
+
{{/each}}
diff --git a/frontend/tests/integration/components/dashboard-card-test.js b/frontend/tests/integration/components/dashboard-card-test.js
index 42a1806c1..316b4b05a 100644
--- a/frontend/tests/integration/components/dashboard-card-test.js
+++ b/frontend/tests/integration/components/dashboard-card-test.js
@@ -20,7 +20,7 @@ module("Integration | Component | dashboard-card", function (hooks) {
// Template block usage:
await render(hbs`
-
+
`);
diff --git a/spec/controllers/api/encryptables_controller_spec.rb b/spec/controllers/api/encryptables_controller_spec.rb
index e551e166a..3122d15de 100644
--- a/spec/controllers/api/encryptables_controller_spec.rb
+++ b/spec/controllers/api/encryptables_controller_spec.rb
@@ -21,7 +21,8 @@
get :index, params: { 'q': 'Personal' }, xhr: true
- credentials1_json = data.second
+ expect(data.count).to eq 1
+ credentials1_json = data.first
credentials1_json_attributes = credentials1_json['attributes']
credentials1_json_relationships = credentials1_json['relationships']
@@ -37,7 +38,7 @@
expect_json_object_includes_keys(credentials1_json_relationships, nested_models)
end
- it 'returns all enncryptables if empty query param given' do
+ it 'returns all encryptables if empty query param given' do
login_as(:alice)
get :index, params: { 'q': '' }, xhr: true
@@ -129,6 +130,87 @@
expect(response.status).to eq(403)
end
+
+ context 'recent Credentials' do
+ let!(:recent_credentials) do
+ folder = teams(:team1).folders.first
+ private_key = decrypt_private_key(bob)
+ team_password = folder.team.decrypt_team_password(bob, private_key)
+ Fabricate.times(
+ 6,
+ :credential,
+ folder: folder,
+ team_password: team_password
+ )
+ end
+
+ it 'returns most recent credentials' do
+ login_as(:alice)
+
+ recent_credentials.each do |credential|
+ log_read_access(alice.id, credential)
+ end
+
+ get :index, params: { recent: true }, xhr: true
+
+ expect(response.status).to be(200)
+ expect(data.size).to eq(5)
+ attributes = data.first['attributes']
+ expect(attributes['name']).to eq recent_credentials.last.name
+ expect(attributes['description']).to eq recent_credentials.last.description
+ end
+
+ it 'shows most recently used credential first in list' do
+ login_as(:alice)
+
+
+ recent_credentials.each do |credential|
+ log_read_access(alice.id, credential)
+ end
+ log_read_access(alice.id, credentials1)
+
+ get :index, params: { recent: true }, xhr: true
+
+ expect(response.status).to be(200)
+ expect(data.size).to eq(5)
+ attributes = data.first['attributes']
+ expect(attributes['name']).to eq credentials1.name
+ expect(attributes['description']).to eq credentials1.description
+
+ end
+ it 'does not show credentials with no access' do
+ login_as(:bob)
+
+ recent_credentials1 = recent_credentials.first
+ log_read_access(alice.id, recent_credentials1)
+
+ get :index, params: { recent: true }, xhr: true
+
+ expect(response.status).to be(200)
+ expect(data.size).to eq(0)
+ end
+
+ it 'does not show deleted credentials' do
+ login_as(:alice)
+
+ recent_credentials1 = recent_credentials.first
+ log_read_access(alice.id, recent_credentials1)
+
+ get :index, params: { recent: true }, xhr: true
+
+ expect(data.size).to eq(1)
+ attributes = data.first['attributes']
+ expect(attributes['name']).to eq recent_credentials1.name
+ expect(attributes['description']).to eq recent_credentials1.description
+
+ recent_credentials1.destroy!
+
+ get :index, params: { recent: true }, xhr: true
+
+ expect(response.status).to be(200)
+ expect(data.size).to eq(0)
+ end
+ end
end
context 'GET show' do
@@ -777,4 +859,12 @@ def set_auth_headers
request.headers['Authorization-User'] = bob.username
request.headers['Authorization-Password'] = Base64.encode64('password')
end
+
+ def log_read_access(user_id, credential)
+ v = credential.paper_trail.save_with_version
+ v.whodunnit = user_id
+ v.event = :viewed
+ v.created_at = DateTime.now
+ v.save!
+ end
end
diff --git a/spec/fabricators/encryptables/credentials_fabricator.rb b/spec/fabricators/encryptables/credentials_fabricator.rb
index dd4cd5d7a..8797270d9 100644
--- a/spec/fabricators/encryptables/credentials_fabricator.rb
+++ b/spec/fabricators/encryptables/credentials_fabricator.rb
@@ -7,7 +7,7 @@
Fabricator(:credential, from: 'Encryptable::Credentials') do
transient :team_password
- name { Faker::Team.creature }
+ name { sequence(:name) { |i| Faker::Team.creature + " #{i}" } }
cleartext_username { Faker::Internet.user_name }
cleartext_password { Faker::Internet.password }
before_save do |account, attrs|
diff --git a/spec/system/dashboard_system_spec.rb b/spec/system/dashboard_system_spec.rb
index 20d2e2182..44f9a987e 100644
--- a/spec/system/dashboard_system_spec.rb
+++ b/spec/system/dashboard_system_spec.rb
@@ -5,30 +5,55 @@
describe 'Dashboard', type: :system, js: true do
include SystemHelpers
+ let(:credentials) { encryptables(:credentials1) }
+ let(:team) { teams(:team1) }
+
+
it 'renders dashboard grid' do
login_as_user(:alice)
+ visit("/encryptables/#{credentials.id}")
+ visit('/dashboard')
expect(page.current_path).to eq('/dashboard')
-
expect(page).to have_selector('pzsh-hero', visible: true)
expect(page).to have_selector('pzsh-banner', visible: true)
+ expect(page).to have_text('Recent Credentials', count: 1)
expect(page).to have_text('Favourites', count: 1)
expect(page).to have_text('Teams', count: 1)
-
expect(page).not_to have_selector 'div.content'
-
- expect(page).to have_selector('div.dashboard-grid-card', count: 3)
+ expect(page).to have_selector('div.primary-content', count: 3)
end
it 'navigates to team on team card click' do
login_as_user(:alice)
+ visit("/encryptables/#{credentials.id}")
+ visit('/dashboard')
+
expect(page.current_path).to eq('/dashboard')
expect(page).to have_selector('pzsh-hero', visible: true)
- all('div.dashboard-grid-card').first.click
- expect(page.current_path).to eq('/teams/235930340')
+ first('div.dashboard-grid-card', text: team.name).click
+
+ expect(page.current_path).to eq("/teams/#{team.id}")
expect(page).to have_selector 'div.content'
end
+
+ it 'lists recently accessed encryptable' do
+ login_as_user(:alice)
+ visit("/encryptables/#{credentials.id}")
+ visit('/dashboard')
+
+
+ expect(page.current_path).to eq('/dashboard')
+
+ expect(page).to have_selector('pzsh-hero', visible: true)
+
+ first('div.dashboard-grid-card', text: credentials.name).click
+
+ expect(page.current_path).to eq("/encryptables/#{credentials.id}")
+ expect(page).to have_selector 'div.content'
+ end
+
end