We currently have `user_color_schemes` serialized on the preloaded
`Site` object, but it's very lightweight, only having an ID and name.
It would be useful for some themes and also the user Interface
preferences tab to be able to access the colors for the color scheme,
as well as the theme it belongs to.
This commit expands the serializer to include these extra attributes.
This `user_color_schemes` key is already cached as a fragment, so this
shouldn't be too much extra burden to send to the client.
The URLs returned by DiscourseConnect for the user's avatar, profile and
card backgrounds were not always correctly handling CDN.
This make use of the `GlobalPath.full_cdn_url` helper method which has
been battle-tested.
Ref - https://meta.discourse.org/t/-/356599
When multiple admins are working in the review queue, it's quite easy for two people to try and handle the same reviewable at the same time. This change addresses the two major situations where this can occur.
The `ReviewableClaimedTopic` model has been extended to allow the system to mark a reviewable as claimed as soon as the first moderator starts handling the reviewable, even when the `reviewable_claiming` setting is disabled. This ensures that reviewable actions with client-site activity (for example, `agree_and_suspend`) will lock the reviewable before another moderator starts working on it.
When someone handles handles a reviewable, we now use `MessageBus` to inform other moderators that it's changed. If any of the other moderator have that reviewable open (either individually, or on the list screen), it will automatically refresh that data.
This PR renames a couple of settings related to anonymous mode:
1. `allow_anonymous_posting` → `allow_anonymous_mode`. This setting is
used as a switch for the entire anonymous mode feature, so it makes
sense to give it a generic name that better reflects what the setting
does.
2. `allow_anonymous_likes` → `allow_likes_in_anonymous_mode`. The new
name is clearer and will match a new setting that we'll add to allow
anonymous users to post in chat.
Internal topic: t/148088.
Setting Rails.logger after the application has been initialized does not
seem to be safe anymore and can lead to flaky tests. This commit
disallows the reassignment of `Rails.logger` going forward and updates
the affected test.
### Reviewer notes
Reassigning `Rails.logger` from within RSpec tests is causing tests
which uses `Rails.logger.broadcast_to(FakeLogger.new)` to flake.
Example:
https://github.com/discourse/discourse/actions/runs/13951116847/job/39050616967
```
1) invalid requests handles NotFound with invalid json body
Failure/Error: expect(fake_logger.errors).to have_attributes(size: 1)
expected [] to have attributes {:size => 1} but had attributes {:size => 0}
Diff:
@@ -1 +1 @@
-:size => 1,
+:size => 0,
# ./spec/integration/invalid_request_spec.rb:18:in `block (2 levels) in <main>'
# ./spec/rails_helper.rb:619:in `block (3 levels) in <top (required)>'
# /var/www/discourse/vendor/bundle/ruby/3.3.0/gems/benchmark-0.4.0/lib/benchmark.rb:304:in `measure'
# ./spec/rails_helper.rb:619:in `block (2 levels) in <top (required)>'
# ./spec/rails_helper.rb:580:in `block (3 levels) in <top (required)>'
# /var/www/discourse/vendor/bundle/ruby/3.3.0/gems/timeout-0.4.3/lib/timeout.rb:185:in `block in timeout'
# /var/www/discourse/vendor/bundle/ruby/3.3.0/gems/timeout-0.4.3/lib/timeout.rb:192:in `timeout'
# ./spec/rails_helper.rb:570:in `block (2 levels) in <top (required)>'
# ./spec/rails_helper.rb:527:in `block (2 levels) in <top (required)>'
# /var/www/discourse/vendor/bundle/ruby/3.3.0/gems/webmock-3.25.1/lib/webmock/rspec.rb:39:in `block (2 levels) in <top (required)>'
```
Fixes the custom homepage crawler output to include links to the site's
top menu.

This also provides a way to override that content via a plugin. Example
usage in a `plugin.rb` file:
```
register_html_builder("server:custom-homepage-crawler-view") do |c|
"<div>override</div>"
end
```
We already support `/apple-app-site-association` at the root. Apple also
accepts `.well-known/apple-app-site-association` as a valid path so this
adds that as well, just in case.
The service worker isn't served via normal asset paths or the CDN.
Instead, the ERB was being compiled by sprockets, fished out of the
`public/` directory by the static_controller, and then the
sprockets-specific stuff like `sourceMappingUrl` was being removed.
Instead, we can put the ERB under `views/static/`, and have it evaluate
at runtime. There are only a couple of super-cheap interpolations, plus
the route is cached in nginx, so there is no performance concern.
This takes us one step closer to removing sprockets.
This list of all reports is needed in the admin search
controller as well, so this commit refactors it into
a service, adds specs, and also updates the admin
search code to use this new service & avoid a second
AJAX call to the server.
When a tag has a description defined, we should use that in the `<meta
name="description"...` tag on the tagged topic list page. This matches
the behaviour on the equivalent category pages.
Follow up from https://github.com/discourse/discourse/pull/31559.
We expect some standard headers to be added from
`Rails.application.config.action_dispatch.default_headers` for
responses, however these were found to be removed in some error paths.
For more detail on this behaviour, refer to https://github.com/discourse/discourse/pull/31619#issuecomment-2699644232.
This PR adds those headers back if they aren't there, with the caveats
that we don't add headers that are irrelevant for non-HTML responses,
and neither do we add X-Frame-Options which is intentionally removed for
embeddables.
Adds `.json` a suffix everywhere and makes it clear that's it's a json
route.
Also adds a missing spec for this endpoint and updates the underlying
discourse-emojis gem for better symlinking
This commit moves most of emoji logic into the discourse-emojis gem:
https://github.com/discourse/discourse-emojis/
Most notably:
- images are now symlinked from the gem
- the gem provides path to the json files
Search aliases have also been made asynchronous and memoized. When you
will search for an emoji we will now load the aliases and store the list
for future use.
---------
Co-authored-by: David Taylor <david@taylorhq.com>
In some error paths, headers that were set earlier can get overwritten
(e.g. `Cross-Origin-Opener-Policy`) by middleware such as
ActionDispatch::ShowExceptions.
This PR sets the `Cross-Origin-Opener-Policy` header to the value of the
SiteSetting `cross_origin_opener_policy_header` if it's missing and if
the response is for HTML.
In future, this DefaultHeaders middleware can be used to set other
default headers that relate to security or other purposes.
### Testing
<img width="631" alt="test"
src="https://github.com/user-attachments/assets/05106a40-2bc7-435d-91a2-4dd2a098f349"
/>
The API docs is incorrect as the `active` param is only permitted when
an admin API key
is used. This has always been the case since
429f27ec96c090d9054c498263f0cb635b665d99
This change standardises the `User-Agent` header that Discourse will send when talking to other sites.
`Discourse.user_agent` is now the authority on what the user agent value should be. For Onebox requests, this changes the user agent from their existing value to match the new value (unless overridden).
For all other requests, `Net::HTTPHeader` is monkey-patched to add a default `User-Agent` header when one hasn't been provided.
This reverts commit 38de3d7bd1f503743c5d0237bc8a8d9d89effb8e. This
changed seemed to be blocking our own AI helper as well if it has the
“Search” tool.
This change detects if a crawler is trying to load a search results page, and returns a simple response that should indicate to them that there's no content of interest available there.
When an email is sent by sendgrid to an email address with an invalid
host, the webhook payload does not contain the "status" field:
```
[
{
"bounce_classification": "Unclassified",
"email": "noemail@this.does.not.exist.tld",
"event": "bounce",
"reason": "unable to get mx info: failed to get IPs from PTR record: lookup <nil>: unrecognized address",
"sg_event_id": "Ym91bmNlLTQtNTA0ODUxOTUtZXVvMmlLeGRTYXlQRjRZRTQtLUk3QS0w",
"sg_message_id": "euo2iKxdSayPF4YE4--I7A.recvd-5f54b5d587-pczjm-1-67BADEEA-6.0",
"smtp-id": "<870b3a2a-160c-4fc8-bc9a-bd0d5b943b81@forum.umbraco.com>",
"timestamp": 1740300320,
"tls": 0,
"type": "blocked"
}
]
```
When the `status` field is missing, it results in a `NoMethodError
(undefined method `[]' for nil:NilClass)`
error in the controller method. In this commit, we will specifically
handle the webhook event from sendgrid when the email address's domain
is invalid.
Co-Authored-By: @nul800sebastiaan
Previously the rendered locale was based on the current session's
locale. Now that we're routing requests via the CDN, we can't rely on
the user's session, and should instead include the locale name in the
URL. Also adds a `.js` suffix for parity with our other JS assets.
CDNs are often configured to strip query params, which means that the
`?v=` parameter wasn't reaching the Rails app, and therefore the
cache-control header was not being set correctly. Having a 40 character
sha1 digest in the **path** is the approach we take for other similar
assets like stylesheets and theme-javascripts.
Also adds a spec for the fix in 573fbeef64f052decc47e740cbe01a3c298c20b5
Previously, the SiteSetting::Update service allowed to update of a
single site setting. In the About controller, we were using the loop
through all settings -
https://github.com/discourse/discourse/blob/main/app/controllers/admin/config/about_controller.rb#L39
It is suboptimal because if the 3 first settings are saved and the
fourth is invalid, we will end with partially updated data.
Changing SiteSetting::Update to accept hash means that we will check
upfront if none of the settings are hidden or invalid and update all or
none.
Custom policies are used to report which settings are failing.
Code/translations for the admin panel and wizard are not considered
sensitive, so there's no need for access control checks here. Once
they're removed, we can cache in NGINX and the CDN, and thereby improve
server and client-load performance.
This totally separate SCSS and i18n compilation pipelines only existed
so that we could run `ember exam` in CI without starting Rails.
Now that our CI has such heavy caching of Ruby dependencies and database
migrations, the speed benefit of this is not worth the cost of
maintaining these separate pipelines.
Therefore, this commit removes that system, and updates CI to use
`bin/rake qunit:test`. That will start up a Rails server and proxy
stylesheet/locale requests to it. This strategy was already used for our
theme and plugin qunit test runs.
...loading an invite link that points to a topic they already have
access to.
This "feature" was removed in 07ef1a80a1461123d602c57e366974aed265a91e
as part of the security fix.
Internal ref - t/145628
In a few places throughout the app, when we render the `<GroupChooser
/>` component, we fetch the full groups list of the site from the
`/groups/search` endpoint. This is wasteful because the full groups list
is already included in the preloaded data that's sent to the client app
on the initial page load, so we can just use this preloaded list for
`<GroupChooser />` and we can avoid making an HTTP request.
Internal topic: t/147297.
By default, when multiple login providers are enabled, Discourse
requires user interaction before triggering an external auth flow. This
is defense-in-depth against "Login CSRF" attacks.
This commit introduces a setting to control this behavior, so that it
can be disabled when admins fully trust the downstream systems, and need
an interaction-free login flow on a site with multiple login providers.
Default behavior remains unchanged.
6fd577d97d3923cec3d2458f45ebd2704703fd22 widened the scope of
`use_email_for_username_and_name_suggestions` (default false) to include
invites, which means that it fell back to a generic username like
`user1`.
This commit makes it bail out earlier in this situation, so that no
suggestion is attempted.
This commit makes the
[color-scheme-toggle](https://github.com/discourse/discourse-color-scheme-toggle)
theme component a core feature with improvements and bug fixes. The
theme component will be updated to become a no-op if the core feature is
enabled.
Noteworthy changes:
* the color mode selector has a new "Auto" option that makes the site
render in the same color mode as the user's system preference
* the splash screen respects the color mode selected by the user
* dark/light variants of category logos and background images are now
picked correctly based on the selected color mode
* a new `interface_color_selector` site setting to disable the selector
or choose its location between the sidebar footer or header
Internal topic: t/139465.
---------
Co-authored-by: Ella <ella.estigoy@gmail.com>
Plugins like for example AI or Akismet create reviewable items. When the
plugin is disabled, then we cannot properly handle those items.
In that situation, we should display warnings about unhandled types.
Instruct admin to reenable plugins. In addition, we should allow the
admin to delete all pending reviews from disabled plugins.
This commit drops the `before_action :preload_json` callback in `ApplicationController` as it adds unnecessary complexity to `ApplicationController` as well as other controllers which has to skip this callback. The source of the complexity comes mainly from the following two conditionals in the `preload_json` method:
```
# We don't preload JSON on xhr or JSON request
return if request.xhr? || request.format.json?
# if we are posting in makes no sense to preload
return if request.method != "GET"
```
Basically, the conditionals solely exists for optimization purposes to ensure that we don't run the preloading code when the request is not a GET request and the response is not expected to be HTML. The key problem here is that the conditionals are trying to expect what the content type of the response will be and this has proven to be hard to get right. Instead, we can simplify this problem by running the preloading code in a more deterministic way which is to preload only when the `application` layout is being rendered and this is main change that this commit introduces.
This PR raises an error on any deprecated icon names being converted by
svg_sprite.rb, which will result in any deprecated icons being processed
by the ruby lib to fail tests.
Previously, for a search query with `page=11` or higher, we were quietly
returning the page 10 results. The frontend app isn't affected because
it sets its own limit to 10 pages, but still, this response from the
search endpoint does not make sense.
This change switches to returning a 400 error when the `page` parameter
is above the allowed limit (a max of 10).
It seems from the original commit notes that this was only included as a query
optimisation, but doing so leads to confusion: https://meta.discourse.org/t/348688
Searching for outbound mail to an address should find that address regardless
of whether or not the mail type to search for is explicitly `group_smtp`.
Rename `min_first_post_typing_time` to `fast_typing_threshold` and
provide admin 4 options:
- disabled
- low - 1 second
- standard - 3 seconds
- high - 5 seconds
Related PRs:
- https://github.com/discourse/discourse-zoom/pull/112