Commit Graph

1723 Commits

Author SHA1 Message Date
Emi Matchu 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
Emi Matchu 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
Emi Matchu 9384fd2aa7 Add additional cookie method to view hidden NeoPass features
This is gonna help me in development, to stop having to add stuff to
the URL all the time!! I also considered just always making it
available in development, but I wanted to match production behavior to
help us ensure the hiding behavior is working, to avoid leaking NeoPass
without realizing.
2024-04-09 06:36:44 -07:00
Emi Matchu 95c1a4f391 Fix bugs in Settings page when changes to the model are incomplete
Ahh okay tricky lil thing: if you show the settings page with a partial
change to `AuthUser` that didn't get saved, it can throw off the state
of some stuff. For example, if you don't have a password yet, then
enter a new password but leave the confirmation box blank, then you'll
correctly see "Password confirmation can't be blank", but you'll *also*
then be prompted for your "Current password", even though you don't
have one yet, because `@auth_user.uses_password?` is true now.

In this change, we extend the Settings form to use two copies of the
`AuthUser`. One is the copy with changes on it, and the other is the
"persisted" copy, which we check for parts of the UI that care about
what's actually saved, vs form state.
2024-04-09 06:34:06 -07:00
Emi Matchu 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
Emi Matchu d10c11e261 Oops, fix tracking `neopass_email` on account creation.
My bad!
2024-04-09 05:45:39 -07:00
Emi Matchu 0a046ed9c1 Oh right, hide NeoPass on settings page unless you set the magic param!
Simplified this a bit into a helper. It's kinda odd to me, but
convenient for this moment, that Rails allows views to read `params`! I
guess it's for escape hatches exactly like this! lol
2024-04-08 05:34:47 -07:00
Emi Matchu 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
Emi Matchu 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
Emi Matchu 889c454c65 Oops, fix a redirect URL I missed when ejecting from Devise controller 2024-04-08 04:32:34 -07:00
Emi Matchu f6d3992045 Don't require `current_password` for settings if user doesn't have one 2024-04-08 04:13:07 -07:00
Emi Matchu 0f5bb2a861 Oops, stay signed in when changing password 2024-04-08 04:12:46 -07:00
Emi Matchu ae2b62956a Eject AuthUsersController from the default Devise controller
I'm getting ready to add handling for "what if you don't *have* a
current password*??", so it seems like the right way to do that is to
just eject the controller and start customizing!
2024-04-08 04:02:54 -07:00
Emi Matchu 3e92d89765 Fix error when multiple accounts have a blank email address 2024-04-08 03:46:41 -07:00
Emi Matchu 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
Emi Matchu b5e203c0e5 Oops, fix settings page styles when validation fails
Ahh right, when I fixed this for Turbo, I forgot this page can also
render in the `update` action when it fails!
2024-04-07 08:32:38 -07:00
Emi Matchu 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
Emi Matchu 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
Emi Matchu 89fc99c918 Oops, fix bug for authorizing the NeoPass disconnect endpoint
Ahh right, in development `User` and `AuthUser` will have the same ID,
but that got messed up early on for us in production DTI 😅

Here, we switch the form to reference the `User` instead of the
`AuthUser` (to get the ID right), then we also change how we compare
the IDs, because `User#to_param` appends extra text onto the ID after
the number!
2024-04-07 08:11:22 -07:00
Emi Matchu 66978bf5a0 Oops, fix Settings page styles spreading to other pages via Turbo
Oh right, I made this mistake before too, lol! Once stylesheets are
added, they don't go away!
2024-04-07 08:04:32 -07:00
Emi Matchu 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
Emi Matchu 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
Emi Matchu 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
Emi Matchu 82aea20679 Redesign user settings form
Motivation is that I wanna add NeoPass stuff to here! But also like,
it's looked bad for a long time, let's clean it up!! (I just used the
Devise default without any styling at all lol)
2024-04-07 06:43:29 -07:00
Emi Matchu 8e269df3c1 Oh wow, don't use the images.neopets.com asset proxy anymore either!
Huh, I was writing up an API inventory doc to send to TNT, and was
gonna explain why we proxy these assets… but turns out we don't need to
anymore! Nice!

This is a bit fragile if they ever change their headers, so I'll
mention that in the doc, but for now, yeah sure let's save the planet
some computational effort!
2024-04-06 03:38:23 -07:00
Emi Matchu bb90f92a06 Don't use the pets.neopets.com asset proxy anymore
Ah right, now that HTTPS works, we can skip this!
2024-04-06 03:25:06 -07:00
Emi Matchu 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
Emi Matchu 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
Emi Matchu ebc01518bd Remove unused Pet::WARDROBE_PATH constant
Huh, weird! Goodbye!
2024-04-06 02:38:20 -07:00
Emi Matchu 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
Emi Matchu 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
Emi Matchu 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
Emi Matchu 3419f8b8d1 Tweak NeoPass login success messages, to focus less on random username
Until we have more figured out about the username situation here, let's
not greet the user by the name we *generated* for them.
2024-04-01 06:00:49 -07:00
Emi Matchu 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
Emi Matchu 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
Emi Matchu 2e3cfd7cd1 Add development tooling to use live NeoPass, kinda
Hacky and inconvenient, but it works!

I want this primarily to enable me to live-debug what info we're
getting back in the auth token. In production right now, the flow with
NeoPass succeeds, but we fail to create the account, and my production
error logs say it's because the username field is too long. I had hoped
it would just be the Neopets username, but now that I've poked at
NeoPass itself a bit, I'm realizing it won't be that simple.

So, we'll use this to investigate!
2024-04-01 05:26:00 -07:00
Emi Matchu e2d763e3c3 Fix NeoPass access token request to use POST data instead of Auth header
Ahh okay, the NeoPass spec says to pass the `client_id` and
`client_secret` as POST form data when exchanging the code for the
access token, but the default behavior of our client is to pass it as
an `Authorization` header instead.

In this change, we set an option to change that behavior, and also add
a lot of comments about this and the other options!
2024-04-01 05:08:47 -07:00
Emi Matchu 08986153df Add our client ID and client secret, to connect to NeoPass for real!
Wowie, it's starting to happen! :3

When you run this in production, though, you get back the auth failure
message, and the OmniAuth logs say the server returned the following:

> invalid_client: Client authentication failed (e.g., unknown client,
> no client authentication included, or unsupported authentication
> method). The OAuth 2.0 Client supports client authentication method
> 'client_secret_post', but method 'client_secret_basic' was requested.
> You must configure the OAuth 2.0 client's
> 'token_endpoint_auth_method' value to accept 'client_secret_basic'.

I'll add a fix for this in the next commit, with some explanations as
to why!
2024-04-01 04:55:42 -07:00
Emi Matchu fcc17d3dcf Whoops, fix some style regressions for the React app!
Ah beans, I didn't notice when doing my Turbo fixes in
40804c1543, that I had accidentally
prevented Chakra from applying some of its usual global styles! This
caused some minor visual regressions in various parts of the app, e.g.,
the default border color for the search field in the wardrobe app
became way darker.

Here, instead of copy-pasting the styles and missing some parts, we
scope the global styles a bit more carefully: we first use
`extendTheme` with the same code as Impress 2020 to get a good
`globalStyles` function that includes Chakra's defaults, *then* delete
the key from the theme.

Then, in `ScopedCSSReset`, we use code similar to Chakra's own global
style application code: call the `globalStyles` function with the
current theme and color mode, use Chakra's `css` function to convert
values like `green.800` to CSS values, prepend our scoping rule onto
the selectors, and drop it into our Emotion CSS.

Tbh I was worried because when I first noticed this issue while on my
trip, I saw the black item search box border, and was like "ah dang,
did I destroy all the color in the app by breaking the part where
Chakra defines its CSS color variables??" And no, that's not actually
what happened, a lot of the app still had its colors!

So this was less pressing than I had thought, but still good to get
fixed!
2024-03-31 01:20:45 -07:00
Emi Matchu 7d18da01d8 Update schema.rb for ID column limit increase
Commit 07617fa34f was an emergency commit
while I was on a trip, away from my usual workstation! I was able to
write the migration and run it against production, but I didn't have
the dev server fully set up, so I wasn't able to run the migration in
development, which is when Rails updates `schema.rb`.

Now I'm home and can run it, easy peasy! `rails db:migrate`
2024-03-30 23:38:04 -07:00
Emi Matchu 92f6cb189f Add /privacy route
I don't link to this in the footer or anything yet, but TNT asked for
it as part of the NeoPass setup, so I currently just redirect to the
outdated Impress 2020 privacy policy. (It's outdated in the sense that
we share *less* data with our third party services now, e.g. we moved
our analytics and error tracking onto our own machines!)
2024-03-30 23:36:30 -07:00
Emi Matchu 07617fa34f Increase ID column limit for item_outfit_relationships
Ahh wow, we're finally at the point of having enough outfits to need to
increase this ID field!

I got an alert this morning that queries were failing with the MySQL error
"id out of range". I went and found that creating a new outfit still works if
it's empty, but fails once you try to add an item to it. So, I assume this is
the failing column!

Note that I'm writing this from my emergency Steam Deck workstation, and that
I didn't quite get MySQL set up on it yet (I just yolo ran this in
production), so we don't have the corresponding changes to `db/schema.rb` yet.
This might require another commit from my normal workstation later to
complete this change!
2024-03-24 11:09:40 +11:00
Emi Matchu 812700248e Update Devise paths to be at /users instead of /auth_users
Been bothering me for a bit, and now I'm about to submit my official
redirect URL to the Neopets eng team so, let's polish this up!
2024-03-14 20:34:05 -07:00
Emi Matchu f4133f8283 Add some cheesy formatting to the placeholder NeoPass UI
Just to make it fun and cute! I'm doing some other stuff for demo video
purposes, but this is the only one I'm committing :p
2024-03-14 19:47:19 -07:00
Emi Matchu 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
Emi Matchu 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
Emi Matchu 31a11a04fa Read and customize the username reported by `neopass-server`
Okay, `sub` seems to be a pretty standard place for user identifiers.
Let's start with that assumption! I override the `oauth2-mock-server`'s
default of `johndoe` with `theneopetsteam`, just to be cute :3
2024-03-14 18:19:45 -07:00
Emi Matchu 9cbeee0acd Refactor to use OpenID Connect OmniAuth gem instead of plain OAuth2
Right, I didn't totally connect the dots that there's some OpenID
features in the mix here for how we expect to identify the user once
they authenticate. It requires looking up the provider's public key,
and validating the JWT they sent us. This gem does all that for us!

I don't actually know what a real NeoPass `id_token` looks like yet?
But I'll fill in some placeholder stuff for now, and use that for
initializing the account!
2024-03-14 18:11:40 -07:00
Emi Matchu ffcfce2eb8 Use local-only HTTPS certs for the development neopass-server
I'm starting to work with the OpenID Connect stuff in NeoPass, and the
library I'm using for discovery doesn't seem to want to do it over a
plain `http://` connection. (I dug into the source files, and it just
actually is hardcoded to only work with HTTPS, as far as I can tell?)

So, I've added logic to `neopass-server` to try to make an HTTPS
certificate with the `mkcert` utility (if installed), which is a tool
that installs a root certificate authority on your local machine, then
helps you create certificates via that authority that will work only on
your local machine.

I think I'll also be able to remove the "main" server in front of the
backing server, because the library I'm using now will be able to
"discover" the auth and token endpoints, so it won't matter that our
local one uses different URLs than live NeoPass does? We'll find out!

I also remove `neopass-server` from the `Procfile`, because I think
it's a bit rude to have it auto-try to run `mkcert`. We could like,
make the process just a no-op in that case? But I think I'd prefer to
just run `neopass-server` by hand when I want it, for simplicity.
2024-03-14 18:01:54 -07:00
Emi Matchu 21bc4bcadc Fix `neopass-server` to respond correctly to `/token` requests
Hey nice, now we can actually get a round-trip auth success! This gets
us to the `Devise::OmniauthCallbacksController#neopass` success method,
so now we need to add our logic to actually login/signup!
2024-03-14 16:26:43 -07:00