A password change ends every session, but a sign-in or sign-in link checked
against the old password just before the change could still save its session
just after, and that session outlived the change.
The session now checks, as it's saved, that the user's password is still the
one the request loaded. The save's transaction holds SQLite's write lock from
the start, so a change commits either before the check, which fails it, or
after the insert, and ends this session with the others. Sessions start
through the user's association so the check sees the user that was
authenticated, and a refused sign-in is answered like a wrong password.
(cherry picked from commit 19319a1cff)
An asset host's path can contain the asset directory too, as in
https://cdn.example.com/assets, and cutting each URL at the first one would
allow everything the host serves under it. Cut at the last one instead.
(cherry picked from commit 2ba940c683)
Behind a proxy that terminates TLS without X-Forwarded-Proto, Rails sees
http while the page is https, and an http source on a non-default port
doesn't match. A source without a scheme takes the page's own.
(cherry picked from commit 0a494a10cb)
Uploads are served from this origin too, so script-src 'self' would run one
that a cache still holds under a script type from before the upload guard.
Application pages now allow each importmap URL up to its asset directory
(relative root and asset host included), the inline importmap by hash, and
the service worker by worker-src.
(cherry picked from commit 2be2227f40)
With DISABLE_CONTENT_SECURITY_POLICY=true there's no application policy,
and the hash callback started an empty one, so pages sent an empty
Content-Security-Policy header. Skip the callback then.
(cherry picked from commit 2a449401ec)
script-src 'self', object-src 'none' and base-uri 'self', enforced. The
two inline scripts the importmap helper emits are allowed by their
SHA-256, and the origins its URLs point to (a configured asset host, in
any form Rails accepts) are allowed by name. There is no per-request
nonce, so cached pages stay valid.
DISABLE_CONTENT_SECURITY_POLICY=true leaves the policy off, for an
install whose proxy or customization injects scripts.
(cherry picked from commit 562888251e)
Backport: 1.5.2 has no response cache, so the cached-page test is left out.
Action Cable authorizes a Connection once at the WebSocket handshake and
never re-checks it. Destroying the session record refuses future handshakes
and HTTP requests bearing the cookie, but a socket opened before sign out
keeps its handshake-time current_user and keeps authorizing new
subscriptions and delivering frames as the signed-out user.
Reset the user's remote connections when the session is terminated. Clients
tear down and reconnect: the signed-out device carries a destroyed session
and cleared cookie and is rejected at the fresh handshake, while the user's
other devices with still-valid sessions reconnect and stay live. This reuses
the existing reset_remote_connections primitive already used on membership
removal, for the same reason.
Run the disconnect last and best-effort, after the session record and cookie
are already gone, so sign out completes even when the realtime service is
unreachable.
Active Storage mounts its direct-upload write endpoints -- POST
/rails/active_storage/direct_uploads and the disk-service PUT at
/rails/active_storage/disk/:token -- on framework controllers that inherit
from ActiveStorage::BaseController, so they never pass through
ApplicationController's require_authentication. Anyone who can read the
public login page can lift a CSRF token and Rails session cookie, POST to
the metadata endpoint, and receive a signed disk PUT URL without holding a
Campfire session_token.
That is enough to allocate ActiveStorage::Blob rows and persist bytes to
disk anonymously. The blobs stay unattached (no message can be created
without an account) and nothing purges them, so an unauthenticated caller
can grow storage without bound. Because the recommended self-host layout
co-locates uploaded files and the SQLite database on one /rails/storage
volume, that growth eventually makes database writes fail -- blocking login
and messaging until an administrator frees space and purges the blobs.
Campfire uploads attachments through MessagesController as a normal
multipart POST and does not use direct uploads at all, so these endpoints
have no legitimate anonymous caller. Require a valid Campfire session
before the metadata endpoint allocates a blob or the disk endpoint accepts
an upload; both return 401 to anonymous callers. Serving (disk#show,
representations, blob redirects) is unchanged.
- Use jbuilder instead of hashes
- Use resource instead of direct HTTP verbs
Verbs only make sense if you have one or two routes, if there are
multiple that emulate what resource does then it's better to use resource.
- Paginate using link headers
- Cache responses
This adds the ability to ban a user by their IP address.
When an admin is viewing a user profile, a new "Ban user" button is
present. Clicking on that will:
- Create a ban on the IP addresses that were tracked for that user's
sessions
- Remove all the messages authored by that user
- Log the user out immediately
In addition, that user will no longer be shown in most user lists in the
app. They are still shown to admins, in account settings. Viewing their
profile from there will now show a "Remove ban" button which can be used
to restore their access (it doesn't restore their messages though --
those are already gone -- it just removes the blocks so they can log in
again).