Add links to the Stripe payment page and the Stripe receipt page on
completed user upgrades.
The Stripe payment link is a link to the payment details on the Stripe
dashboard and is only visible to the owner.
Redirect the old user upgrade page, /user_upgrade/new, to the new
user upgrade page, /user_upgrades/new page. Some old forum posts still
link to the old page.
Log the Referer header, as well as the Sec-Fetch-* headers. These are
only sent by recent versions of Chrome; see https://www.w3.org/TR/fetch-metadata.
Require new accounts to verify their email address if any of the
following conditions are true:
* Their IP is a proxy.
* Their IP is under a partial IP ban.
* They're creating a new account while logged in to another account.
* Somebody recently created an account from the same IP in the last week.
Changes from before:
* Allow logged in users to view the signup page and create new accounts.
Creating a new account while logged in to your old account is now
allowed, but it requires email verification. This is a honeypot.
* Creating multiple accounts from the same IP is now allowed, but they
require email verification. Previously the same IP check was only for
the last day (now it's the last week), and only for an exact IP match
(now it's a subnet match, /24 for IPv4 or /64 for IPv6).
* New account verification is disabled for private IPs (e.g. 127.0.0.1,
192.168.0.1), to make development or running personal boorus easier
(fixes#4618).
Tag.category_for looked up a tag's category in the Redis cache. This was
only used in a few places (in related tags, and on the popular/missed
search pages). Get rid of this method so we can work towards getting rid
of caching tag categories in Redis.
We used to not validate user email addresses, which means we have a lot
of users with invalid emails. This script deletes all emails that are
missing both an `@` and a `.` This amounts to about 3000 invalid emails.
There are an additional ~1000 emails that are missing just the `@` sign.
Many of these are simple typos, for example skipping the `@` or typing a
2 instead. Some of these may be manually fixable.
This fixes an issue where upgrading to Gold could fail if you had an
invalid email address, because we prefilled the buyer's email address on
the Stripe checkout page and an invalid email would cause Stripe to
throw an error.
Mark links to the login page as rel="nofollow" so that search crawlers
don't constantly try to crawl it. Otherwise the fact the login url is
different on every page (/login?url=<current_url>) confuses crawlers.
Also strip the url param from the canonical url (<link rel="canonical">)
on the login page.
* Add a frequently asked questions section.
* Add nicer looking upgrade buttons.
* Format the page nicer.
* Prevent users from attempting invalid upgrades on users that are
already Platinum or above.
* Refactor to move upgrade logic from UserPromotion to UserUpgrade.
* Send the recipient and the purchaser of a gifted upgrade separate
dmail notifications.
Add a model to store the status of user upgrades.
* Store the upgrade purchaser and the upgrade receiver (these are
different for a gifted upgrade, the same for a self upgrade).
* Store the upgrade type: gold, platinum, or gold-to-platinum upgrades.
* Store the upgrade status:
** pending: User is still on the Stripe checkout page, no payment
received yet.
** processing: User has completed checkout, but the checkout status in
Stripe is still 'unpaid'.
** complete: We've received notification from Stripe that the payment
has gone through and the user has been upgraded.
* Store the Stripe checkout ID, to cross-reference the upgrade record on
Danbooru with the checkout record on Stripe.
This is the upgrade flow:
* When the user clicks the upgrade button on the upgrade page, we call
POST /user_upgrades and create a pending UserUpgrade.
* We redirect the user to the checkout page on Stripe.
* When the user completes checkout on Stripe, Stripe sends us a webhook
notification at POST /webhooks/receive.
* When we receive the webhook, we check the payment status, and if it's
paid we mark the UserUpgrade as complete and upgrade the user.
* After Stripe sees that we have successfully processed the webhook,
they redirect the user to the /user_upgrades/:id page, where we show
the user their upgrade receipt.
This upgrades from the legacy version of Stripe's checkout system to the
new version:
> The legacy version of Checkout presented customers with a modal dialog
> that collected card information, and returned a token or a source to
> your website. In contrast, the new version of Checkout is a smart
> payment page hosted by Stripe that creates payments or subscriptions. It
> supports Apple Pay, Dynamic 3D Secure, and many other features.
Basic overview of the new system:
* We send the user to a checkout page on Stripe.
* Stripe collects payment and sends us a webhook notification when the
order is complete.
* We receive the webhook notification and upgrade the user.
Docs:
* https://stripe.com/docs/payments/checkout
* https://stripe.com/docs/payments/checkout/migration#client-products
* https://stripe.com/docs/payments/handling-payment-events
* https://stripe.com/docs/payments/checkout/fulfill-orders
* Fix a bug where non-GET 404 requests weren't handled.
* Fix a bug where non-HTML 404 requests weren't handled.
* Show a random image from a specified pool on the 404 page.
Remove various redirects for old Danbooru 1 links. Most of these
received little to no traffic and were only used in a small number of
places in old comments or forum posts.
Add a debug mode option. This is useful when debugging failed tests.
Debug mode disables parallel testing so you can set breakpoints in tests
with binding.pry (normally parallel testing makes it hard to set
breakpoints).
Debug mode also disables global exception handling for controllers. This
lets exceptions bubble up to the console during controller tests
(normally exceptions are swallowed by the controller, which prevents you
from seeing backtraces in failed controller tests).
Don't show other names of banned artists on the /artists page to
anonymous users. Hides potentially sensitive information from Google and
logged out users.
Most of the new settings aren't relevant to us. We do have to fix some
tests to work around a Rails bug. `assert_enqueued_email_with` uses the
wrong queue, so we have to specify it explicitly. This is fixed in Rails
HEAD but not yet released.
* Introduce an abstraction for normalizing attributes. Very loosely
modeled after https://github.com/fnando/normalize_attributes.
* Normalize wiki bodies to Unicode NFC form.
* Normalize Unicode space characters in wiki bodies (strip zero width
spaces, normalize line endings to CRLF, normalize Unicode spaces to
ASCII spaces).
* Trim spaces from the start and end of wiki page bodies. This may cause
wiki page diffs to show spaces being removed even when the user didn't
explicitly remove the spaces themselves.
The `source` command is a bash-ism and doesn't work in a strictly POSIX
shell like dash, which is the /bin/sh on Debian/Ubuntu. Use `.` instead.
https://en.wikipedia.org/wiki/Dot_(command)
Don't allow wiki pages to have invalid names.
This incidentally means that you can't create wiki pages for pools. For
example, you can't create a wiki titled "pool:almost_heart-warming".
This is not a valid tag name, so it's not a valid wiki name either. This
was done in a handful of cases to translate Pixiv tags to Danbooru pools
(see: <https://danbooru.donmai.us/wiki_page_versions?search[title_like]=pool:*>)
Also fix it so that titles are normalized before validation, not before save.
Rename the following post replacement attributes:
* file_size_was -> old_file_size
* file_ext_was -> old_file_ext
* image_width_was -> old_image_width
* image_height_was -> old_image_height
* md5_was -> old_md5
In Rails 6.1, having attributes named `file_size` and `file_size_was` on
the same model breaks things because it conflicts with Rails' dirty
attribute tracking.
* Swap out activerecord-hierarchical_query gem for some guy's patched
version because the mainline version is incompatible with 6.1.
* Disable meta_request gem because it hangs puma on startup on 6.1.
Expand the tag abbreviation system introduced in b0be8ae45 so that it
works in searches and when tagging posts, not just in autocomplete.
For example, you can tag a post with /evth and it will add the tag
eyebrows_visible_through_hair. You can search for /evth and it will
search for the tag eyebrows_visible_through_hair.
Some more examples:
* /ops is short for one-piece_swimsuit
* /hooe is short for hair_over_one_eye
* /saol is short for standing_on_one_leg
* /tlozbotw is short for the_legend_of_zelda:_breath_of_the_wild
If two tags have the same abbreviation, then the larger tag takes
precedence. For example, /be is short for blue_eyes, not brown_eyes,
because blue_eyes is the bigger tag.
If there is an existing shortcut alias that conflicts with the
abbreviation, then the alias take precedence. For example, /sh is short
for suzumiya_haruhi, not short_hair, because there's an old alias for
/sh -> suzumiya_haruhi.
Limit tag length to 170 chars. 170 chars was chosen because it's
longer than the longest active tag on Danbooru.
Tag length is limited because in some contexts we can't deal with
excessively long tags. Tag autocorrect for example uses the levenshtein
function in Postgres, which can't handle strings more than 255 chars long.
Disallow tags from starting with a '/' character. This is so that tag
abbreviations in autocomplete, which start with a '/', don't conflict
with regular tags.
Also disallow some other punctuation characters: `%{})]. Currently no
tags start with these characters. This is to reserve other special
characters in case we need them for other future syntax extensions.
Add the following search operators:
* /tags?search[post_count_eq]=42
* /tags?search[post_count_not_eq]=42
* /tags?search[post_count_gt]=42
* /tags?search[post_count_gteq]=42
* /tags?search[post_count_lt]=42
* /tags?search[post_count_lteq]=42
Works for all numeric attributes on all index actions.
This is the scenario:
* You type something in autocomplete, let's say 'touhou'.
* Autocomplete calls /autocomplete?search[query]=touhou&search[type]=tag_query
* The endpoint returns JSON, because the autocomplete call sets an
`Accept: application/json` header requesting JSON.
* Visit /autocomplete?search[query]=touhou&search[type]=tag_query in your browser.
* Notice that the cached JSON response is incorrectly returned, not an
HTML response like the browser requested.
The problem is that the response type is chosen based on the Accept
header, but the response didn't set the `Vary: Accept` header, so the
browser doesn't know the response type can vary and so it incorrectly
returns the cached response.
This issue is partially fixed by Rails 6.1 ([1]), which properly sets the
`Vary: Accept` header when the response depends on the Accept header.
However, the next issue is that Cloudflare doesn't respect the Vary
header at all ([2], [3]). Therefore we can't use the Accept header to
pick the format, instead we have explicitly specify the format with
/autocomplete.json.
This is clearer and better for caching anyway. Using the `Vary: Accept`
header reduces the cache hit rate, because the exact format of the
Accept header varies across browsers, which fragments the cache.
Whew.
[1] https://github.com/rails/rails/pull.36213
[2] https://community.cloudflare.com/t/cloudflare-cdn-cache-to-support-http-vary-header/160802
[3] https://support.cloudflare.com/hc/en-us/articles/115003206852
[4] https://www.smashingmagazine.com/2017/11/understanding-vary-header/
Fix session cookies being sent in publicly cached /autocomplete.json
responses. We can't set any cookies in a response that is being publicly
cached, otherwise they'll be visible to other users. If a user's session
cookies were to be cached, then it would allow their account to be stolen.
In reality, well-behaved caches like Cloudflare will simply refuse to
cache responses that contain cookies to avoid this scenario.
https://support.cloudflare.com/hc/en-us/articles/200172516-Understanding-Cloudflare-s-CDN:
BYPASS is returned when enabling Origin Cache-Control. Cloudflare also
sets BYPASS when your origin web server sends cookies in the response
header.
Fix an exception that could occur when typing "/" by itself in
autocomplete and a regular tag starting with "/" was returned. This
caused an exception in `r[:antecedent].length` because the tag's
antecedent was nil.
* Remove the `source` and `weight` html data attributes (no longer used).
* Make the `type` html data attribute properly indicate the completion
type. Valid types: `tag`, `tag-alias`, `tag-abbreviation`,
`tag-autocorrect`, `tag-other-name`.
Allowing typing Japanese tags in autocomplete. For example, typing 東方
in autocomplete will be completed to the touhou tag. Typing ぶくぶ will
complete to the bkub tag.
This works using wiki page and artist other names. Effectively, any name
listed as an other name in a wiki or artist page will be treated like an
alias for autocomplete purposes. This is limited to non-ASCII other names,
to prevent English other names from interfering with regular tag searches.
* Set Max-Age= flag instead of Expires= flag.
* Set Secure flag when using HTTPS.
* Extend default cookie lifetime from 1 year to 20 years.
* Remove "session" expiration option (unused).
* Remove max cookie size check.
The cookie size check was previously added in #2518 to deal with running
out of space due to tag scripts and blacklists. This should no longer
happen since we no longer use cookies for these things. Remove the
warning because it should never happen, we can't fix it if it does, and
the user probably won't know how to fix it either.
* Let Mods and Admins see the email addresses of users below their level.
* Let users see their own email address on their profile.
* Let users verify or edit their email address from their profile.
This is to make catching sockpuppets easier, and to make it easier for
users to fix their email.
Don't allow admins to bypass promotion restrictions by manually updating
user levels with a `PUT /users/:id` API call. Level changes have to go
through the /admin/users/:id/edit page.
Don't allow gifting Gold or Platinum upgrades to users above Platinum
level. Fixes an exploit where you could demote Builders and above by
gifting them an upgrade.
* Test that the user upgrade process integrates with Stripe correctly.
* Replace a deprecated `card` param with `source` in `Stripe::Charge.create`.
* Rescue Stripe::StripeError instead of Stripe::CardError so that we
handle failures outside of card failures, such as network errors.
New rules for user promotions:
* Moderators can no longer promote other users to moderator level. Only
Admins can promote users to Mod level. Mods can only promote up to Builder level.
* Admins can no longer promote other users to Admin level. Only Owners
can promote users to Admin. Admins can only promote up to Mod level.
* Admins can no longer demote themselves or other admins.
These rules are being changed to account for the new Owner user level.
Also change it so that when a user upgrades their account, the promotion
is done by DanbooruBot. This means that the inviter and the mod action
will show DanbooruBot as the promoter instead of the user themselves.
Add a new Owner user level for the site owner. Highly sensitive
operations like manually changing the passwords of other users will be
restricted to the site owner.
* Rename the `#negate` and `#and` methods that we monkey patch into
ActiveRecord::Relation. These methods are now defined in Rails 6.1, but
they shadow our methods and have slightly different behavior.
* Fix a call to `invert`. It no longer accepts an argument.
Upgrade the http-cookie gem to a personal fork containing a bugfix for a
http-cookie bug that is triggered by Rails 6.1.
The bug is that HTTP::Cookie objects raise an exception if they're
compared against non-cookie objects. This bug gets triggered when
the Nijie source strategy calls `Rails.cache.fetch` to cache the
Nijie login cookie. `Rails.cache.fetch` ends up calling
ActiveSupport::Cache::Store::Entry#dup_value!, which compares the cookie
with `true`, which triggers the exception.
The http-cookie gem hasn't been updated for 4 years, so we're stuck
patching the library ourselves.
Display autocorrected tags similar to aliases, with an arrow pointing at
the corrected tag, but with a dotted underline beneath the misspelled
tag to indicate that it's misspelled.
Tune autocorrect to produce fewer false positives. Before we used
trigram similarity. Now we use Levenshtein edit distance with a dynamic
typo threshold. Trigram similarity was able to correct large
transpositions (e.g. `miku_hatsune` -> `hatsune_miku`), but it was bad
at correcting small typos. Levenshtein is good at small typos, but can't
correct large transpositions.
Fix an exception that was thrown when trying to autocomplete saved
search labels (e.g. `search:all`) as an anonymous user. This was a
pre-existing bug.
The previous cache policy was that all autocomplete results were cached
for a fixed 7 days. The new policy is that if autocomplete returns more
than 10 results they're cached for 24 hours, otherwise if it returns
less than 10 results they're cached for 1 hour.
The rationale is that if autocomplete returns a lot of results, then the
top 10 results are relatively stable and unlikely to change, but if it
returns less than 10 results, then the results are unstable and can be
easily changed.
We also change it so that autocomplete calls can be cached publicly.
Public caching means that HTTP requests are cached by Cloudflare. This
will ideally reduce load on the server and reduce latency for end users.
This is only safe for calls that return the same results for all users
(i.e. the results don't depend on the current user), since the cache is
publicly shared by all users. Currently username, favgroup, and saved
search autocomplete results depend on the current user, so they can't be
publicly cached.
Reworks tag autocomplete to work the same way for all users. Previously
autocomplete for Builders worked differently than autocomplete for
regular users.
This is how it works now:
* If the search starts with a slash (/), then do a tag abbreviation
match. For example, `/evth` matches eyebrows_visible_through_hair.
* Otherwise if the search contains a wildcard (*), then just do a simple
wildcard search.
* Otherwise do a tag prefix match against tags and aliases. For example,
`black` matches all tags or aliases beginning with `black`.
* If the tag prefix match returns no results, then do a autocorrect match.
The differences for regular users:
* You can abbreviate tags with a slash (/).
The differences for Builders:
* Now tag abbreviations have to start with a slash (/).
* Autocorrect isn't performed unless a regular search returns no results.
* Results are always sorted by tag count. Before different types of
results (regular tag matches, alias matches, abbreviation matches,
and autocorrect matches) were all mixed together based on a tag
weighting scheme.
This refactors the autocomplete Javascript to use a single dedicated
/autocomplete.json endpoint instead of a bunch of separate endpoints.
This simplifies the autocomplete Javascript by making it so that instead
of calling a different endpoint for each type of query (for users, wiki
pages, pools, artists, etc), then having to parse the results of each
call to get the data we need, we can call a single endpoint that returns
exactly what we need.
This also means we don't have to parse searches clientside in order to
autocomplete metatags. Instead we can just pass the search term to the
server and let it parse the search, which is easy to do serverside.
Finally, this makes autocomplete easier to test, and it makes it easier
to add more sophisticated autocomplete behavior, since most of the logic
lives serverside.
Remove the "Repopulated 1 old tag" message. Show "Created 1 new tag"
instead. The distinction between creating a brand new tag and
repopulating an empty tag doesn't matter.