Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update AuthUser to offer a better DX #1915

Merged
merged 18 commits into from
Apr 22, 2024
Merged

Update AuthUser to offer a better DX #1915

merged 18 commits into from
Apr 22, 2024

Conversation

infomiho
Copy link
Contributor

@infomiho infomiho commented Mar 21, 2024

Note

This is probably need to be a breaking change to make the API clean.

@@ -88,20 +87,5 @@ type Context<Entities extends _Entity[]> = Expand<{
{=# isAuthEnabled =}
type ContextWithUser<Entities extends _Entity[]> = Expand<Context<Entities> & { user?: AuthUser }>

// TODO: This type must match the logic in auth/session.js (if we remove the
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This TODO is now resolved yay! Since the type and the runtime value are based on the same logic.

@@ -43,3 +46,13 @@ emailAuthProvider =
{ E._providerId = fromJust $ makeProviderId "email",
E._displayName = "Email and password"
}

getEnabledAuthProvidersJson :: AS.Auth.Auth -> Aeson.Value
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generating this JSON in one place instead of three.

) {
const { auth, ...rest } = user
const identities = {
{=# enabledProviders.isEmailAuthEnabled =}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generating fields based on the enabled auth methods.

Copy link
Member

@Martinsos Martinsos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Took a quick look, looks good in general, left some comments!
But I will let @sodic do a proper review.

How would you describe the crux of this PR regarding its impact on the user? getFirstProviderId is now method on user. You also have createAuthUser which helps with something from what I saw. Something else? This is only part of the Auth DX improvements right?

return {
...rest,
identities,
getFirstProviderUserId: () => getFirstProviderUserId(user),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aha, so this is now method on the user!

import express from 'express'

export const fooBar: FooBar = (_req, res, context) => {
const username = getFirstProviderUserId(context?.user) ?? 'Anonymous'
const username = context.user?.getFirstProviderUserId()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok cool this is nicer now!
What does getFirstProviderUserId promise? If I have multiple providers, it will return id from one of them. And it will be consistent in the order, right? What if I add a new provider? Maybe this ID will become different, right? That might be ok,I am just curious.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will return the first available provider :D the order might change with if you add a new provider.

I predict that this method will be mostly used by people who only have one provider anyways or just want to display something identifying the user. For proper "production usage" I suspect people will want to put some sort of an "email" or "username" on the User object anyways.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I agree! We should make sure to mention this in the docs of this function, to make it clear how it is intended to be used, and what the pitfalls might be, and why they should therefore potentially put stuff on User instead. I don't think this stuff will be clear to a new Wasp user.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mentioned putting stuff on User here: https://github.com/wasp-lang/wasp/pull/1915/files#diff-1da5418e699dcbebea8ab516c4e60235efd7191abad17057916ca5f9bba2ddb1R263

But I didn't comment on the pitfalls of the getFirstProviderUserId method.

waspc/examples/todoApp/src/pages/ProfilePage.tsx Outdated Show resolved Hide resolved
Copy link
Contributor

@sodic sodic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a big fan of this change!

Left a couple of comments to discuss.

@@ -3,7 +3,7 @@ import { deserialize as superjsonDeserialize } from 'superjson'
import { useQuery, addMetadataToQuery } from 'wasp/client/operations'
import { api, handleApiError } from 'wasp/client/api'
import { HttpMethod } from 'wasp/client'
import type { AuthUser } from './types'
import type { AuthUser } from 'wasp/auth'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're phasing out package imports in the SDK (as of very recently 😄), so let's not add new ones:

Context: #1922

}

return newUser
return createAuthUser(newUser as any)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is something the users will have to do, I assume?

If that's the case, can we avoid the assertion?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to get this code to work, I wanted to make it work without an assertion but it proved difficult. I'll give it an another try.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've now hidden this API since it would expose some server imports on the client. Now this test is written a bit differently with the {} as AuthUser method which works well for these kinds of tests.

waspc/examples/todoApp/src/pages/ProfilePage.tsx Outdated Show resolved Hide resolved
@@ -8,7 +7,7 @@ export const webSocketFn: WebSocketDefinition<
InterServerEvents
> = (io, context) => {
io.on('connection', (socket) => {
const username = getFirstProviderUserId(socket.data.user) ?? 'Unknown'
const username = socket.data.user?.getFirstProviderUserId() ?? 'Unknown'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How will we decide whether we want methods or getters? Is there a rule of thumb in the JS world?

Getters are nicer, and look less bureaucratic, but if some contain arguments, then the API looks janky.
Also, what if one of the getters that doesn't need any arguments today ends up needing arguments in the future?

Then again, getters are nicer and look less like java bureaucratic.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was using a rule of thumb: if it's a property that they would be able to get just by going through the raw DB entity, I put a getter. If it's something special like getting the first available provider, I used a function.

The options seem to be:

  • user.getIdentities().email
  • user.identities.email
  • user.firstProviderId
  • user.getFirstProviderId()

The combination felt okay to me, as user.firstProviderId feels off since the jobs of getting the first provider id feels dynamic.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I like this logic, but I do have to admit I am also not quite used to using JS getters, so I can imagine somebody possibly convincing me the other way. But with knowledge I have, I like it.

Comment on lines +13 to +14
if (user.identities.google !== null) {
return `Google user ${user.identities.google.id}`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Much better!

@infomiho infomiho marked this pull request as ready for review April 4, 2024 11:17
@infomiho infomiho requested review from Martinsos and sodic April 9, 2024 10:05
@infomiho
Copy link
Contributor Author

infomiho commented Apr 9, 2024

Docs are ready for review. I've tried to rewrite them to fit the new API and explain some extra stuff.

Copy link
Member

@Martinsos Martinsos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@infomiho I gave it a look, looks fine in general! Seems like there was no big structural changes, you mostly covered the change in the API + added the migration instructions?
It can be a bit hard to review docs sometimes, even harder than code, because in code I can really on Typescript / Haskell to know that stuff is vibing together, and I can somehow easier get a bigger picture, in docs it is a bit harder to figure out what was supposed to change, what is the context of the rest of the docs, ... . What might be helpful when doing docs change like this is if you wrote a tiny bit about what you changed, from the very high level, to give me an idea of what I am looking at, what should I focus on. You did that somewhat "rewrite ... new API ... explain extra stuff", so maybe that is it, not sure, but for example I would to hear if you want me to look at something specific, maybe what is that new stuff, ... .

web/docs/auth/_accessing-user-data-note.md Outdated Show resolved Hide resolved
web/docs/auth/entities/_email-data.md Outdated Show resolved Hide resolved
web/docs/auth/entities/_email-data.md Outdated Show resolved Hide resolved
web/docs/auth/entities/_email-data.md Outdated Show resolved Hide resolved
web/docs/auth/entities/_email-data.md Outdated Show resolved Hide resolved
web/docs/migrate-from-0-13-to-0-14.md Outdated Show resolved Hide resolved
web/docs/migrate-from-0-13-to-0-14.md Outdated Show resolved Hide resolved
web/docs/migrate-from-0-13-to-0-14.md Outdated Show resolved Hide resolved
web/docs/migrate-from-0-13-to-0-14.md Outdated Show resolved Hide resolved
web/docs/migrate-from-0-13-to-0-14.md Show resolved Hide resolved
Copy link
Member

@Martinsos Martinsos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@infomiho responded to comments!

@infomiho infomiho requested a review from Martinsos April 22, 2024 15:31
@infomiho infomiho merged commit 0e70547 into main Apr 22, 2024
6 checks passed
@infomiho infomiho deleted the update-auth-user-api branch April 22, 2024 15:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants