* Replace bcrypt challenge auth with direct password comparison Closes #2428 Highlights: * Replaced the bcrypt challenge authentication flow with direct password comparison against the bcrypt hash stored in the user configuration. * Removed the `bcrypt.js` dependency, so authentication no longer depends on JavaScript. * Removed the nonce and challenge mechanism and the `nonce` endpoint from the `javascript` controller. * Removed the JavaScript and HTML code used for the previous authentication flow. * Removed the `<noscript>` blocks that were only needed for the previous authentication flow. * Updated `FreshRSS_FormAuth::checkCredentials()` to accept the username, stored bcrypt hash, and plain-text password directly. * The stored password hash must still use the expected bcrypt format. * Empty or whitespace-only passwords are rejected even if the hash would otherwise match. * The bcrypt 72-byte limit is enforced during normal authentication. * Existing bcrypt passwords are handled correctly when the 72-byte limit cuts through a UTF-8 character. * Password input is no longer trimmed before authentication or password changes. Leading and trailing whitespace is therefore kept as part of the password. * Added `FreshRSS_FormAuth::passwordRequirementsMet()` to replace `FreshRSS_password_Util::check()`. * New passwords must be between 7 and 72 bytes by default. * The minimum length check can be disabled where needed. * Password requirements are now checked server-side for all password creation and update paths. * Added client-side byte-length validation for password fields using `TextEncoder` and `data-max-bytes`. * This gives better feedback for UTF-8 passwords. * Server-side validation remains authoritative. * Updated password handling in authentication, re-authentication, self-deletion, user creation, and user update paths so passwords are not trimmed automatically. * Added `FormAuthTest` to cover: * password length limits in bytes * UTF-8 passwords around the 72-byte limit * bcrypt truncation behavior during authentication * malformed and non-bcrypt hashes * empty passwords * authentication failures and log messages * Removed `bcrypt.js` from the documented dependencies and updated the authentication documentation. * Updated the installation and authentication views and JavaScript to match the new password handling. This simplifies the authentication flow, removes the client-side bcrypt challenge, makes password validation consistent between the client and server, and adds more test coverage while keeping compatibility with existing bcrypt password hashes. Because the password is now sent directly to the server instead of being processed by `bcrypt.js`, deployments should use HTTPS. Documentation for HTTPS setup can be handled separately. * Add test case with password truncated down to 71 characters * Compare log messages too during form auth tests * Mark the `getLastLog()` method as impure * Fix: Check password reqs against new password instead of current * Fix: Add custom JS validator for UTF-8, handle cut-off UTF-8 during login * Fix: Enforce password requirements everywhere server-side * Add TODO for updateUser in CLI * make readme * Fix extra.js in install.php * Don't report validity yet if the password is too short or not filled out * Simplify checking of whether to report validity * Fix: Do not trim password from user's input * Also `trim: false` in some userController paths * Use the proper argument order for PHPUnit's `assertSame()` * Remove `maxlength` in favor of `data-max-bytes` But add `pattern` again for some nojs support * make readme * composer run-script translations * Update i18n string for web form admin setting * i18n: fr * Minor whitespace --------- Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
- Back to main read-me
FreshRSS Command-Line Interface (CLI)
Note on access rights
When using the command-line interface, remember that your user might not be the same as the one used by your Web server. This might create some access right problems.
It is recommended to invoke commands using the same user as your Web server:
cd /usr/share/FreshRSS
sudo -u www-data sh -c './cli/list-users.php'
In any case, when you are done with a series of commands, you should re-apply the access rights:
cd /usr/share/FreshRSS
sudo cli/access-permissions.sh
Commands
Options in parenthesis are optional.
System
cd /usr/share/FreshRSS
./cli/prepare.php
# Ensure the needed directories in ./data/
./cli/do-install.php --default-user admin [ --auth-type form --environment production --base-url https://rss.example.net --language en --title FreshRSS --allow-anonymous --allow-anonymous-refresh --api-enabled --db-type sqlite --db-host localhost:3306 --db-user freshrss --db-password dbPassword123 --db-base freshrss --db-prefix freshrss_ ]
# --default-user must be alphanumeric and not longer than 38 characters. The default user of this FreshRSS instance, used as the public user for anonymous reading.
# --auth-type can be: 'form' (default), 'http_auth' (using the Web server access control), 'none' (dangerous).
# --environment can be: 'production' (default), 'development' (for additional log messages).
# --base-url should be a public (routable) URL if possible, and is used for push (WebSub), for some API functions (e.g. favicons), and external URLs in FreshRSS.
# --language can be: 'en' (default), 'fr', or one of the [supported languages](../app/i18n/).
# --title web user interface title for this FreshRSS instance.
# --allow-anonymous sets whether non logged-in visitors are permitted to see the default user's feeds.
# --allow-anonymous-refresh sets whether to permit anonymous users to start the refresh process.
# --api-enabled sets whether the API may be used for mobile apps. API passwords must be set for individual users.
# --db-type can be: 'sqlite' (default), 'mysql' (MySQL or MariaDB), 'pgsql' (PostgreSQL).
# --db-host URL of the database server. Default is 'localhost'.
# --db-user sets database user.
# --db-password sets database password.
# --db-base sets database name.
# --db-prefix is an optional prefix in front of the names of the tables. We suggest using 'freshrss_' (default).
# This command does not create the default user. Do that with ./cli/create-user.php.
./cli/reconfigure.php
# Same parameters as for do-install.php. Used to update an existing installation.
ℹ️ More options for the configuration of your instance may be set in
./data/config.custom.phpbefore the install process, or in./data/config.phpafter the install process.
User
cd /usr/share/FreshRSS
./cli/create-user.php --user username [ --password 'password' --api-password 'api_password' --language en --email user@example.net --token 'longRandomString' --no-default-feeds --purge-after-months 3 --feed-min-articles-default 50 --feed-ttl-default 3600 --since-hours-posts-per-rss 168 --max-posts-per-rss 400 ]
# --user must be alphanumeric, not longer than 38 characters. The name of the user to be created/updated.
# --password sets the user's password.
# --api-password sets the user's api password.
# --language can be: 'en' (default), 'fr', or one of the [supported languages](../app/i18n/).
# --email sets an email for the user which will be used email validation if it forced email validation is enabled.
# --no-default-feeds do not add this FreshRSS instance's default feeds to the user during creation.
# --purge-after-months max age an article can reach before being archived. Default is '3'.
# --feed-min-articles-default number of articles in a feed at which archiving will pause. Default is '50'.
# --feed-ttl-default minimum number of seconds to elapse between feed refreshes. Default is '3600'.
# --max-posts-per-rss number of articles in a feed at which an old article will be archived before a new article is added. Default is '200'.
./cli/update-user.php --user username [ ... ]
# Same options as create-user.php, except --no-default-feeds which is only available for create-user.php.
ℹ️ More options for the configuration of users may be set in
./data/config-user.custom.phpprior to creating new users, or in./data/users/*/config.phpfor existing users.
./cli/reconfigure-user.php --user username --list [ --show-secrets ]
# List all configuration attributes of the user, one per line as key=value.
# Known-sensitive keys (matching *hash, *key, *password, *token, *secret) are redacted as *** unless --show-secrets is given.
./cli/reconfigure-user.php --user username --key attribute
# Read the value of a single attribute. Exit code 2 if the key does not exist.
./cli/reconfigure-user.php --user username --key attribute --set --value 'new-value'
# Set an attribute to the given value. Type is inferred from the existing value (bool, int, string).
# Fails if the key does not exist; use --force to create a new key (e.g. for extension-specific config).
./cli/reconfigure-user.php --user username --key attribute --set --value-stdin
# Read the new value from stdin instead of --value (recommended for secrets to avoid leaking in shell history / ps).
./cli/reconfigure-user.php --user username --key attribute --unset
# Remove the attribute from the user's configuration. Exit code 2 if the key does not exist.
./cli/actualize-user.php --user username [ --feed-id 123 ]
# Fetch feeds for the specified user.
./cli/delete-user.php --user username
# Deletes the specified user.
./cli/list-users.php
# Return a list of users, with the default/admin user first.
./cli/user-info.php [ --human-readable --header --json --user username1 --user username2 ... ]
# -h, --human-readable display output in a human readable format
# --header outputs some columns headers.
# --json JSON format (disables --header and --human-readable but uses ISO Zulu format for dates).
# --no-db-size for faster responses by disabling database size calculation.
# --no-db-counts for faster responses by disabling counting the different types of articles in database.
# --user indicates a username, and can be repeated.
# Returns: 1) a * if the user is admin, 2) the name of the user,
# 3) the date/time of last user action, 4) the size occupied,
# and the number of: 5) categories, 6) feeds, 7) read articles, 8) unread articles, 9) favourites, 10) tags,
# 11) language, 12) e-mail.
./cli/import-for-user.php --user username --filename /path/to/file.ext
# The extension of the file { .json, .opml, .xml, .zip } is used to detect the type of import.
./cli/export-sqlite-for-user.php --user username --filename /path/to/db.sqlite
# Export the user’s database to a new SQLite file.
./cli/import-sqlite-for-user.php --user username [ --force-overwrite ] --filename /path/to/db.sqlite
# Import the user’s database from an SQLite file.
# --force-overwrite will clear the target user database before import (import only works on an empty user database).
./cli/export-opml-for-user.php --user username > /path/to/file.opml.xml
./cli/export-zip-for-user.php --user username [ --max-feed-entries 100 ] > /path/to/file.zip
Database
cd /usr/share/FreshRSS
./cli/db-backup.php
# Back-up all users respective database to `data/users/*/backup.sqlite`
# -q, --quiet suppress non-error messages
./cli/export-sqlite-auto.php
# Periodic SQLite export per user to `data/users/*/sqlite-backups/<YYYYMMDDTHHMMSSZ>.sqlite`, pruned to retention.
# Gated by `auto_sqlite_export` in `data/config.php`.
# -q, --quiet suppress non-error messages
./cli/db-restore.php --delete-backup --force-overwrite
# Restore all users respective database from `data/users/*/backup.sqlite`
# --delete-backup: delete `data/users/*/backup.sqlite` after successful import
# --force-overwrite: will clear the users respective database before import
./cli/db-optimize.php --user username
# Optimize database (reduces the size) for a given user (perform `OPTIMIZE TABLE` in MySQL, `VACUUM` in SQLite)
./cli/purge.php --user username
# Apply the purge policy (max number of articles, max age, exceptions) to all feeds of the given user.
# Equivalent to clicking ‘Purge now’ in the GUI, but suitable for cron.
# Purge queries are expensive and blocking; running daily or weekly is usually enough.
Translation
cd /usr/share/FreshRSS
./cli/manipulate.translation.php --action [ --help --key --value --language --revert --origin-language ]
# manipulate translation files.
# -a, --action selects the action to perform. (can be either: add, delete, exist, format, or ignore)
# -h, --help displays the commands help file.
# -k, --key selects the key to work on.
# -v, --value selects the value to set.
# -l, --language selects the language to work on.
# -r, --revert revert the action (only used with ignore action).
# -o, --origin-language selects the origin language (only used with add language action).
./cli/compile.plurals.php [ --all --file app/i18n/en/plurals.php --formula 'nplurals=2; plural=(n != 1);' ]
# Compile gettext plural formulas into PHP callables for runtime use.
# Plural source files are driven by a leading comment such as:
# // Plural-Forms: nplurals=2; plural=(n != 1);
# Run this command, or `make fix-all`, after editing those comments.
# See examples: https://docs.translatehouse.org/projects/localization-guide/en/latest/l10n/pluralforms.html
./cli/check.translation.php [ ---display-result --help --language fr --display-report --generate-readme ]
# Check if translation files have missing keys or missing translations.
# -d, --display-result display results of check.
# -h, --help display help text and exit.
# -l, --language set the language check.
# -r, --display-report display completion report.
# -g, --generate-readme generate translation progress section in readme.
Note about cron
Some commands display information on standard error; cron will send an email with this information every time the command will be executed (exited zero or non-zero).
To avoid cron sending email on success:
@daily /usr/local/bin/my-command > /var/log/cron-freshrss-stdout.log 2>/var/log/cron-freshrss-stderr.log || cat /var/log/cron-freshrss-stderr.log
Explanations:
/usr/local/bin/my-command > /var/log/cron-freshrss-stdout.log_ : redirect the standard output to a log file/usr/local/bin/my-command 2> /var/log/cron-freshrss-stderr.log: redirect the standard error to a log file|| cat /var/log/cron-freshrss-stderr.log_ : if the exit code of _/usr/local/bin/my-commandis non-zero, then it send by mail the content error file.
Now, cron will send you an email only if the exit code is non-zero and with the content of the file containing the errors.
Unix piping
It is possible to invoke a command multiple times, e.g. with different usernames, thanks to the xargs -n1 command.
Example showing user information for all users which username starts with ‘a’:
./cli/list-users.php | grep '^a' | xargs -n1 ./cli/user-info.php -h --user
Example showing all users ranked by date of last activity:
./cli/user-info.php -h | sort -k2 -r
Example to get the number of feeds of a given user:
./cli/user-info.php --user alex | cut -f6
#or
./cli/user-info.php --user alex --json | jq '.[] | .feeds'
Example to count the total number of feeds across all users:
./cli/user-info.php --json | jq '[.[].feeds] | add'
Example to get the name of the users who have not been active since a given date:
cli/user-info.php --json | jq '.[] | select(.last_user_activity < "2020-05-01") | .user'
Example to get the date and name of users who have not been active the past 24 hours (86400 seconds):
cli/user-info.php --json | jq -r '.[] | select((.last_user_activity | fromdate) < (now - 86400)) | [.last_user_activity, .user] | @csv'
Install and updates
If you want to administrate FreshRSS using git, please read our installation docs and update guidelines.