* New hook: `Minz_HookType::AutofillField` for auth views
Currently to be used in the Demo extension, because it overrides both the formLogin and reauth views (for autofilling username/password), it can be broken every time there is a slight change in core.
For example, after https://github.com/FreshRSS/FreshRSS/pull/9358.
In the future the hook can be extended to support more fields.
Demo extension PR: https://github.com/FreshRSS/xExtension-Demo/pull/6
* Suggested alternative without returning HTML code
Closes#4616
The _Force HTTPS_ mechanism (`./force-https.default.txt` and `./data/force-https.txt`) was not documented. This adds a short section to the admin page about configuration without a user interface.
* Update registerHook docs with newer PHP 8.1+ syntax for callables
* Use the correct function name for the second hook
* Use correct return type for the second hook function
Closes#9394
## Summary
The _User CSS_ and _User JS_ sections already existed in the user documentation, but they did not say where to find the settings. This explains that each extension has a gear icon (_Manage_) next to its switch, and gives the steps to use both extensions.
## Changes
- `docs/en/users/05_Configuration.md`: describe the extension list (switch, gear icon, name), add the steps for User CSS, and the same for User JS with a note that it might ask to sign in again.
- `docs/en/users/05_Configuration.md`, `docs/en/admins/11_Themes.md`, `docs/en/contributing.md`: the "CustomCSS extension" links pointed to FreshRSS/Extensions, where it is no longer available. They now point to the built-in User CSS section.
- `docs/en/admins/15_extensions.md`, `lib/core-extensions/README.md`: list the two core extensions with links to their documentation. Also fix the `.lib/core-extensions` path.
- `CREDITS.md`: add my name.
Only the English documentation is changed.
* Check SVG optimization in the tests CI workflow with SVGO
Previously it was only recommended by the documentation that SVG images should be optimized using [SVGOMG](https://github.com/jakearchibald/svgomg), the web GUI version of https://github.com/svg/svgo (CLI)
Now all SVG images are optimized, and optimization is mandatory enforced by the tests CI workflow.
\+ SVGs are sanitized of potentially malicious JS code using the https://svgo.dev/docs/plugins/removeScripts/ plugin
* Output prettified SVGs instead and remove unchanged files from stdout
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
* Use `removeScripts` SVGO plugin to reduce risk of malicious SVGs
* Move the CLI options to the JS config instead
* Disable `removeComments` plugin included in the default preset
* editorconfig, and easier config
---------
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
* Update documentation of TRUSTED_PROXY syntax and document limitations.
Single IPs are now supported as of #9301.
Document the existing limitation of domain names, see #9210.
For IPs without `/`, I manually tested the behavior and found requests got rejected on my server before #9301. After #9301 is merged, I've tested again and found that requests are now authorized as expected. So I've updated the corresponding documentation to say "_IPs or_ ranges" where applicable.
Domain names are still silently ignored as of writing. I looked at the code, as described in #9210 investigation steps, and concluded that they are WAI (working as _implemented_ , not sure if _intended_ ). I don't particularly like the existing behavior (failures with no debuggable warnings or logs) but I try to document it as-is without judgement.
* Update TRUSTED_PROXY comments accordingly for Docker usage.
Single IPs are now supported as of #9301.
Document the existing limitation of domain names, see #9210.
Changes proposed in this pull request:
- `docs/en/users/02_First_steps.md` links to four section anchors that don't exist on the English pages. The ids look like literal translations of the French headings, but the English headings are worded differently:
- `04_Subscriptions.md#import-and-export` -> `#import-export` (`## Import / export`)
- `04_Subscriptions.md#feed-management` -> `#subscription-management` (`## Subscription management`)
- `05_Configuration.md#changing-the-view` -> `#display` (`## Display`, the section the French page links as "Personnaliser la vue")
- `05_Configuration.md#reading-options` -> `#reading` (`## Reading`)
- The French page already resolves correctly, so this only touches the English source.
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Reword explanations for edge/latest channels
Highlight the fact that we do not backport bug fixes or security fixes to previous releases.
Simplify also a slightly obsolte MySQL doc to favour SQLite
* Merge i18n: az
* Update Polish translations to specify rolling/versioned releases
Co-authored-by: Inverle <inverle@proton.me>
* Update 05_Release_new_version.md
---------
Co-authored-by: Inverle <inverle@proton.me>
* Add configurable log_level system setting
Minz_Log currently only distinguishes 'production' (errors and
warnings only) from any other environment (everything, including
debug messages), with no granularity in between.
Introduce an optional 'log_level' system setting (error, warning,
notice, info, or debug) that overrides the verbosity implied by
'environment'. It defaults to an empty string, which preserves the
exact current behaviour.
Fixes#7594
* Slight rewrite
---------
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
## Problem
With OIDC on the official Docker image, interactive SSO logins intermittently return `HTTP 400` at the `/i/oidc/` callback. The failure tracks login duration. `mod_auth_openidc` sets an anti-CSRF state cookie bounded by `OIDCStateTimeout` (default 300 s). If a login through an external IdP takes longer than that (MFA, a slow provider screen, the user stepping away), the cookie is already gone when the callback returns. The image does not set `OIDCDefaultURL`, so the module has no fallback and returns an error instead of restarting the login.
Apache log:
```
oidc_response_proto_state_restore: state has expired
oidc_response_process: invalid authorization response state and
no default SSO URL is set, sending an error...
→ GET /i/oidc/?code=…&state=… 400
```
This is [documented mod_auth_openidc behaviour](https://github.com/OpenIDC/mod_auth_openidc/blob/72c9f479c2d228477ff0a9518964f61879c83fb6/auth_openidc.conf#L723-L733) when `OIDCDefaultURL` is unset. Both `OIDCDefaultURL` and `OIDCStateTimeout` are `RSRC_CONF` (server config only), so the `./FreshRSS/p/i/.htaccess` escape hatch cannot set them and the fix has to live in the image.
Reproduce: log out, start SSO, wait more than 5 minutes on the IdP screen, complete.
## Fix
Add `OIDC_DEFAULT_URL`, wired through the existing `${OIDC_*}` env pattern, defaulting to `/i/` (the index, consistent with `OIDCRedirectURI /i/oidc/`). On an expired or unmatchable state the module now redirects to `/i/`, which is protected and starts a fresh login against the existing IdP session. `OIDCStateTimeout` stays at 300 s; the window is fine, the missing fallback was the bug.
`OIDC_STATE_TIMEOUT` is intentionally not exposed. It can be added the same way if there is a reason to tune the window.
* Docker/OIDC: redirect to a default URL on expired auth state instead of 400-ing
Adds OIDC_DEFAULT_URL (default /i/), wired through the existing ${OIDC_*} env
pattern, so an expired or unmatchable mod_auth_openidc state redirects to the
FreshRSS index and restarts the login instead of returning HTTP 400.
* git commit update
---------
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
* Clarify archiving/purge documentation
Explain purge policy priority (feed > category > global), the discovery-date based purge logic, and that purging is only triggered on a random subset of feed refreshes (or via Purge now / cli/purge.php).
Fixes#6863.
Co-Authored-By: Claude <noreply@anthropic.com>
* Typographic quotes
* No changelog for (minor) documentation
---------
Co-authored-by: Gerard Alvear <gerard.alvear@logiqd.me>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
* AI skills: suggestion to limit the number of PRs
* review comments
* tweak phrasing
* Bump to 10
---------
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
* Document OpenBSD httpd API configuration
Closes#7493. Adds an OpenBSD httpd example for FreshRSS with PATH_INFO preserved for the Google Reader API.
* Document caching considerations for OpenBSD httpd
* A few edits
---------
Co-authored-by: Gerard Alvear <gerard.alvear@logiqd.me>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
Closes#7209. Documents CURLOPT_HTTPPROXYTUNNEL with a data/config.php example for HTTPS feeds through HTTP CONNECT proxies.
Co-authored-by: Gerard Alvear <gerard.alvear@logiqd.me>
* Add Nextcloud Bookmarks sharing service
Adds a new self-hosted sharing option that posts articles to a
Nextcloud instance's Bookmarks app bookmarklet endpoint, following
the same pattern as the existing Linkding and Shaarli entries.
Closes#8053
* i18n: use Nextcloud Bookmarks translations
* Update i18n
* Fix wrong search&replace
---------
Co-authored-by: Gerard Alvear <gerard.alvear@logiqd.me>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
* Add LinkAce as a sharing service
LinkAce is a self-hosted bookmark manager with a bookmarklet-style
add endpoint, similar to Linkding and Shaarli. Add a 'linkace' entry
to app/shares.php using its documented bookmarklet URL scheme, add
the corresponding translation key, and list it in the sharing
services documentation.
Fixes#6698
* i18n Ignore
---------
Co-authored-by: Gerard Alvear <gerard.alvear@logiqd.me>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
The Docker Quick Run / cron examples use single-quoted `-e 'CRON_MIN=1,31'`. This works on Linux/macOS shells and PowerShell, but not on Windows `cmd.exe`, which does not strip single quotes from arguments — they end up included literally in the variable, so the cron job never gets scheduled correctly. Using `-e CRON_MIN=*/20` (no quotes) works, which was the original report.
Fixes#4818
* Docs: remove CRON_MIN quoting
---------
Co-authored-by: Gerard Alvear <gerard.alvear@logiqd.me>
The original Fever API docs at feedafever.com/api are offline, and only
an archive.org snapshot remains linked from our Fever API page. Add a
Markdown mirror of that content (with Alkarex's HTML-to-Markdown
translation) directly into the docs so it is preserved even if the
archive.org copy disappears.
The mirror is a faithful copy of Alkarex's translation, including the
"API Public Beta" intro paragraphs, with only the heading levels
adjusted to fit this page.
Fixes#7331
Co-authored-by: Gerard Alvear <gerard.alvear@logiqd.me>
PHPMailer enables SMTPAutoTLS by default, meaning it opportunistically
upgrades a plain SMTP connection to STARTTLS whenever the server
advertises support for it, regardless of the 'secure' => '' setting
documented in config.default.php. Minz_Mailer never read or set
$mail->SMTPAutoTLS, so this behaviour could not be turned off, making
it impossible to talk to SMTP servers with a self-signed or otherwise
untrusted certificate (e.g. a mail server on an internal Docker
network) without importing a custom CA.
Add a new 'auto_tls' key to the 'smtp' config array, defaulting to
true to preserve current behaviour, and wire it through to
$mail->SMTPAutoTLS in Minz_Mailer. Document the new option.
Fixes#2997
Co-authored-by: Gerard Alvear <gerard.alvear@logiqd.me>
* Dispatch a JS event when an entry finishes being marked read/unread
Extensions that render their own read/unread indicator outside of the
main article list (e.g. a custom reading pane) had no way to know when
a mark read/unread AJAX request completed, so their own UI could get
stuck (e.g. a spinner never reset).
Dispatch a `freshrss:entryStateChange` event on `document` with the
entry id and new state once the toggle has been applied, following the
existing `freshrss:*` JavaScript event convention used elsewhere in
main.js.
Fixes#8862
* Minor preferences
---------
Co-authored-by: Gerard Alvear <gerard.alvear@logiqd.me>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
Explain that mod_auth_openidc defaults to client_secret_basic since
our reference Apache configuration does not set
OIDCProviderTokenEndpointAuth. If the identity provider's discovery
metadata advertises a token_endpoint_auth_methods_supported array
instead, mod_auth_openidc picks the first mutually supported method
from that array rather than falling back to client_secret_basic.
Also point to the .htaccess override for administrators who need
something else.
This only addresses the token-endpoint-auth-method part of #6102;
the redirect-URI-port question raised in that issue is out of scope
here.
Addresses the token-endpoint-auth-method part of #6102
Co-authored-by: Gerard Alvear <gerard.alvear@logiqd.me>
- Add a new `EntriesRead` / `entries_read` hook for extensions.
- Trigger the hook when `markRead()` changes entry read/unread state for explicit entry IDs.
- Cover both the default DAO path and the SQLite/PGSQL DAO path.
- Add Minz hook tests for the new hook signature and argument passing.
## Why
Extensions can already react to favorite/bookmark changes through `EntriesFavorite`. This adds the matching read/unread hook requested in #4051 for extensions that need to collect stats or react to manual read state changes.
Closes#4051
* Migrate markdownlint-cli to markdownlint-cli2
Replace markdownlint-cli with markdownlint-cli2 and consolidate the three
separate configuration points (.markdownlint.json rules, .markdownlintignore
excludes, and the '**/*.md' glob in the npm scripts) into a single
.markdownlint-cli2.jsonc holding config + globs + ignores.
The npm script names (markdownlint / markdownlint_fix) are unchanged, so the
CI step and the test/fix compound scripts need no change. markdownlint-cli2
v0.23.0 bundles the same markdownlint v0.41.0, so linting behaviour is
identical (112 files, 0 errors).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Add JSONC to .editorconfig
* Reduce obvious comments
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
Feeds moved between instances via OPML lost their refresh interval,
forcing manual reconfiguration. Add a 'frss:ttl' attribute (signed, so a
negative value also carries the muted state) to the OPML export, and read
it back on import. Documented in docs/en/developers/OPML.md.
For https://github.com/FreshRSS/FreshRSS/issues/5914
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* Add SSRF mitigations using `filter_var` and `CURLOPT_RESOLVE`
The idea is to prevent FreshRSS from sending any HTTP requests to internal services, except for the ones that are explicitly allowed in the config.
Based on https://github.com/moodle/moodle/blob/6e82b46a480826d1a85394d9e5087f7d82d1dd52/lib/filelib.php#L3818 and https://github.com/symfony/symfony/blob/8.1/src/Symfony/Component/HttpClient/NoPrivateNetworkHttpClient.phphttps://github.com/FreshRSS/simplepie/pull/76https://github.com/FreshRSS/simplepie/pull/78
* Add allowlist setting in Web UI
* make readme
* Update app/i18n/fr/admin.php
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
* make readme again
* make readme
* Further work
Still WIP and needs testing etc.
* Readd previous if check for domain combination allowlist
* Turn POST to GET after redirect
* Improve
* Update config.default.php
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
* make readme
* Skip SSRF check if `CURLOPT_PROXY` is set
* make readme
* Fix `!empty()` mistake
* Respect max redirects feed option when fetching with `httpGet()`
* Respect max redirects during SimplePie fetching + fix bypass
bypass fix: `CURLOPT_FOLLOWLOCATION` was moved below so that emulated redirects are enforced.
* Avoid FreshRSS and Minz code in SimplePie
https://github.com/FreshRSS/FreshRSS/pull/8400#discussion_r2935375980
* Corrected hook code
* phpdoc wrong return type
* Add CIDR support in allowlist
* Implement simple DNS caching
* Suppress `dns_get_record()` warnings
* A bit of proof-reading
* Minor typo
* Fix proxy logic
* Fix HTTP POST redirect logic
* Proofread checkCIDR
Add fixes for several situations
* Remove credentials from URL in logs
* Ensure `CURLOPT_FOLLOWLOCATION` is `false` by setting it at the end
* Fix codesniffer long line
* Fix potential bypass due to wrong return value
If there were no records returned by `dns_get_record()`, no overrides to `CURLOPT_RESOLVE` would get passed,
and a potential bypass could occur, when cURL would try to resolve the domain by itself.
* Put the URL at the end in logs
* Add documentation and environment variable support
* make readme
* Fix wrong behavior in case of IP
* Fix duplicate selector in CSS
* Minor type check change
* i18n fr, en
* Minor type check change
* Fix whitespace i18n fr
* make fix-all
* Fix `$ips_ok` not being returned after domain records were cached
* make readme
* PHPStan fix
* make readme
* Minor syntax in SimplePie
* Only return `null` if no allowed IPs were found
* Add wildcard *, help message
* Consistent docs with help message
* i18n: pl
* SimplePie compatibility PHP 7.2
* make fix-all
* Sync SimplePie
* https://github.com/FreshRSS/simplepie/pull/76
* 💥 Breaking change in the Changelog
* Document `INTERNAL_HOST_ALLOWLIST` in Docker docs
* Remove `Cookie` and `Authorization` headers in `httpGet()` during cross-origin redirect
* Minor whitespace
And same comment convention than below
* Remove authentication headers and change POST to GET on redirect in SimplePie
* Remove .local in Docker example
* Fill in default ports when comparing URL origins
* Remove .local from other places than the Docker example
* Rewrite WebSub subscribe to use `httpGet()`
* make fix-all
* Also unset `CURLOPT_USERPWD` during redirects
* phpcs fix
* Always unset `CURLOPT_FOLLOWLOCATION`
* Bump SimplePie
https://github.com/FreshRSS/simplepie/pull/78
* Update logic for CURLOPT_FOLLOWLOCATION
* Fix PHPStan
* Changelog fix security section
* Update most common RSS Bridge case
https://hub.docker.com/r/rssbridge/rss-bridge
* Replace misleading 127.0.0.1:8080 example for Docker
This does not make sense for a Docker container
---------
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
Closes#8882
- avoid recommending `/usr/share/` for writable FreshRSS data in the general install docs
- update the Debian/Ubuntu git install path and symlink example to `/var/www/FreshRSS`
- add an FAQ entry for PHP-FPM hardening that can make `/usr/` read-only despite correct Unix permissions
* Add a warning message to the API password section, add a log warning when a client uses GET, use POST in sample curl API usage
References #8834.
* Apply suggestion from @Inverle
Co-authored-by: Inverle <inverle@proton.me>
* Apply suggestion from @Inverle (Polish translation)
Co-authored-by: Inverle <inverle@proton.me>
* French translation
* Dutch translation
* German
* Apply suggestion from @Inverle api via post
Co-authored-by: Inverle <inverle@proton.me>
* Apply suggestion from @Inverle via POST (French)
Co-authored-by: Inverle <inverle@proton.me>
* Add user-agent
* update translation info in README
* avoid phpcs line too long
* also trigger on email as suggested by @Inverle
* i18n: fr typography
* make fix-all
---------
Co-authored-by: Inverle <inverle@proton.me>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
Add an opt-in CLI that exports each user's database to
`data/users/<user>/sqlite-backups/<YYYYMMDDTHHMMSSZ>.sqlite` (UTC) and
prunes older files to a configured count. Gated by two new settings,
`auto_sqlite_export.enabled` and `auto_sqlite_export.retention`.
Kept separate from `cli/db-backup.php` / `cli/db-restore.php`, which
stay the fixed-filename migration tool. First step of #8183.
Co-authored-by: Bjørn A. Andersen <polybjorn@users.noreply.github.com>
#button-update-loading and the generic .loading class (used by
#first_load) painted loader.gif as a background. Both now use the
#load_more.loading::after border-rotation spinner from #8804. The
unused #slider-content .loader rule is also removed. Drops the
--frss-loading-image variable, all twelve loader.gif files, and
references in the theme contribution docs.
Co-authored-by: Bjørn A. Andersen <polybjorn@users.noreply.github.com>
Rename the Nord theme display name from "Nord theme" to "Nord". The upstream Nord palette project at https://www.nordtheme.com refers to itself consistently as "Nord" throughout its branding; the "theme" suffix is redundant. The rename also matches the other entries in the theme picker (Ansum, Dark, Mapco, Pafat, Swage), which all use a single short name.
The theme directory, file paths, and CSS class names are unchanged. Only the user-facing display name in the theme picker (and the corresponding rows in the user docs) is affected.
Co-authored-by: Bjørn A. Andersen <polybjorn@users.noreply.github.com>
Jekyll 3 is unmaintained and its safe_yaml dependency breaks on
Ruby 3.4+ where base64 left the default stdlib. The docs workflow
already runs `bundle exec jekyll build` directly, so GitHub Pages'
Jekyll 3 lock does not apply here.
Changes:
* Pin `jekyll ~> 4.3`. Replace `jekyll-commonmark-ghpages` (pinned
to Jekyll 3) with upstream `jekyll-commonmark`. Drop unused
`jekyll-paginate`. Add `gem 'base64'` so the lockfile stays valid
on Ruby 3.4+ where it became a bundled gem.
* Move CommonMark options into `_config.yml` to match what the
`-ghpages` variant enabled by default: SMART, FOOTNOTES, UNSAFE
(so inline HTML like `<br>` in tables still renders), plus the
strikethrough, autolink, table, and tagfilter extensions. Set
Rouge as the highlighter.
* Strip the leading slash from `defaults.scope.path` (`/en/*` to
`en`). Jekyll 4 requires no leading slash, otherwise `page.lang`
silently fails to propagate and `jekyll-i18n_tags` crashes the
build.
* Pass `generateId=true` to the existing `anchor_headings.html`
include so headings get slugified IDs. The `-ghpages` variant
produced these by default.
* Bump CI Ruby to 3.4.9 and `cache-version` to invalidate the old
Jekyll 3 gem cache on the first run.
Build is about 9x faster (1.0s vs 9.2s). Compared the rendered
output page by page against the Jekyll 3 baseline; remaining
differences are whitespace, footnote class names (no CSS depends
on either set), and one HTML entity (`↩`) becoming the
literal `↩`. Visually and functionally the same.
Co-authored-by: Bjørn A. Andersen <polybjorn@users.noreply.github.com>