From ccc652ebfbbc47638519be0c41a55d4531721bbd Mon Sep 17 00:00:00 2001 From: Mihovil Ilakovac Date: Tue, 27 Aug 2024 14:39:06 +0200 Subject: [PATCH] Prepare 0.14.1 Wasp version (#2252) --- waspc/ChangeLog.md | 22 + .../waspBuild-golden/waspBuild/main.wasp | 2 +- .../waspCompile-golden/waspCompile/main.wasp | 2 +- .../waspComplexTest/main.wasp | 2 +- .../waspJob-golden/waspJob/main.wasp | 2 +- .../waspMigrate-golden/waspMigrate/main.wasp | 2 +- .../waspNew-golden/waspNew/main.wasp | 2 +- waspc/waspc.cabal | 2 +- web/docs/data-model/entities.md | 2 +- .../version-0.14.0/auth/auth-hooks.md | 544 +++++++++++++++--- .../version-0.14.0/auth/username-and-pass.md | 2 +- 11 files changed, 480 insertions(+), 104 deletions(-) diff --git a/waspc/ChangeLog.md b/waspc/ChangeLog.md index ff5c907542..3ee26f32fc 100644 --- a/waspc/ChangeLog.md +++ b/waspc/ChangeLog.md @@ -1,5 +1,27 @@ # Changelog +## 0.14.1 (2024-08-26) + +### 🎉 New Features + +- Wasp now supports `onBeforeLogin` and `onAfterLogin` auth hooks! You can use these hooks to run custom logic before and after a user logs in. For example, you can use the `onBeforeLogin` hook to check if the user is allowed to log in. +- OAuth refresh tokens are here. If the OAuth provider supports refresh tokens, you'll be able to use them to refresh the access token when it expires. This is useful for using OAuth provider APIs in the background e.g. accessing user's calendar events. + +### ⚠️ Breaking Changes + +- To make the API consistent across different auth hooks, we change how the `onBeforeOAuthRedirect` hook receives the `uniqueRequestId` value to `oauth.uniqueRequestId`. + +### 🐞 Bug fixes + +- Prisma file parser now allows using empty arrays as default values. + +### 🔧 Small improvements + +- Replace `oslo/password` with directly using `@node-rs/argon2` +- We now use `websocket` transport for the WebSocket client to avoid issues when deploying the server behind a load balancer. + +Community contributions by @rubyisrust @santolucito @sezercik @LLxD! + ## 0.14.0 (2024-07-17) ### 🎉 New Features diff --git a/waspc/e2e-test/test-outputs/waspBuild-golden/waspBuild/main.wasp b/waspc/e2e-test/test-outputs/waspBuild-golden/waspBuild/main.wasp index eb7c32e2d1..1d5e7fc6a1 100644 --- a/waspc/e2e-test/test-outputs/waspBuild-golden/waspBuild/main.wasp +++ b/waspc/e2e-test/test-outputs/waspBuild-golden/waspBuild/main.wasp @@ -1,6 +1,6 @@ app waspBuild { wasp: { - version: "^0.14.0" + version: "^0.14.1" }, title: "waspBuild" } diff --git a/waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/main.wasp b/waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/main.wasp index 925dc6c4fe..6e0a63a5de 100644 --- a/waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/main.wasp +++ b/waspc/e2e-test/test-outputs/waspCompile-golden/waspCompile/main.wasp @@ -1,6 +1,6 @@ app waspCompile { wasp: { - version: "^0.14.0" + version: "^0.14.1" }, title: "waspCompile" } diff --git a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/main.wasp b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/main.wasp index 91b9f55274..4148dd52a2 100644 --- a/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/main.wasp +++ b/waspc/e2e-test/test-outputs/waspComplexTest-golden/waspComplexTest/main.wasp @@ -1,6 +1,6 @@ app waspComplexTest { wasp: { - version: "^0.14.0" + version: "^0.14.1" }, auth: { userEntity: User, diff --git a/waspc/e2e-test/test-outputs/waspJob-golden/waspJob/main.wasp b/waspc/e2e-test/test-outputs/waspJob-golden/waspJob/main.wasp index 8e24bfcb6f..1deb5bca3a 100644 --- a/waspc/e2e-test/test-outputs/waspJob-golden/waspJob/main.wasp +++ b/waspc/e2e-test/test-outputs/waspJob-golden/waspJob/main.wasp @@ -1,6 +1,6 @@ app waspJob { wasp: { - version: "^0.14.0" + version: "^0.14.1" }, title: "waspJob" } diff --git a/waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/main.wasp b/waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/main.wasp index 79f0eb6743..d2692a3560 100644 --- a/waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/main.wasp +++ b/waspc/e2e-test/test-outputs/waspMigrate-golden/waspMigrate/main.wasp @@ -1,6 +1,6 @@ app waspMigrate { wasp: { - version: "^0.14.0" + version: "^0.14.1" }, title: "waspMigrate" } diff --git a/waspc/e2e-test/test-outputs/waspNew-golden/waspNew/main.wasp b/waspc/e2e-test/test-outputs/waspNew-golden/waspNew/main.wasp index 40033d108b..5ea5f45250 100644 --- a/waspc/e2e-test/test-outputs/waspNew-golden/waspNew/main.wasp +++ b/waspc/e2e-test/test-outputs/waspNew-golden/waspNew/main.wasp @@ -1,6 +1,6 @@ app waspNew { wasp: { - version: "^0.14.0" + version: "^0.14.1" }, title: "waspNew" } diff --git a/waspc/waspc.cabal b/waspc/waspc.cabal index 2515e921d0..1560cc70c6 100644 --- a/waspc/waspc.cabal +++ b/waspc/waspc.cabal @@ -6,7 +6,7 @@ cabal-version: 2.4 -- Consider using hpack, or maybe even hpack-dhall. name: waspc -version: 0.14.0 +version: 0.14.1 description: Please see the README on GitHub at homepage: https://github.com/wasp-lang/wasp/waspc#readme bug-reports: https://github.com/wasp-lang/wasp/issues diff --git a/web/docs/data-model/entities.md b/web/docs/data-model/entities.md index 77994e92fc..37b2b5a1c4 100644 --- a/web/docs/data-model/entities.md +++ b/web/docs/data-model/entities.md @@ -99,7 +99,7 @@ Entity types are available everywhere, including the client code: ```ts import { Task } from "wasp/entities" -export function ExamplePage() {} +export function ExamplePage() { const task: Task = { id: 123, description: "Some random task", diff --git a/web/versioned_docs/version-0.14.0/auth/auth-hooks.md b/web/versioned_docs/version-0.14.0/auth/auth-hooks.md index 95aa6b3e72..69937590ef 100644 --- a/web/versioned_docs/version-0.14.0/auth/auth-hooks.md +++ b/web/versioned_docs/version-0.14.0/auth/auth-hooks.md @@ -4,17 +4,21 @@ title: Auth Hooks import { EmailPill, UsernameAndPasswordPill, GithubPill, GooglePill, KeycloakPill, DiscordPill } from "./Pills"; import ImgWithCaption from '@site/blog/components/ImgWithCaption' +import { ShowForTs } from '@site/src/components/TsJsHelpers' Auth hooks allow you to "hook into" the auth process at various stages and run your custom code. For example, if you want to forbid certain emails from signing up, or if you wish to send a welcome email to the user after they sign up, auth hooks are the way to go. ## Supported hooks The following auth hooks are available in Wasp: + - [`onBeforeSignup`](#executing-code-before-the-user-signs-up) - [`onAfterSignup`](#executing-code-after-the-user-signs-up) - [`onBeforeOAuthRedirect`](#executing-code-before-the-oauth-redirect) +- [`onBeforeLogin`](#executing-code-before-the-user-logs-in) +- [`onAfterLogin`](#executing-code-after-the-user-logs-in) -We'll go through each of these hooks in detail. But first, let's see how the hooks fit into the signup flow: +We'll go through each of these hooks in detail. But first, let's see how the hooks fit into the auth flows: -If you are using OAuth, the flow includes extra steps before the signup flow: + + + + +\* When using the OAuth auth providers, the login hooks are both called before the session is created but the session is created quickly afterward, so it shouldn't make any difference in practice. + + +If you are using OAuth, the flow includes extra steps before the auth flow: @@ -69,9 +87,12 @@ app myApp { onBeforeSignup: import { onBeforeSignup } from "@src/auth/hooks", onAfterSignup: import { onAfterSignup } from "@src/auth/hooks", onBeforeOAuthRedirect: import { onBeforeOAuthRedirect } from "@src/auth/hooks", + onBeforeLogin: import { onBeforeLogin } from "@src/auth/hooks", + onAfterLogin: import { onAfterLogin } from "@src/auth/hooks", }, } ``` + @@ -105,11 +126,7 @@ app myApp { ```js title="src/auth/hooks.js" import { HttpError } from 'wasp/server' -export const onBeforeSignup = async ({ - providerId, - prisma, - req, -}) => { +export const onBeforeSignup = async ({ providerId, prisma, req }) => { const count = await prisma.user.count() console.log('number of users before', count) console.log('provider name', providerId.providerName) @@ -119,7 +136,10 @@ export const onBeforeSignup = async ({ throw new HttpError(403, 'Too many users') } - if (providerId.providerName === 'email' && providerId.providerUserId === 'some@email.com') { + if ( + providerId.providerName === 'email' && + providerId.providerUserId === 'some@email.com' + ) { throw new HttpError(403, 'This email is not allowed') } } @@ -156,7 +176,10 @@ export const onBeforeSignup: OnBeforeSignupHook = async ({ throw new HttpError(403, 'Too many users') } - if (providerId.providerName === 'email' && providerId.providerUserId === 'some@email.com') { + if ( + providerId.providerName === 'email' && + providerId.providerUserId === 'some@email.com' + ) { throw new HttpError(403, 'This email is not allowed') } } @@ -173,7 +196,7 @@ Wasp calls the `onAfterSignup` hook after the user is created. The `onAfterSignup` hook can be useful if you want to send the user a welcome email or perform some other action after the user signs up like syncing the user with a third-party service. -Since the `onAfterSignup` hook receives the OAuth access token, it can also be used to store the OAuth access token for the user in your database. +Since the `onAfterSignup` hook receives the OAuth tokens, you can use this hook to store the OAuth access token and/or [refresh token](#refreshing-the-oauth-access-token) in your database. Works with @@ -202,9 +225,9 @@ export const onAfterSignup = async ({ console.log('number of users after', count) console.log('user object', user) - // If this is an OAuth signup, we have the access token and uniqueRequestId + // If this is an OAuth signup, you have access to the OAuth tokens and the uniqueRequestId if (oauth) { - console.log('accessToken', oauth.accessToken) + console.log('accessToken', oauth.tokens.accessToken) console.log('uniqueRequestId', oauth.uniqueRequestId) const id = oauth.uniqueRequestId @@ -244,9 +267,9 @@ export const onAfterSignup: OnAfterSignupHook = async ({ console.log('number of users after', count) console.log('user object', user) - // If this is an OAuth signup, we have the access token and uniqueRequestId + // If this is an OAuth signup, you have access to the OAuth tokens and the uniqueRequestId if (oauth) { - console.log('accessToken', oauth.accessToken) + console.log('accessToken', oauth.tokens.accessToken) console.log('uniqueRequestId', oauth.uniqueRequestId) const id = oauth.uniqueRequestId @@ -268,7 +291,7 @@ Read more about the data the `onAfterSignup` hook receives in the [API Reference Wasp calls the `onBeforeOAuthRedirect` hook after the OAuth redirect URL is generated but before redirecting the user. This hook can access the request object sent from the client at the start of the OAuth process. -The `onBeforeOAuthRedirect` hook can be useful if you want to save some data (e.g. request query parameters) that can be used later in the OAuth flow. You can use the `uniqueRequestId` parameter to reference this data later in the `onAfterSignup` hook. +The `onBeforeOAuthRedirect` hook can be useful if you want to save some data (e.g. request query parameters) that you can use later in the OAuth flow. You can use the `uniqueRequestId` parameter to reference this data later in the `onAfterSignup` or `onAfterLogin` hooks. Works with @@ -286,16 +309,11 @@ app myApp { ``` ```js title="src/auth/hooks.js" -export const onBeforeOAuthRedirect = async ({ - url, - uniqueRequestId, - prisma, - req, -}) => { +export const onBeforeOAuthRedirect = async ({ url, oauth, prisma, req }) => { console.log('query params before oAuth redirect', req.query) - // Saving query params for later use in the onAfterSignup hook - const id = uniqueRequestId + // Saving query params for later use in onAfterSignup or onAfterLogin hooks + const id = oauth.uniqueRequestId someKindOfStore.set(id, req.query) return { url } @@ -320,14 +338,14 @@ import type { OnBeforeOAuthRedirectHook } from 'wasp/server/auth' export const onBeforeOAuthRedirect: OnBeforeOAuthRedirectHook = async ({ url, - uniqueRequestId, + oauth, prisma, req, }) => { console.log('query params before oAuth redirect', req.query) - // Saving query params for later use in the onAfterSignup hook - const id = uniqueRequestId + // Saving query params for later use in onAfterSignup or onAfterLogin hooks + const id = oauth.uniqueRequestId someKindOfStore.set(id, req.query) return { url } @@ -341,6 +359,222 @@ This hook's return value must be an object that looks like this: `{ url: URL }`. Read more about the data the `onBeforeOAuthRedirect` hook receives in the [API Reference](#the-onbeforeoauthredirect-hook). +### Executing code before the user logs in + +Wasp calls the `onBeforeLogin` hook before the user is logged in. + +The `onBeforeLogin` hook can be useful if you want to reject a user based on some criteria before they log in. + +Works with + + + + +```wasp title="main.wasp" +app myApp { + ... + auth: { + ... + onBeforeLogin: import { onBeforeLogin } from "@src/auth/hooks", + }, +} +``` + +```js title="src/auth/hooks.js" +import { HttpError } from 'wasp/server' + +export const onBeforeLogin = async ({ providerId, user, prisma, req }) => { + if ( + providerId.providerName === 'email' && + providerId.providerUserId === 'some@email.com' + ) { + throw new HttpError(403, 'You cannot log in with this email') + } +} +``` + + + + +```wasp title="main.wasp" +app myApp { + ... + auth: { + ... + onBeforeLogin: import { onBeforeLogin } from "@src/auth/hooks", + }, +} +``` + +```ts title="src/auth/hooks.ts" +import { HttpError } from 'wasp/server' +import type { OnBeforeLoginHook } from 'wasp/server/auth' + +export const onBeforeLogin: OnBeforeLoginHook = async ({ + providerId, + user, + prisma, + req, +}) => { + if ( + providerId.providerName === 'email' && + providerId.providerUserId === 'some@email.com' + ) { + throw new HttpError(403, 'You cannot log in with this email') + } +} +``` + + + + +Read more about the data the `onBeforeLogin` hook receives in the [API Reference](#the-onbeforelogin-hook). + +### Executing code after the user logs in + +Wasp calls the `onAfterLogin` hook after the user logs in. + +The `onAfterLogin` hook can be useful if you want to perform some action after the user logs in, like syncing the user with a third-party service. + +Since the `onAfterLogin` hook receives the OAuth tokens, you can use it to update the OAuth access token for the user in your database. You can also use it to [refresh the OAuth access token](#refreshing-the-oauth-access-token) if the provider supports it. + +Works with + + + + +```wasp title="main.wasp" +app myApp { + ... + auth: { + ... + onAfterLogin: import { onAfterLogin } from "@src/auth/hooks", + }, +} +``` + +```js title="src/auth/hooks.js" +export const onAfterLogin = async ({ + providerId, + user, + oauth, + prisma, + req, +}) => { + console.log('user object', user) + + // If this is an OAuth signup, you have access to the OAuth tokens and the uniqueRequestId + if (oauth) { + console.log('accessToken', oauth.tokens.accessToken) + console.log('uniqueRequestId', oauth.uniqueRequestId) + + const id = oauth.uniqueRequestId + const data = someKindOfStore.get(id) + if (data) { + console.log('saved data for the ID', data) + } + someKindOfStore.delete(id) + } +} +``` + + + + +```wasp title="main.wasp" +app myApp { + ... + auth: { + ... + onAfterLogin: import { onAfterLogin } from "@src/auth/hooks", + }, +} +``` + +```ts title="src/auth/hooks.ts" +import type { OnAfterLoginHook } from 'wasp/server/auth' + +export const onAfterLogin: OnAfterLoginHook = async ({ + providerId, + user, + oauth, + prisma, + req, +}) => { + console.log('user object', user) + + // If this is an OAuth signup, you have access to the OAuth tokens and the uniqueRequestId + if (oauth) { + console.log('accessToken', oauth.tokens.accessToken) + console.log('uniqueRequestId', oauth.uniqueRequestId) + + const id = oauth.uniqueRequestId + const data = someKindOfStore.get(id) + if (data) { + console.log('saved data for the ID', data) + } + someKindOfStore.delete(id) + } +} +``` + + + + +Read more about the data the `onAfterLogin` hook receives in the [API Reference](#the-onafterlogin-hook). + +### Refreshing the OAuth access token + +Some OAuth providers support refreshing the access token when it expires. To refresh the access token, you need the OAuth **refresh token**. + +Wasp exposes the OAuth refresh token in the `onAfterSignup` and `onAfterLogin` hooks. You can store the refresh token in your database and use it to refresh the access token when it expires. + +Import the provider object with the OAuth client from the `wasp/server/oauth` module. For example, to refresh the Google OAuth access token, import the `google` object from the `wasp/server/oauth` module. You use the `refreshAccessToken` method of the OAuth client to refresh the access token. + +Here's an example of how you can refresh the access token for Google OAuth: + + + + + +```js title="src/auth/hooks.js" +import { google } from 'wasp/server/oauth' + +export const onAfterLogin = async ({ oauth }) => { + if (oauth.provider === 'google' && oauth.tokens.refreshToken !== null) { + const newTokens = await google.oAuthClient.refreshAccessToken( + oauth.tokens.refreshToken + ) + log('new tokens', newTokens) + } +} +``` + + + + + +```ts title="src/auth/hooks.ts" +import type { OnAfterLoginHook } from 'wasp/server/auth' +import { google } from 'wasp/server/oauth' + +export const onAfterLogin: OnAfterLoginHook = async ({ oauth }) => { + if (oauth.provider === 'google' && oauth.tokens.refreshToken !== null) { + const newTokens = await google.oAuthClient.refreshAccessToken( + oauth.tokens.refreshToken + ) + log('new tokens', newTokens) + } +} +``` + + + + +Google exposes the `accessTokenExpiresAt` field in the `oauth.tokens` object. You can use this field to determine when the access token expires. + +If you want to refresh the token periodically, use a [Wasp Job](../advanced/jobs.md). + ## API Reference @@ -359,9 +593,12 @@ app myApp { onBeforeSignup: import { onBeforeSignup } from "@src/auth/hooks", onAfterSignup: import { onAfterSignup } from "@src/auth/hooks", onBeforeOAuthRedirect: import { onBeforeOAuthRedirect } from "@src/auth/hooks", + onBeforeLogin: import { onBeforeLogin } from "@src/auth/hooks", + onAfterLogin: import { onAfterLogin } from "@src/auth/hooks", }, } ``` + @@ -378,26 +615,35 @@ app myApp { onBeforeSignup: import { onBeforeSignup } from "@src/auth/hooks", onAfterSignup: import { onAfterSignup } from "@src/auth/hooks", onBeforeOAuthRedirect: import { onBeforeOAuthRedirect } from "@src/auth/hooks", + onBeforeLogin: import { onBeforeLogin } from "@src/auth/hooks", + onAfterLogin: import { onAfterLogin } from "@src/auth/hooks", }, } ``` + +### Common hook input + +The following properties are available in all auth hooks: + +- `prisma: PrismaClient` + + The Prisma client instance which you can use to query your database. + +- `req: Request` + + The [Express request object](https://expressjs.com/en/api.html#req) from which you can access the request headers, cookies, etc. + ### The `onBeforeSignup` hook ```js title="src/auth/hooks.js" -import { HttpError } from 'wasp/server' - -export const onBeforeSignup = async ({ - providerId, - prisma, - req, -}) => { - // Hook code here +export const onBeforeSignup = async ({ providerId, prisma, req }) => { + // Hook code goes here } ``` @@ -405,7 +651,6 @@ export const onBeforeSignup = async ({ ```ts title="src/auth/hooks.ts" -import { HttpError } from 'wasp/server' import type { OnBeforeSignupHook } from 'wasp/server/auth' export const onBeforeSignup: OnBeforeSignupHook = async ({ @@ -413,7 +658,7 @@ export const onBeforeSignup: OnBeforeSignupHook = async ({ prisma, req, }) => { - // Hook code here + // Hook code goes here } ``` @@ -422,21 +667,9 @@ export const onBeforeSignup: OnBeforeSignupHook = async ({ The hook receives an object as **input** with the following properties: -- `providerId: ProviderId` - - The user's provider ID is an object with two properties: - - `providerName: string` - - The provider's name (e.g. `'email'`, `'google'`, `'github'`) - - `providerUserId: string` - - The user's unique ID in the provider's system (e.g. email, Google ID, GitHub ID) -- `prisma: PrismaClient` - - The Prisma client instance which you can use to query your database. -- `req: Request` +- [`providerId: ProviderId`](#providerid-fields) - The [Express request object](https://expressjs.com/en/api.html#req) from which you can access the request headers, cookies, etc. +- Plus the [common hook input](#common-hook-input) Wasp ignores this hook's **return value**. @@ -453,7 +686,7 @@ export const onAfterSignup = async ({ prisma, req, }) => { - // Hook code here + // Hook code goes here } ``` @@ -470,7 +703,7 @@ export const onAfterSignup: OnAfterSignupHook = async ({ prisma, req, }) => { - // Hook code here + // Hook code goes here } ``` @@ -478,36 +711,15 @@ export const onAfterSignup: OnAfterSignupHook = async ({ The hook receives an object as **input** with the following properties: -- `providerId: ProviderId` - - The user's provider ID is an object with two properties: - - `providerName: string` - - The provider's name (e.g. `'email'`, `'google'`, `'github'`) - - `providerUserId: string` - - The user's unique ID in the provider's system (e.g. email, Google ID, GitHub ID) + +- [`providerId: ProviderId`](#providerid-fields) - `user: User` - - The user object that was created. -- `oauth?: OAuthFields` - This object is present only when the user is created using [Social Auth](./social-auth/overview.md). - It contains the following fields: - - `accessToken: string` + The user object that was created. - You can use the OAuth access token to use the provider's API on user's behalf. - - `uniqueRequestId: string` - - The unique request ID for the OAuth flow (you might know it as the `state` parameter in OAuth.) - - You can use the unique request ID to get the data saved in the `onBeforeOAuthRedirect` hook. -- `prisma: PrismaClient` +- [`oauth?: OAuthFields`](#oauth-fields) - The Prisma client instance which you can use to query your database. -- `req: Request` - - The [Express request object](https://expressjs.com/en/api.html#req) from which you can access the request headers, cookies, etc. +- Plus the [common hook input](#common-hook-input) Wasp ignores this hook's **return value**. @@ -517,13 +729,8 @@ Wasp ignores this hook's **return value**. ```js title="src/auth/hooks.js" -export const onBeforeOAuthRedirect = async ({ - url, - uniqueRequestId, - prisma, - req, -}) => { - // Hook code here +export const onBeforeOAuthRedirect = async ({ url, oauth, prisma, req }) => { + // Hook code goes here return { url } } @@ -537,11 +744,11 @@ import type { OnBeforeOAuthRedirectHook } from 'wasp/server/auth' export const onBeforeOAuthRedirect: OnBeforeOAuthRedirectHook = async ({ url, - uniqueRequestId, + oauth, prisma, req, }) => { - // Hook code here + // Hook code goes here return { url } } @@ -551,19 +758,166 @@ export const onBeforeOAuthRedirect: OnBeforeOAuthRedirectHook = async ({ The hook receives an object as **input** with the following properties: + - `url: URL` - Wasp uses the URL for the OAuth redirect. -- `uniqueRequestId: string` + Wasp uses the URL for the OAuth redirect. + +- `oauth: { uniqueRequestId: string }` + + The `oauth` object has the following fields: + + - `uniqueRequestId: string` The unique request ID for the OAuth flow (you might know it as the `state` parameter in OAuth.) - You can use the unique request ID to save data (e.g. request query params) that you can later use in the `onAfterSignup` hook. -- `prisma: PrismaClient` - - The Prisma client instance which you can use to query your database. -- `req: Request` + You can use the unique request ID to save data (e.g. request query params) that you can later use in the `onAfterSignup` or `onAfterLogin` hooks. - The [Express request object](https://expressjs.com/en/api.html#req) from which you can access the request headers, cookies, etc. +- Plus the [common hook input](#common-hook-input) This hook's return value must be an object that looks like this: `{ url: URL }`. Wasp uses the URL to redirect the user to the OAuth provider. + +### The `onBeforeLogin` hook + + + + +```js title="src/auth/hooks.js" +export const onBeforeLogin = async ({ providerId, prisma, req }) => { + // Hook code goes here +} +``` + + + + +```ts title="src/auth/hooks.ts" +import type { OnBeforeLoginHook } from 'wasp/server/auth' + +export const onBeforeLogin: OnBeforeLoginHook = async ({ + providerId, + prisma, + req, +}) => { + // Hook code goes here +} +``` + + + + +The hook receives an object as **input** with the following properties: + +- [`providerId: ProviderId`](#providerid-fields) + +- `user: User` + + The user that is trying to log in. + +- Plus the [common hook input](#common-hook-input) + +Wasp ignores this hook's **return value**. + +### The `onAfterLogin` hook + + + + + +```js title="src/auth/hooks.js" +export const onAfterLogin = async ({ + providerId, + user, + oauth, + prisma, + req, +}) => { + // Hook code goes here +} +``` + + + + +```ts title="src/auth/hooks.ts" +import type { OnAfterLoginHook } from 'wasp/server/auth' + +export const onAfterLogin: OnAfterLoginHook = async ({ + providerId, + user, + oauth, + prisma, + req, +}) => { + // Hook code goes here +} +``` + + + + +The hook receives an object as **input** with the following properties: + +- [`providerId: ProviderId`](#providerid-fields) + +- `user: User` + + The logged-in user's object. + +- [`oauth?: OAuthFields`](#oauth-fields) + +- Plus the [common hook input](#common-hook-input) + +Wasp ignores this hook's **return value**. + +### ProviderId fields + +The `providerId` object represents the user for the current authentication method. Wasp passes it to the `onBeforeSignup`, `onAfterSignup`, `onBeforeLogin`, and `onAfterLogin` hooks. + +It has the following fields: + +- `providerName: string` + + The provider's name (e.g. `'email'`, `'google'`, `'github`) + +- `providerUserId: string` + + The user's unique ID in the provider's system (e.g. email, Google ID, GitHub ID) + +### OAuth fields + +Wasp passes the `oauth` object to the `onAfterSignup` and `onAfterLogin` hooks only when the user is authenticated with [Social Auth](./social-auth/overview.md). + +It has the following fields: + +- `providerName: string` + + The name of the OAuth provider the user authenticated with (e.g. `'google'`, `'github'`). + +- `tokens: Tokens` + + You can use the OAuth tokens to make requests to the provider's API on the user's behalf. + + Depending on the OAuth provider, the `tokens` object might have different fields. For example, Google has the fields `accessToken`, `refreshToken`, `idToken`, and `accessTokenExpiresAt`. + + + + To access the provider-specific fields, you must first narrow down the `oauth.tokens` object type to the specific OAuth provider type. + + ```ts + if (oauth && oauth.providerName === 'google') { + console.log(oauth.tokens.accessToken) + // ^ Google specific tokens are available here + console.log(oauth.tokens.refreshToken) + console.log(oauth.tokens.idToken) + console.log(oauth.tokens.accessTokenExpiresAt) + } + ``` + + + +- `uniqueRequestId: string` + + The unique request ID for the OAuth flow (you might know it as the `state` parameter in OAuth.) + + You can use the unique request ID to get the data that was saved in the `onBeforeOAuthRedirect` hook. diff --git a/web/versioned_docs/version-0.14.0/auth/username-and-pass.md b/web/versioned_docs/version-0.14.0/auth/username-and-pass.md index 9a731b9e40..5418f66e1a 100644 --- a/web/versioned_docs/version-0.14.0/auth/username-and-pass.md +++ b/web/versioned_docs/version-0.14.0/auth/username-and-pass.md @@ -522,7 +522,7 @@ export const signup = async (args, _context) => { // ... action customSignup { - fn: import { signup } from "@src/auth/signup.js", + fn: import { signup } from "@src/auth/signup", } ```