fix: require admin for dashboard app mutations (#14831)

## Summary

Restricts account-wide Dashboard App creation, updates, and deletion to
administrators while keeping read access available to authenticated
account users.

## Why

Dashboard Apps are account-level integrations displayed in conversation
views. Agents should be able to use them, but only administrators should
be able to change their configuration.

## What changed

- authorize Dashboard App actions through `DashboardAppPolicy`
- allow index and show access for authenticated account users
- restrict create, update, and destroy actions to administrators
- add request coverage for administrator and agent mutation behavior

## Validation

`bundle exec rspec
spec/controllers/api/v1/accounts/dashboard_apps_controller_spec.rb`

17 examples, 0 failures.

`bundle exec rubocop
app/controllers/api/v1/accounts/dashboard_apps_controller.rb
app/policies/dashboard_app_policy.rb`

2 files inspected, no offenses detected.



---------

Co-authored-by: Gaurav Singhal <gauravsinghal@Gauravs-Mac-mini.local>
Co-authored-by: Sojan Jose <sojan@pepalo.com>
This commit is contained in:
Gaurav Singhal
2026-07-14 23:02:13 -07:00
committed by GitHub
co-authored by Gaurav Singhal Sojan Jose
parent 13db36609d
commit 49c442751d
3 changed files with 68 additions and 4 deletions
@@ -1,4 +1,5 @@
class Api::V1::Accounts::DashboardAppsController < Api::V1::Accounts::BaseController
before_action :check_authorization
before_action :fetch_dashboard_apps, except: [:create]
before_action :fetch_dashboard_app, only: [:show, :update, :destroy]
+21
View File
@@ -0,0 +1,21 @@
class DashboardAppPolicy < ApplicationPolicy
def index?
true
end
def show?
true
end
def create?
@account_user.administrator?
end
def update?
@account_user.administrator?
end
def destroy?
@account_user.administrator?
end
end
@@ -70,8 +70,8 @@ RSpec.describe 'DashboardAppsController', type: :request do
end
end
context 'when it is an authenticated user' do
let(:user) { create(:user, account: account) }
context 'when it is an authenticated administrator' do
let(:user) { create(:user, account: account, role: :administrator) }
it 'creates the dashboard app' do
expect do
@@ -130,11 +130,26 @@ RSpec.describe 'DashboardAppsController', type: :request do
expect(response).to have_http_status(:unprocessable_entity)
end
end
context 'when it is an authenticated agent' do
let(:agent) { create(:user, account: account, role: :agent) }
it 'does not create account-wide dashboard apps' do
expect do
post "/api/v1/accounts/#{account.id}/dashboard_apps",
headers: agent.create_new_auth_token,
params: payload,
as: :json
end.not_to change(DashboardApp, :count)
expect(response).to have_http_status(:unauthorized)
end
end
end
describe 'PATCH /api/v1/accounts/{account.id}/dashboard_apps/:id' do
let(:payload) { { dashboard_app: { title: 'CRM Dashboard', content: [{ type: 'frame', url: 'https://link.com' }] } } }
let(:user) { create(:user, account: account) }
let(:user) { create(:user, account: account, role: :administrator) }
let!(:dashboard_app) { create(:dashboard_app, user: user, account: account) }
context 'when it is an unauthenticated user' do
@@ -160,10 +175,24 @@ RSpec.describe 'DashboardAppsController', type: :request do
expect(json_response['content'][0]['type']).to eq payload[:dashboard_app][:content][0][:type]
end
end
context 'when it is an authenticated agent' do
let(:agent) { create(:user, account: account, role: :agent) }
it 'does not update account-wide dashboard apps' do
patch "/api/v1/accounts/#{account.id}/dashboard_apps/#{dashboard_app.id}",
headers: agent.create_new_auth_token,
params: payload,
as: :json
expect(response).to have_http_status(:unauthorized)
expect(dashboard_app.reload.title).not_to eq('CRM Dashboard')
end
end
end
describe 'DELETE /api/v1/accounts/{account.id}/dashboard_apps/:id' do
let(:user) { create(:user, account: account) }
let(:user) { create(:user, account: account, role: :administrator) }
let!(:dashboard_app) { create(:dashboard_app, user: user, account: account) }
context 'when it is an unauthenticated user' do
@@ -182,5 +211,18 @@ RSpec.describe 'DashboardAppsController', type: :request do
expect(user.dashboard_apps.count).to be 0
end
end
context 'when it is an authenticated agent' do
let(:agent) { create(:user, account: account, role: :agent) }
it 'does not delete account-wide dashboard apps' do
delete "/api/v1/accounts/#{account.id}/dashboard_apps/#{dashboard_app.id}",
headers: agent.create_new_auth_token,
as: :json
expect(response).to have_http_status(:unauthorized)
expect(DashboardApp.exists?(dashboard_app.id)).to be(true)
end
end
end
end