Commit graph

604 commits

Author SHA1 Message Date
156cabbab4 Add shadowban mechanism for closet lists
Simple enough to start! If `shadowbanned: true` gets set on a user,
then we show a 404 instead of the actual list page, *unless* you're
logged in as that user, or coming from a known IP of that user.

This isn't a very strong mechanism! Just something to hopefully
increase the costs of messing around with list spam.
2024-04-20 20:57:15 -07:00
4ae5acfdc3 Disallow email addresses in closet list descriptions
Just another attempt to communicate the rules!
2024-04-16 17:04:31 -07:00
fa202af26d Time out if manifest loading takes too long
This hasn't been causing issues as far as I know, I just noticed
*months ago* that I forgot to do this, and have had a sticky note about
it on my desk since then lol.

I tested this by temporarily setting the timeout to `0.5`, and watching
it fail!
2024-04-16 16:18:51 -07:00
f8e4e83723 To "fetch" the image hash of an image hash name, just take off the @!
A further optimization, this lets us use the image hash as the new hash
for the pet type if it would be useful! (whereas before this change,
we'd dip into `fetch_metadata` and just get back `nil`, which was okay
too but a little bit less helpful!)
2024-04-16 15:57:39 -07:00
3ea0842f00 Skip loading image hashes for "pets" that start with @
This is an optimization on top of 9d8f035, in which we skip the network
request altogether in this case that we know will and should fail!
2024-04-16 10:10:28 -07:00
9d8f035360 Oops, stop crashing when modeling "pets" that start with @
Ahh, we recently added a step to pet loading that sends a metadata
request to `PetService.getPet`, which is now (in a sense, correctly!)
raising a `PetNotFound` error when we try modeling with a "pet" that
starts with `@` (a trick we use in situations where we can get an image
hash for a modeling situation, but not an irl pet itself).

In this change, we make it no longer a crashing issue if the pet
metadata request fails: it's not a big deal to have a `PetType` have no
image hash or not have it be up-to-date!

In the next change, I'll also add an optimization to skip fetching it
altogether in this case—but I wanted to see this work first, because
the more general resilience is more important imo!
2024-04-16 10:03:36 -07:00
40bfd42af6 NeoPass launch message on homepage
Some cute logged-in-user differentiation uwu
2024-04-12 07:22:25 -07:00
644b181ed0 Use Neopets username as base name for new NeoPass accounts, if possible
Yay, we got the API endpoint for this! The `linkage` scope is the key.

Rather than pulling back the specific fallback behavior we had wrote
for usernames before, which was slightly different and involved
appending `neopass` in there too (e.g. `matchu-neopass-1234`), I
figured let's just use a lot of the same logic, and just use the
preferred name as the base name. (I figure the `neopass` suffix isn't
that useful anyway, `matchu-1234` kinda looks better tbh! And it's all
fallback stuff that I expect serious users to replace, anyway.)
2024-04-09 07:48:13 -07:00
d50672fd73 Add User-Agent header to our AMFPHP requests
Oh right, I never did catch this when setting up User-Agent in the app!

(I noticed this because I'm making a new request now, and went to look
how we set it in previous stuff, and was like. Oh. We don't anywhere
right now. Interesting LOL)
2024-04-09 06:55:41 -07:00
58d86cf3ac Prevent user from removing all their login methods
Oh right, if you can remove your email, there's a way to fully lock out
your account:

1. Create account via NeoPass, so no password is set.
2. Ensure you have an email saved, then disconnect NeoPass.
3. Remove the email.
4. Now you have no NeoPass, no email, and no password!

In this change, we add a validation that requires an account to always
have at least one login method. This works well for the case described
above, and also helps offer server-side validation to the "can't
disconnect NeoPass until you have an email and password" stuff that
previously was only enforced by disabling the button.

That is, the following procedure could also lock you out before,
whereas now it raises the "Whoops, there was an error disconnecting
your NeoPass from your account, sorry." message:

1. Create account via NeoPass, so no password is set.
2. Ensure you have an email saved, so "Disconnect" button is enabled.
3. Open a new browser tab, and remove the email.
4. In the original browser tab, click "Disconnect".
2024-04-09 06:40:56 -07:00
f450937952 Oops, fix error when saving user settings with no password set
Ah okay, if you leave the password field blank but don't have one set,
our simple `update` method gets annoyed that you left it blank.

In this change, we simplify the model API by just overriding
`update_with_password` with our own special behavior for the
no-password case.
2024-04-09 06:20:13 -07:00
d10c11e261 Oops, fix tracking neopass_email on account creation.
My bad!
2024-04-09 05:45:39 -07:00
5cc219c795 Connect a NeoPass to an existing account
including validation logic to make sure it's not already connected to
another one!

The `intent` param on the NeoPass form is part of the key! Thanks
OmniAuth for making it easy to pass that data through!
2024-04-08 05:33:58 -07:00
09bccd41da Oops, stop saying "Welcome back" for new NeoPass users!
Ahh I see, if you do a no-op update, it still clears the
`previously_new_record?` state, so our NeoPass controller thinks this
account already existed. Instead, let's only do this update if it's an
account that already exists, instead of depending on the no-op-iness!
2024-04-08 05:00:27 -07:00
3e92d89765 Fix error when multiple accounts have a blank email address 2024-04-08 03:46:41 -07:00
ed89380152 Oops, allow NeoPass to be disconnected if you have no email address
That is, you're required to add a password *or* an email before
disconnecting your NeoPass, but idk, I think it's rude to demand an
email from someone for the sake of *disconnection*. Email is no longer
required for accounts that already exist!
2024-04-07 08:42:41 -07:00
54a052848a Disable disconnecting NeoPass if no password/email is set
Just as a precautionary thing! Seems polite.
2024-04-07 08:27:02 -07:00
b827727102 Rename AuthUser#neopass? -> AuthUser#uses_neopass?
This is more consistent with the `uses_omniauth?` we already have, and
it also will help for the next change, where I want a `uses_password?`
method (and using the name `password?` breaks some of Devise's
validation code).
2024-04-07 08:12:38 -07:00
88a2688ac8 Add form to disconnect NeoPass
Can't connect it back yet! But you can disconnect it! :3
2024-04-07 07:52:23 -07:00
21b967f83d Add some NeoPass info to the Settings page, if you have one
No buttons to change it or anything, or to link if you don't! Just a
basic display and explanation!
2024-04-07 07:17:33 -07:00
d5c3bc087e Track neopass_email when logging in with NeoPass
Gonna use this in the Settings UI to communicate what NeoPass you're
connected to!
2024-04-07 07:17:07 -07:00
bd4b67316c Refactor image hash loading to use PetService.getPet, not CPN redirs
Previously, the way we loaded the image hash for a given pet was to
navigate to `https://pets.neopets.com/cpn/<pet_name>/1/1.png`, but
*not* follow the redirect, and extract the image hash from the URL
where it redirected us to.

In this change, we refactor to use the AMFPHP RPC `PetService.getPet`
instead. I don't think it had this data last time I looked at it, but
now it does! Much prefer to use an actual RPC than our weird hacky
thing!

(We might also be able to use this call for other stuff, like
auto-labeling gender & mood for pet states, maybe?? That's in this data
too! We used to load petlookups for this, long long ago, before the
petlookup captchas got added.)
2024-04-06 02:56:40 -07:00
1d3aac436b Fix detecting "pet not found" case
Oh huh, now instead of returning an AMF error message, the service
returns a blank pet object if the pet doesn't exist. Okay!
2024-04-06 02:49:08 -07:00
ebc01518bd Remove unused Pet::WARDROBE_PATH constant
Huh, weird! Goodbye!
2024-04-06 02:38:20 -07:00
848e71f16d Remove unused Pet.from_viewer_data constructor
I guess this was like, we had some call site that was handling loading
the viewer data itself, and didn't want to have to reload it?

But whatever, not used now, let's simplify! We can rebuild this easily
if we need it again.
2024-04-06 02:33:28 -07:00
f0ac2adc78 Remove unused options when loading pets
Locale is the big one that's not really relevant anymore (I don't want
to be loading non-English item names anymore, now that we've simplified
to only support English like TNT has!), but there was also `item_scope`
and stuff.

The timeout option is technically not used in any call sites, but I
think that one's useful to leave around; timeout stuff is important,
and I don't want to rewrite it sometime if we need it again!
2024-04-06 02:31:24 -07:00
57dcc88b27 Refactor pet image hash loading into the Pet model, not PetType
Just a small thing, I guess when I was a kid I did a weird thing where
I attached `origin_pet` to `PetType`, then upon saving `PetType` I
loaded the image hash for the pet to save as the pet type's new image
hash.

I guess this does have the nice property of not bothering to load that
stuff until we need it? But whatever, I'm moving this into `Pet` both
to simplify the relationship between the models, and to prepare for
another potential refactor: using `PetService.getPet` for this instead!
2024-04-06 02:25:22 -07:00
6618651fcb Use completely random NeoPass usernames for now
Ahh, I had assumed the `uid` provided by NeoPass would be the user's
Neopets username, but in hindsight that was never gonna work out since
NeoPass doesn't think of things in terms of usernames at all!

For now, we create 100% random NeoPass usernames, of the form
"neopass-shoyru-5812" or similar. This will be an important fallback
anyway, because it's possible to have a NeoPass with *no* Neopets.com
account attached.

But hopefully we'll be able to work with TNT to request the user's main
Neopets account's username somehow, to use that as the default when
possible!
2024-04-01 05:57:06 -07:00
b03d9b264a Increase maximum username length to 30
I'm writing some code for default NeoPass usernames, and they can get
kinda long, so I want to clear some extra space for them!
2024-04-01 05:53:38 -07:00
7f4c34ff6a Oops, stop requiring a new password whenever AuthUser is changed
Ah right, I went and checked the Devise source code, and the default
implementation for `password_required?` is a bit trickier than I
expected:

```ruby
def password_required?
  !persisted? || !password.nil? || !password_confirmation.nil?
end
```

Looks like `super` does a good enough job here, though! (I'm actually
kinda surprised, I wasn't sure how Ruby's `super` rules worked, and
this isn't a subclass thing—or maybe it is, maybe the `devise` method
adds a mixin? Idk! But it does what I expect, so, great!)

So now, we require the password if 1) Devise doesn't see a UI reason
not to, *and* 2) the user isn't using OmniAuth (i.e. NeoPass).

This had caused a bug where it was impossible to use the Settings page
*without* changing your password! (The form says it's okay to leave it
blank, which stopped being true! But now it's fixed!)
2024-03-14 19:20:33 -07:00
3eeb5d1065 Actually create user from NeoPass authentication! <3 <3
Whew, exciting! Still done nothing against the live NeoPass server, but
we've got this fully working with the development server, it seems!
Wowie!!

This is all still hidden behind secret flags, so it's fine to deploy
live. (And it's not actually a problem if someone gets past to the
endpoints behind it, because we haven't actually set up real
credentials for our NeoPass client yet, so authentication will fail!)

Okay time to lie down lol.
2024-03-14 19:11:06 -07:00
f483722af4 NeoPass strategy interacts with dev NeoPass server, which is still WIP
In this change, we wire up a new NeoPass OAuth2 strategy for OmniAuth,
and hook up the "Log in with NeoPass" button to use it!

The authentication currently fails with `invalid_credentials`, and
shows the `owo` response we hardcoded into the NeoPass server's token
response. We need to finally follow up on the little `TODO` written in
there!
2024-03-14 16:13:31 -07:00
77057fe6a2 Add hidden "Log in with NeoPass" button, to placeholder login strategy
If you pass `?neopass=1` (or a secret value in production), you can see
the "Log in with NeoPass" button, which currently takes you to
OmniAuth's "developer" login page, where you can specify a name and
email and be redirected back. (All placeholder UI!)

We're gonna strip the whole developer strategy out pretty fast and
replace it with one that uses our NeoPass test server. This is just me
checking my understanding of the wiring!
2024-03-14 15:34:24 -07:00
08b1b9e83b Add OmniAuth plugin to AuthUser
This is setting us up for NeoPass, but first we're just gonna try stuff
with the "developer" strategy that's built in for testing, rather than
using the NeoPass dev server!
2024-03-14 15:06:13 -07:00
cf6921329d Oops, choose the *first* PNG from the manifest, to avoid reference art
When we moved more logic into the main app, we made some assumptions
about manifest art that were different than Impress 2020's, in hopes
that they would be More Correct for potential future edge cases.

Turns out, they were actually *less* correct for *current* edge cases!
Chips linked us to a few examples, including this Reddit post:

https://www.reddit.com/r/neopets/comments/1b8fd72/i_dont_think_thats_the_correct_image/

Fixed now!
2024-03-07 14:16:05 -08:00
522287ed53 Fix MissingAttributeError in ClosetHanger#merge_quantities
Oh rough, when moving an item list entry from one list to another, our
logic to merge their quantities if it's already in that list was just
fully crashing!

That is, moves without anything to merge were working, but moves that
required a merge were raising Internal Server Error 500, because the
`list_id` attribute wasn't present.

I'm not sure why this ever worked, I'm assuming using `list_id` in the
`where` condition would include it in the `select` implicitly in a
previous version of Rails? Or maybe Rails used to have fallback
behavior to run a second query, instead of raising
`MissingAttributeError` like it does now?

Well, in any case, this seems to fix it! Whew!
2024-02-28 13:30:55 -08:00
87782767f8 Filter search in wardrobe-2020 by alt styles!
Yay, we finally added it, the part where we include the appearance data
for the items based on both the species/color and the alt style! Now,
switching to Faerie Acara correctly filters the search only to items
that would fit (I think literally just only body_id=0 items right now,
but we're not banking on that!)
2024-02-27 16:11:06 -08:00
421f2ce39f Use fits:nostalgic-faerie-draik filter format when we can
This only *really* shows up right now in the case where you construct
an Advanced Search form query (which only the wardrobe-2020 app does
now, and in limited form), and we return the query back (which only
gets used by the HTML view for item search, which doesn't have any way
to build one of these requests against it).

This is because, if you just type in `fits:alt-style-87305`, we always
keep your search string the same when outputting it back to you, to
avoid the weirdness of canonicalizing it and changing it up on you in
surprising ways!

But idk, this is just looking forward a bit, and keeping the system's
semantics in place. I hope someday we can bring robust text filter
and Advanced Search stuff back into the main app again, maybe!
2024-02-27 15:51:27 -08:00
18c7a34b8f Update series_name for alt styles to be null, with a fallback string
I considered this at first, but decided to keep it simple until it
turned out to matter. Oops, it already matters, lol!

I want the item search code to be able to easily tell if the series
name is real or a placeholder, so we can decide whether to build the
filter text in `fits:$series-$color-$species` form or
`fits:alt-style-$id` form.

So in this change, we keep it that `AltStyle#series_name` returns the
placeholder string if none is set, but callers can explicitly ask
whether it's a real series name or not. Will use this in our next
change!
2024-02-27 15:48:28 -08:00
9243193bc8 Add support for fits:alt-style-87305 item searches
Easy peasy!
2024-02-27 15:36:45 -08:00
d983a20989 Add support for fits:nostalgic-faerie-draik item searches
Nice and easy now that we have `series_name` in the database!

I didn't add the `fits:alt-style-12345` case yet though!
2024-02-27 15:33:08 -08:00
9f74e6020e Add series_name database field to alt styles
Previously we did this hackily by comparing the ID to a hardcoded list
of IDs, but I think putting this in the database is clearer and more
robust, and it should also help with our upcoming item search stuff
that will filter by it!
2024-02-27 15:28:05 -08:00
cd786ffcb1 Oops, I broke user filters in my previous refactor!
Right, `user` is an important argument that I missed! 😅
2024-02-27 15:08:15 -08:00
76d741091c Extract "raise_search_error" method in item search query parsing
Just to make all this a bit more wieldy, and not repeat the same prefix
all the time!
2024-02-27 15:03:18 -08:00
61b1a1aed1 Improve parsing fits:blue-acara filter
Previously, passing in `fits:blue` would cause a crash, because
`species_name` part of the split would be `nil`, oops!

In this change, we use a regex for more explicitness about the pattern
we're trying to match. We'll also add more cases next! (You'll note the
error message mentions `fits:nostalgic-faerie-draik`, which isn't
actually possible yet, but will be!)
2024-02-27 14:56:36 -08:00
19ebf4d78a Extract item search filter parsing into helper methods
I think this is a bit clearer and lets us clean up some of the syntax a
bit (don't need to always say `filters <<`), and also it will let us
use `return`, which I'm interested in for my next change!
2024-02-27 14:43:42 -08:00
3781c9810a Item search can filter by fitting alt styles (but missing many details!)
A query like this works for finding items containing "hat" that fit
alt style #87296 (the Faerie Acara):

http://localhost:3000/items?q%5B0%5D%5Bkey%5D=name&q%5B0%5D%5Bvalue%5D=hat&q%5B1%5D%5Bkey%5D=fits&q%5B1%5D%5Bvalue%5D%5Bspecies_id%5D=1&q%5B1%5D%5Bvalue%5D%5Bcolor_id%5D=8&q%5B1%5D%5Bvalue%5D%5Balt_style_id%5D=87296

But there's two main missing pieces still:
1. You can't do a text-filter version of this—in fact, clicking Search
   immediately on the page this loads will return an error!
2. We haven't extended this to the `with_appearances_for` parameter,
   so we add a `NotImplementedError` to that bit for now too.

I'm also thinking that the text filter should ideally be like,
`fits:nostalgic-faerie-acara` if we can pull it off; and then fall back
to the plainer `fits:alt-style-87296` if e.g. we don't know the set
it's from yet.
2024-02-27 14:32:54 -08:00
183cb40e74 Oops, don't return body_id=0 items for "-fits:blue-acara"
Right, fitting isn't just body_id = this one, it's also body_id=0!

Anyway, doing this query on its own is still deathly slow, I wonder if
the idea I had about left joins (back when I was still working in a
Rails version that didn't support it lol) could help! Might poke at
that a smidge.
2024-02-27 14:07:20 -08:00
671a79d158 Refactor fits and not_fits in Item::Search::Query
Just restructuring a bit in anticipation of changes we're gonna make
for alt style support in here!
2024-02-27 14:05:37 -08:00
2e5b1c7350 Remove unused Item.per_page attribute
I feel like this was part of `will_paginate` back before the Rails
community had itself figured out about what belongs in a model?

But yeah, a default per-page value for search results does not belong
here. And I don't think anything references it anymore, because we pass
`per_page` to the `paginate` call in `ItemsController` explicitly! So,
goodbye!
2024-02-25 16:16:43 -08:00