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

Refresh token impl #2212

Merged
merged 37 commits into from
Aug 26, 2024
Merged

Refresh token impl #2212

merged 37 commits into from
Aug 26, 2024

Conversation

infomiho
Copy link
Contributor

@infomiho infomiho commented Jul 30, 2024

Closes #2124

What was done

  • Moved init of OAuth clients (google, github, discord and keycloak) to the SDK so users can also import them and use them
    • These clients are needed to refresh auth tokens
  • Move some of the OAuth helpers to SDK - because they are only used in the SDK or could be useful for users
    • The env helpers are only used in the SDK
    • The redirect and one-time token helpers can be useful if users implement some OAuth client themselves
  • Updated the OAuth tokens that we provide in auth hooks: we now provide all the data we get and not just the access token
    • We forward the data we receive so users will get the refresh token if we receive it

Left to do

  • Update docs
  • Fix provider name types

@infomiho infomiho marked this pull request as ready for review July 31, 2024 12:26
@infomiho infomiho requested a review from sodic August 2, 2024 11:45
Base automatically changed from miho-login-hooks to main August 8, 2024 16:28
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.

Nice work, just took a look at the docs for now to familiarize myself with the feature.

I'm now going for the code.

web/docs/auth/auth-hooks.md Outdated Show resolved Hide resolved
web/docs/auth/auth-hooks.md Outdated Show resolved Hide resolved
web/docs/auth/auth-hooks.md Outdated Show resolved Hide resolved
web/docs/auth/auth-hooks.md Outdated Show resolved Hide resolved
web/docs/auth/auth-hooks.md Outdated Show resolved Hide resolved
web/docs/auth/auth-hooks.md Outdated Show resolved Hide resolved
web/docs/auth/auth-hooks.md Outdated Show resolved Hide resolved
web/docs/auth/auth-hooks.md Outdated Show resolved Hide resolved
web/docs/auth/auth-hooks.md Outdated Show resolved Hide resolved
web/docs/auth/auth-hooks.md Outdated Show resolved Hide resolved
Signed-off-by: Mihovil Ilakovac <mihovil@ilakovac.com>
@infomiho infomiho requested a review from sodic August 20, 2024 12:51
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.

Nice work!

Left some suggestions, don't think I found anything major.

Please remember to change the public API table if you're changing the new API 😬

waspc/data/Generator/templates/sdk/wasp/package.json Outdated Show resolved Hide resolved
req: ExpressRequest
} & InternalAuthHookParams

// PRIVATE API (server)
Copy link
Contributor

Choose a reason for hiding this comment

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

Should be public though, right? According to the "public types" rule from Effective TypeScript.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Didn't we want to hide parts in the top level exports so the users don't get autocomplete suggestions that are not really used by them? Based on that I put "private API" since ideally, users wouldn't need to see this when using the SDK.

Copy link
Contributor

@sodic sodic Aug 23, 2024

Choose a reason for hiding this comment

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

Oh, you're refering to what we did with OnBeforeSignupHookParams and friends?

Ok, if you think this is one of those cases, we can keep it private, just add a note like we did there.

FWIW, I'm not sure this is one of thouse cases though.

The fact that we export many symbols isn't a problem by iteself - users will type up what they need and choose a quick action to import it. It only becomes a problem when the IDE suggests a niche export before a common one.

In the hooks params case, the parameter types had the same prefix as the function types. Since people are likely to use function types and unlikely to use params types, we decided to keep the params private to avoid these frustrating autocomplete/autoimport errors. But I don't think that's the case here.

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 should be a public API since the users can access it anyways through other types, yes? I'll make it public then 👍 It doesn't make sense to hide it and your other arguments make sense as well.

@@ -0,0 +1,50 @@
import { createJWT, validateJWT, TimeSpan } from '../../auth/jwt.js'

export const tokenStore = createTokenStore();
Copy link
Contributor

Choose a reason for hiding this comment

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

Any chance of avoiding a singleton module here?

Copy link
Contributor Author

@infomiho infomiho Aug 22, 2024

Choose a reason for hiding this comment

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

We need the one time tokens to allow the client to exchange one time codes for session IDs. If the server and client were on the same domain, it would be possible. This is not the case right now, so we need this kind of a singleton that keeps track of the tokens. This is not introduced in this PR btw. so I wouldn't change it in this PR irregardless.

At some point in the future, we'll need to figure out the cookies story so we can avoid this.

Copy link
Contributor

Choose a reason for hiding this comment

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

We need the one time tokens to allow the client to exchange one time codes for session IDs. If the server and client were on the same domain, it would be possible.

Oh, I understand we need the single instance that keeps the tokens. What I meant was "Can we not use a singleton pattern for it?"

This is not introduced in this PR btw. so I wouldn't change it in this PR irregardless.

It's perfectly fine not to do it now. FYI, here are two good posts I have bookmarked that will help us avoid singletons in the future (although I admit I sometimes break this rule myself):

Good thing we're not testing anything so the singletons aren't causing any problems 🥲

Feel free to resolve.

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's not a singleton in a sense that there can only exist only one instance of the object. It's true that we create a single instance that we have to use in both places where it's needed. In the case of unit tests, we could make the token store a param of the which ever fn we wanted to unit test.

waspc/src/Wasp/Generator/SdkGenerator/Server/OAuthG.hs Outdated Show resolved Hide resolved
waspc/src/Wasp/Generator/SdkGenerator/Server/OAuthG.hs Outdated Show resolved Hide resolved
web/docs/auth/auth-hooks.md Outdated Show resolved Hide resolved
web/docs/auth/auth-hooks.md Outdated Show resolved Hide resolved
@infomiho infomiho force-pushed the miho-refresh-token branch from 0ee588e to a7e8493 Compare August 22, 2024 13:01
@infomiho infomiho force-pushed the miho-refresh-token branch from 6170080 to 2df9b52 Compare August 22, 2024 13:41
@infomiho infomiho requested a review from sodic August 22, 2024 13:47
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.

Ok, resolved most of the stuff.

I left several more comments with suggestions I'd like, but it can definitely go out as is!

req: ExpressRequest
} & InternalAuthHookParams

// PRIVATE API (server)
Copy link
Contributor

@sodic sodic Aug 23, 2024

Choose a reason for hiding this comment

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

Oh, you're refering to what we did with OnBeforeSignupHookParams and friends?

Ok, if you think this is one of those cases, we can keep it private, just add a note like we did there.

FWIW, I'm not sure this is one of thouse cases though.

The fact that we export many symbols isn't a problem by iteself - users will type up what they need and choose a quick action to import it. It only becomes a problem when the IDE suggests a niche export before a common one.

In the hooks params case, the parameter types had the same prefix as the function types. Since people are likely to use function types and unlikely to use params types, we decided to keep the params private to avoid these frustrating autocomplete/autoimport errors. But I don't think that's the case here.


export function defineProvider<
OAuthClient extends OAuth2Provider | OAuth2ProviderWithPKCE,
Env extends Record<string, string>
Copy link
Contributor

Choose a reason for hiding this comment

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

@@ -0,0 +1,50 @@
import { createJWT, validateJWT, TimeSpan } from '../../auth/jwt.js'

export const tokenStore = createTokenStore();
Copy link
Contributor

Choose a reason for hiding this comment

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

We need the one time tokens to allow the client to exchange one time codes for session IDs. If the server and client were on the same domain, it would be possible.

Oh, I understand we need the single instance that keeps the tokens. What I meant was "Can we not use a singleton pattern for it?"

This is not introduced in this PR btw. so I wouldn't change it in this PR irregardless.

It's perfectly fine not to do it now. FYI, here are two good posts I have bookmarked that will help us avoid singletons in the future (although I admit I sometimes break this rule myself):

Good thing we're not testing anything so the singletons aren't causing any problems 🥲

Feel free to resolve.

waspc/src/Wasp/Generator/SdkGenerator/Server/OAuthG.hs Outdated Show resolved Hide resolved
displayName,
env,
oAuthClient,
})
Copy link
Contributor

Choose a reason for hiding this comment

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

Missing empty lines in some of these files.

Working without the linter on these templates really is a pain. It's all trivial stuff, but very annoying (for example, once you notice how inconsistent we are with the semicolons, you can't unsee it).

{=# enabledProviders.isGitHubAuthEnabled =}
// PUBLIC API
export { github } from './providers/github.js';
{=/ enabledProviders.isGitHubAuthEnabled =}
Copy link
Contributor

Choose a reason for hiding this comment

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

Another great change with scoping the providers flags under enabledProviders

@infomiho infomiho merged commit a6573c2 into main Aug 26, 2024
6 checks passed
@infomiho infomiho deleted the miho-refresh-token branch August 26, 2024 12:32
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.

Implement proper support for OAuth refresh tokens
2 participants