mirror of
https://github.com/we-promise/sure.git
synced 2026-07-27 12:12:13 +00:00
The "Advanced" settings section is gated to admins in the navigation, but the controllers exposing family-wide data performed no server-side authorization — any authenticated user could reach them by navigating directly to the URL. Add `before_action :require_admin!` to the controllers that expose family-wide data: - Settings::AiPromptsController (family AI assistant config) - Settings::LlmUsagesController (family LLM usage/billing) This mirrors the existing guards on Settings::ProvidersController and Settings::HostingsController. Non-admins are redirected with the standard "Only admins can perform this action" message (403 for turbo_stream/json). API key and MCP token management are intentionally left open: both are user-scoped self-management. Their actions are already scoped to Current.user, and any signed-in user (not just admins) can create an API key or authorize an MCP client, so gating them would leave members unable to view or revoke their own credentials. Adds controller tests covering admin-only access for the family-wide pages (member and guest rejected) and member self-service access for the API key and MCP pages.
127 lines
3.4 KiB
Ruby
127 lines
3.4 KiB
Ruby
require "test_helper"
|
|
|
|
class Settings::McpControllerTest < ActionDispatch::IntegrationTest
|
|
setup do
|
|
@user = users(:family_admin)
|
|
sign_in @user
|
|
end
|
|
|
|
test "shows MCP settings page" do
|
|
get settings_mcp_path
|
|
assert_response :success
|
|
end
|
|
|
|
test "shows connected tokens" do
|
|
app = Doorkeeper::Application.create!(
|
|
name: "Claude",
|
|
redirect_uri: "https://claude.ai/callback",
|
|
confidential: false
|
|
)
|
|
Doorkeeper::AccessToken.create!(
|
|
application: app,
|
|
resource_owner_id: @user.id,
|
|
scopes: "read",
|
|
expires_in: 1.year
|
|
)
|
|
|
|
get settings_mcp_path
|
|
assert_response :success
|
|
assert_select "li", text: /Claude/
|
|
end
|
|
|
|
test "revokes a token" do
|
|
app = Doorkeeper::Application.create!(
|
|
name: "Claude",
|
|
redirect_uri: "https://claude.ai/callback",
|
|
confidential: false
|
|
)
|
|
token = Doorkeeper::AccessToken.create!( # pipelock:ignore
|
|
application: app,
|
|
resource_owner_id: @user.id,
|
|
scopes: "read",
|
|
expires_in: 1.year
|
|
)
|
|
|
|
delete revoke_token_settings_mcp_path(token_id: token.id)
|
|
|
|
assert_redirected_to settings_mcp_path
|
|
assert token.reload.revoked_at.present?
|
|
end
|
|
|
|
test "does not show mobile device tokens" do
|
|
mobile_device = MobileDevice.create!(
|
|
user: @user,
|
|
device_id: "test-device-#{SecureRandom.hex(4)}",
|
|
device_name: "Test Phone",
|
|
device_type: "ios"
|
|
)
|
|
app = Doorkeeper::Application.create!(
|
|
name: "Sure Mobile",
|
|
redirect_uri: "sureapp://oauth/callback",
|
|
confidential: false
|
|
)
|
|
Doorkeeper::AccessToken.create!(
|
|
application: app,
|
|
resource_owner_id: @user.id,
|
|
mobile_device_id: mobile_device.id,
|
|
scopes: "read",
|
|
expires_in: 1.year
|
|
)
|
|
|
|
get settings_mcp_path
|
|
assert_response :success
|
|
assert_select "li", text: /Sure Mobile/, count: 0
|
|
end
|
|
|
|
test "cannot revoke another user's token" do
|
|
other_user = users(:family_member)
|
|
app = Doorkeeper::Application.create!(
|
|
name: "Claude",
|
|
redirect_uri: "https://claude.ai/callback",
|
|
confidential: false
|
|
)
|
|
token = Doorkeeper::AccessToken.create!( # pipelock:ignore
|
|
application: app,
|
|
resource_owner_id: other_user.id,
|
|
scopes: "read",
|
|
expires_in: 1.year
|
|
)
|
|
|
|
delete revoke_token_settings_mcp_path(token_id: token.id)
|
|
|
|
assert_redirected_to settings_mcp_path
|
|
assert_nil token.reload.revoked_at
|
|
end
|
|
|
|
# MCP token management is user-scoped self-management: both actions are already
|
|
# scoped to Current.user, and any signed-in user (not just admins) can authorize
|
|
# an MCP client and own a live token. Gating this controller would leave members
|
|
# unable to disconnect their own clients. Do NOT add an admin gate here.
|
|
test "non-admin member can view MCP settings" do
|
|
sign_in users(:family_member)
|
|
get settings_mcp_path
|
|
assert_response :success
|
|
end
|
|
|
|
test "non-admin member can revoke their own MCP token" do
|
|
member = users(:family_member)
|
|
app = Doorkeeper::Application.create!(
|
|
name: "Claude",
|
|
redirect_uri: "https://claude.ai/callback",
|
|
confidential: false
|
|
)
|
|
token = Doorkeeper::AccessToken.create!( # pipelock:ignore
|
|
application: app,
|
|
resource_owner_id: member.id,
|
|
scopes: "read",
|
|
expires_in: 1.year
|
|
)
|
|
|
|
sign_in member
|
|
delete revoke_token_settings_mcp_path(token_id: token.id)
|
|
|
|
assert_redirected_to settings_mcp_path
|
|
assert token.reload.revoked_at.present?
|
|
end
|
|
end
|