Skip to content

FEATURE: add option to hide IP addresses from moderators #33682

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 78 commits into from
Aug 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
56675de
add field to site_settings
Beznet Feb 19, 2025
0d5fd8d
update admin_user_serializer
Beznet Feb 20, 2025
17bfb05
refactor admin_user_serializer
Beznet Feb 20, 2025
ef590de
update user_controllers and history_serializer
Beznet Feb 20, 2025
1d64dd7
fix user_controller, conditionally render IP fields on Admin UI
Beznet Feb 20, 2025
d84988e
fix conditional on admin ui
Beznet Feb 21, 2025
81fda40
restrict screened-ip access
Beznet Feb 21, 2025
480b1c5
update rspec tests
Beznet Feb 21, 2025
08ddd81
append include_ on can_see_ip, add description to setting
Beznet Feb 27, 2025
8bf56de
removed hidden from moderators_view_ips
Beznet Mar 5, 2025
1867805
small tweaks
brrusselburg Jul 17, 2025
b6c4539
Removes .hbs files
brrusselburg Jul 17, 2025
c3dfef6
lint fix
brrusselburg Jul 18, 2025
871c01d
add field to site_settings
brrusselburg Jul 28, 2025
6ce75ca
update admin_user_serializer
Beznet Feb 20, 2025
5e724c7
refactor admin_user_serializer
Beznet Feb 20, 2025
c24b8a8
update user_controllers and history_serializer
brrusselburg Jul 28, 2025
04c17cf
fix user_controller, conditionally render IP fields on Admin UI
brrusselburg Jul 28, 2025
dc09be6
fix conditional on admin ui
brrusselburg Jul 28, 2025
9a57e50
restrict screened-ip access
Beznet Feb 21, 2025
a74a76f
update rspec tests
brrusselburg Jul 28, 2025
3599faf
append include_ on can_see_ip, add description to setting
Beznet Feb 27, 2025
91a4f0d
removed hidden from moderators_view_ips
Beznet Mar 5, 2025
3b06d48
small tweaks
brrusselburg Jul 28, 2025
593530d
Removes .hbs files
brrusselburg Jul 17, 2025
3673c3b
lint fix
brrusselburg Jul 18, 2025
0d15be0
add field to site_settings
Beznet Feb 19, 2025
bb588d2
update admin_user_serializer
Beznet Feb 20, 2025
6697907
refactor admin_user_serializer
Beznet Feb 20, 2025
406de3b
update user_controllers and history_serializer
Beznet Feb 20, 2025
5d1a512
fix user_controller, conditionally render IP fields on Admin UI
Beznet Feb 20, 2025
44bdaa3
fix conditional on admin ui
Beznet Feb 21, 2025
d3fe8e7
restrict screened-ip access
Beznet Feb 21, 2025
0794900
update rspec tests
Beznet Feb 21, 2025
75bf418
append include_ on can_see_ip, add description to setting
Beznet Feb 27, 2025
6db05ee
removed hidden from moderators_view_ips
Beznet Mar 5, 2025
1895166
small tweaks
brrusselburg Jul 17, 2025
1b220d6
Removes .hbs files
brrusselburg Jul 17, 2025
eadcabe
lint fix
brrusselburg Jul 18, 2025
edb005c
gets specs working, UI visible
brrusselburg Jul 31, 2025
30e55fb
update admin_user_serializer
Beznet Feb 20, 2025
9ce34aa
refactor admin_user_serializer
Beznet Feb 20, 2025
ed6cf68
fix user_controller, conditionally render IP fields on Admin UI
Beznet Feb 20, 2025
fd68213
fix conditional on admin ui
Beznet Feb 21, 2025
bafe0e3
restrict screened-ip access
Beznet Feb 21, 2025
3468a99
update rspec tests
Beznet Feb 21, 2025
4cc58b0
append include_ on can_see_ip, add description to setting
Beznet Feb 27, 2025
3ec72f2
small tweaks
brrusselburg Jul 17, 2025
a8d6d5f
Removes .hbs files
brrusselburg Jul 17, 2025
0ca6794
lint fix
brrusselburg Jul 18, 2025
20deff8
add field to site_settings
brrusselburg Jul 28, 2025
bc2277d
update admin_user_serializer
Beznet Feb 20, 2025
7da2209
refactor admin_user_serializer
Beznet Feb 20, 2025
35760ae
update user_controllers and history_serializer
brrusselburg Jul 28, 2025
0996496
fix user_controller, conditionally render IP fields on Admin UI
brrusselburg Jul 28, 2025
5616eea
fix conditional on admin ui
brrusselburg Jul 28, 2025
e8eef6e
restrict screened-ip access
Beznet Feb 21, 2025
f4a6422
update rspec tests
brrusselburg Jul 28, 2025
89e79f5
append include_ on can_see_ip, add description to setting
Beznet Feb 27, 2025
167c9c2
removed hidden from moderators_view_ips
Beznet Mar 5, 2025
4abbe50
small tweaks
brrusselburg Jul 28, 2025
92b5ee7
Removes .hbs files
brrusselburg Jul 17, 2025
5c8ba99
lint fix
brrusselburg Jul 18, 2025
aee5974
add field to site_settings
Beznet Feb 19, 2025
a9f2318
update admin_user_serializer
Beznet Feb 20, 2025
aef90f5
refactor admin_user_serializer
Beznet Feb 20, 2025
ae99757
update user_controllers and history_serializer
Beznet Feb 20, 2025
7ff21ad
fix user_controller, conditionally render IP fields on Admin UI
Beznet Feb 20, 2025
5202f65
fix conditional on admin ui
Beznet Feb 21, 2025
015c6c0
restrict screened-ip access
Beznet Feb 21, 2025
ba8cef6
update rspec tests
Beznet Feb 21, 2025
f614927
append include_ on can_see_ip, add description to setting
Beznet Feb 27, 2025
8cf65e4
removed hidden from moderators_view_ips
Beznet Mar 5, 2025
406aaa8
small tweaks
brrusselburg Jul 17, 2025
5ec3c51
Removes .hbs files
brrusselburg Jul 17, 2025
229c6b5
lint fix
brrusselburg Jul 18, 2025
22cb634
gets specs working, UI visible
brrusselburg Jul 31, 2025
8b7f8ac
remove claude file
brrusselburg Aug 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import { service } from "@ember/service";
import DiscourseRoute from "discourse/routes/discourse";

export default class AdminLogsScreenedIpAddressesRoute extends DiscourseRoute {
@service currentUser;

beforeModel() {
if (!this.currentUser.can_see_ip) {
this.transitionTo("adminLogs.staffActionLogs");
}
}

setupController() {
return this.controllerFor("adminLogsScreenedIpAddresses").show();
}
Expand Down
10 changes: 6 additions & 4 deletions app/assets/javascripts/admin/addon/templates/logs.gjs
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,12 @@ export default RouteTemplate(
@label="admin.config.staff_action_logs.sub_pages.screened_emails.title"
/>
{{/if}}
<NavItem
@route="adminLogs.screenedIpAddresses"
@label="admin.config.staff_action_logs.sub_pages.screened_ips.title"
/>
{{#if @controller.currentUser.can_see_ip}}
<NavItem
@route="adminLogs.screenedIpAddresses"
@label="admin.config.staff_action_logs.sub_pages.screened_ips.title"
/>
{{/if}}
<NavItem
@route="adminLogs.screenedUrls"
@label="admin.config.staff_action_logs.sub_pages.screened_urls.title"
Expand Down
52 changes: 28 additions & 24 deletions app/assets/javascripts/admin/addon/templates/user-index.gjs
Original file line number Diff line number Diff line change
Expand Up @@ -228,35 +228,39 @@ export default RouteTemplate(
/>
</div>

<div class="display-row last-ip">
<div class="field">{{i18n "user.ip_address.title"}}</div>
<div class="value">{{@controller.model.ip_address}}</div>
<div class="controls">
{{#if @controller.currentUser.staff}}
{{#if @controller.model.ip_address}}
<IpLookup
@ip={{@controller.model.ip_address}}
@userId={{@controller.model.id}}
/>
{{#if @controller.model.include_ip}}
<div class="display-row last-ip">
<div class="field">{{i18n "user.ip_address.title"}}</div>
<div class="value">{{@controller.model.ip_address}}</div>
<div class="controls">
{{#if @controller.currentUser.can_see_ip}}
{{#if @controller.model.ip_address}}
<IpLookup
@ip={{@controller.model.ip_address}}
@userId={{@controller.model.id}}
/>
{{/if}}
{{/if}}
{{/if}}
</div>
</div>
</div>
{{/if}}

<div class="display-row registration-ip">
<div class="field">{{i18n "user.registration_ip_address.title"}}</div>
<div class="value">{{@controller.model.registration_ip_address}}</div>
<div class="controls">
{{#if @controller.currentUser.staff}}
{{#if @controller.model.registration_ip_address}}
<IpLookup
@ip={{@controller.model.registration_ip_address}}
@userId={{@controller.model.id}}
/>
{{#if @controller.model.include_ip}}
<div class="display-row registration-ip">
<div class="field">{{i18n "user.registration_ip_address.title"}}</div>
<div class="value">{{@controller.model.registration_ip_address}}</div>
<div class="controls">
{{#if @controller.currentUser.can_see_ip}}
{{#if @controller.model.registration_ip_address}}
<IpLookup
@ip={{@controller.model.registration_ip_address}}
@userId={{@controller.model.id}}
/>
{{/if}}
{{/if}}
{{/if}}
</div>
</div>
</div>
{{/if}}

{{#if @controller.showBadges}}
<div class="display-row">
Expand Down
5 changes: 5 additions & 0 deletions app/controllers/admin/screened_ip_addresses_controller.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# frozen_string_literal: true

class Admin::ScreenedIpAddressesController < Admin::StaffController
before_action :can_see_ip
before_action :fetch_screened_ip_address, only: %i[update destroy]

def index
Expand Down Expand Up @@ -49,6 +50,10 @@ def destroy

private

def can_see_ip
raise Discourse::InvalidAccess.new if !guardian.can_see_ip?
end

def allowed_params
params.require(:ip_address)
params.permit(:ip_address, :action_name)
Expand Down
2 changes: 2 additions & 0 deletions app/controllers/admin/users_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ def show
root: false,
similar_users_count: @user.similar_users.count,
include_silence_reason: true,
include_ip: guardian.can_see_ip?,
)
end

Expand Down Expand Up @@ -438,6 +439,7 @@ def tl3_requirements

def ip_info
params.require(:ip)
raise Discourse::InvalidAccess.new unless guardian.can_see_ip?

render json: DiscourseIpInfo.get(params[:ip], resolve_hostname: true)
end
Expand Down
15 changes: 14 additions & 1 deletion app/serializers/admin_user_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ class AdminUserSerializer < AdminUserListSerializer
:can_deactivate,
:can_approve,
:ip_address,
:registration_ip_address
:registration_ip_address,
:include_ip

has_one :single_sign_on_record, serializer: SingleSignOnRecordSerializer, embed: :objects

Expand Down Expand Up @@ -40,7 +41,19 @@ def registration_ip_address
object.registration_ip_address.try(:to_s)
end

def include_ip_address?
scope.can_see_ip?
end

def include_registration_ip_address?
scope.can_see_ip?
end

def include_can_be_deleted?
true
end

def include_ip
@options[:include_ip]
end
end
11 changes: 10 additions & 1 deletion app/serializers/current_user_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ class CurrentUserSerializer < BasicUserSerializer
:can_localize_content?,
:effective_locale,
:use_reviewable_ui_refresh,
:use_experimental_sidebar_messages_count
:use_experimental_sidebar_messages_count,
:can_see_ip

delegate :user_stat, to: :object, private: true
delegate :any_posts, :draft_count, :pending_posts_count, :read_faq?, to: :user_stat
Expand Down Expand Up @@ -361,4 +362,12 @@ def use_experimental_sidebar_messages_count
def include_use_reviewable_ui_refresh?
scope.can_see_review_queue?
end

def can_see_ip
scope.can_see_ip?
end

def include_can_see_ip?
object.admin? || (object.moderator? && SiteSetting.moderators_view_ips)
end
end
5 changes: 5 additions & 0 deletions app/serializers/user_history_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,9 @@ def previous_value
nil
end
end

def ip_address
return nil unless scope.can_see_ip?
object.ip_address.try(:to_s)
end
end
1 change: 1 addition & 0 deletions config/locales/server.en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1921,6 +1921,7 @@ en:
redirect_users_to_top_page: "Automatically redirect new and long absent users to the top page. Only applies when 'top' is present in the 'top menu' site setting."
top_page_default_timeframe: "Default top page time period for anonymous users (automatically adjusts for logged in users based on their last visit)."
moderators_view_emails: "Allow moderators to view user email addresses."
moderators_view_ips: "Allow moderators to view user ip addresses."
prioritize_username_in_ux: "Show username first on user page, user card and posts (when disabled name is shown first)"
enable_rich_text_paste: "Enable automatic HTML to Markdown conversion when pasting text into the composer."
send_old_credential_reminder_days: "Remind about old credentials after days"
Expand Down
3 changes: 3 additions & 0 deletions config/site_settings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2581,6 +2581,9 @@ security:
moderators_view_emails:
client: true
default: false
moderators_view_ips:
default: true
client: true
non_crawler_user_agents:
hidden: true
default: "trident|webkit|gecko|chrome|safari|msie|opera|goanna|discourse"
Expand Down
7 changes: 7 additions & 0 deletions lib/guardian.rb
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,13 @@ def can_see_emails?
SiteSetting.moderators_view_emails && is_moderator?
end

def can_see_ip?
return true if is_admin?
return true if is_moderator? && SiteSetting.moderators_view_ips

false
end

def can_mute_user?(target_user)
can_mute_users? && @user.id != target_user.id && !target_user.staff?
end
Expand Down
24 changes: 24 additions & 0 deletions spec/lib/guardian_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3286,6 +3286,30 @@
end
end

describe "#can_see_ip?" do
let(:guardian_admin) { Guardian.new(admin) }
let(:guardian_moderator) { Guardian.new(moderator) }

context "when user is an admin" do
it "returns true" do
expect(guardian_admin.can_see_ip?).to eq(true)
end
end

context "when user is a moderator" do
before { SiteSetting.moderators_view_ips = true }

it "returns true if moderators_view_ips is enabled" do
expect(guardian_moderator.can_see_ip?).to eq(true)
end

it "returns false if moderators_view_ips is disabled" do
SiteSetting.moderators_view_ips = false
expect(guardian_moderator.can_see_ip?).to eq(false)
end
end
end

describe "#is_developer?" do
after { Developer.rebuild_cache }

Expand Down
6 changes: 5 additions & 1 deletion spec/requests/api/schemas/json/admin_user_response.json
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,9 @@
},
"external_ids": {
"type": "object"
},
"include_ip": {
"type": "boolean"
}
},
"required": [
Expand Down Expand Up @@ -553,6 +556,7 @@
"suspended_by",
"silenced_by",
"groups",
"external_ids"
"external_ids",
"include_ip"
]
}
32 changes: 32 additions & 0 deletions spec/serializers/current_user_serializer_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,38 @@
end
end

describe "#can_see_ip" do
let(:payload) { serializer.as_json }

context "when user is an admin" do
let(:user) { Fabricate(:admin) }

it "includes can_see_ip as true" do
expect(payload[:can_see_ip]).to eq(true)
end
end

context "when user is a moderator and moderators_view_ips is enabled" do
let(:user) { Fabricate(:moderator) }

before { SiteSetting.moderators_view_ips = true }

it "includes can_see_ip as true" do
expect(payload[:can_see_ip]).to eq(true)
end
end

context "when user is a moderator and moderators_view_ips is disabled" do
let(:user) { Fabricate(:moderator) }

before { SiteSetting.moderators_view_ips = false }

it "does not include can_see_ip" do
expect(payload).not_to have_key(:can_see_ip)
end
end
end

describe "#featured_topic" do
fab!(:featured_topic, :topic)

Expand Down