From e2033adbe8ec74e894e3c81073f3e606bd9c496b Mon Sep 17 00:00:00 2001 From: Rob Cameron Date: Mon, 20 Jun 2022 15:06:55 -0700 Subject: [PATCH 01/17] Reorganize auth docs into sub-categories (#5787) * Reorganize auth docs into sub-categories * Backport auth doc changes to 2.0 --- docs/docs/auth/auth0.md | 264 +++ docs/docs/auth/azure.md | 97 ++ docs/docs/auth/clerk.md | 75 + docs/docs/auth/custom.md | 23 + docs/docs/auth/dbauth.md | 220 +++ docs/docs/auth/firebase.md | 216 +++ docs/docs/auth/gotrue.md | 61 + docs/docs/auth/magic-link.md | 107 ++ docs/docs/auth/netlify.md | 88 + docs/docs/auth/nhost.md | 47 + docs/docs/auth/supabase.md | 60 + docs/docs/auth/wallet-connect.md | 17 + docs/docs/authentication.md | 1474 +---------------- docs/sidebars.js | 23 +- docs/versioned_docs/version-2.0/auth/auth0.md | 264 +++ docs/versioned_docs/version-2.0/auth/azure.md | 97 ++ docs/versioned_docs/version-2.0/auth/clerk.md | 75 + .../versioned_docs/version-2.0/auth/custom.md | 23 + .../versioned_docs/version-2.0/auth/dbauth.md | 220 +++ .../version-2.0/auth/firebase.md | 216 +++ .../versioned_docs/version-2.0/auth/gotrue.md | 61 + .../version-2.0/auth/magic-link.md | 107 ++ .../version-2.0/auth/netlify.md | 88 + docs/versioned_docs/version-2.0/auth/nhost.md | 47 + .../version-2.0/auth/supabase.md | 60 + .../version-2.0/auth/wallet-connect.md | 17 + .../version-2.0/authentication.md | 1474 +---------------- .../version-2.0-sidebars.json | 27 +- 28 files changed, 2765 insertions(+), 2783 deletions(-) create mode 100644 docs/docs/auth/auth0.md create mode 100644 docs/docs/auth/azure.md create mode 100644 docs/docs/auth/clerk.md create mode 100644 docs/docs/auth/custom.md create mode 100644 docs/docs/auth/dbauth.md create mode 100644 docs/docs/auth/firebase.md create mode 100644 docs/docs/auth/gotrue.md create mode 100644 docs/docs/auth/magic-link.md create mode 100644 docs/docs/auth/netlify.md create mode 100644 docs/docs/auth/nhost.md create mode 100644 docs/docs/auth/supabase.md create mode 100644 docs/docs/auth/wallet-connect.md create mode 100644 docs/versioned_docs/version-2.0/auth/auth0.md create mode 100644 docs/versioned_docs/version-2.0/auth/azure.md create mode 100644 docs/versioned_docs/version-2.0/auth/clerk.md create mode 100644 docs/versioned_docs/version-2.0/auth/custom.md create mode 100644 docs/versioned_docs/version-2.0/auth/dbauth.md create mode 100644 docs/versioned_docs/version-2.0/auth/firebase.md create mode 100644 docs/versioned_docs/version-2.0/auth/gotrue.md create mode 100644 docs/versioned_docs/version-2.0/auth/magic-link.md create mode 100644 docs/versioned_docs/version-2.0/auth/netlify.md create mode 100644 docs/versioned_docs/version-2.0/auth/nhost.md create mode 100644 docs/versioned_docs/version-2.0/auth/supabase.md create mode 100644 docs/versioned_docs/version-2.0/auth/wallet-connect.md diff --git a/docs/docs/auth/auth0.md b/docs/docs/auth/auth0.md new file mode 100644 index 000000000000..ac9eae3b47e3 --- /dev/null +++ b/docs/docs/auth/auth0.md @@ -0,0 +1,264 @@ +--- +sidebar_label: Auth0 +--- + +# Auth0 Authentication + +The following CLI command will install required packages and generate boilerplate code and files for Redwood Projects: + +```bash +yarn rw setup auth auth0 +``` + +_If you prefer to manually install the package and add code_, run the following command and then add the required code provided in the next section. + +```bash +cd web +yarn add @redwoodjs/auth @auth0/auth0-spa-js +``` + +## Setup + +To get your application keys, only complete the ["Configure Auth0"](https://auth0.com/docs/quickstart/spa/react#get-your-application-keys) section of the SPA Quickstart guide. + +**NOTE** If you're using Auth0 with Redwood then you must also [create an API](https://auth0.com/docs/quickstart/spa/react/02-calling-an-api#create-an-api) and set the audience parameter, or you'll receive an opaque token instead of the required JWT token. + +The `useRefreshTokens` options is required for automatically extending sessions beyond that set in the initial JWT expiration (often 3600/1 hour or 86400/1 day). + +If you want to allow users to get refresh tokens while offline, you must also enable the Allow Offline Access switch in your Auth0 API Settings as part of setup configuration. See: [https://auth0.com/docs/tokens/refresh-tokens](https://auth0.com/docs/tokens/refresh-tokens) + +You can increase security by using refresh token rotation which issues a new refresh token and invalidates the predecessor token with each request made to Auth0 for a new access token. + +Rotating the refresh token reduces the risk of a compromised refresh token. For more information, see: [https://auth0.com/docs/tokens/refresh-tokens/refresh-token-rotation](https://auth0.com/docs/tokens/refresh-tokens/refresh-token-rotation). + +> **Including Environment Variables in Serverless Deployment:** in addition to adding the following env vars to your deployment hosting provider, you _must_ take an additional step to include them in your deployment build process. Using the names exactly as given below, follow the instructions in [this document](environment-variables.md) to include them in your `redwood.toml`. + +```jsx title="web/src/App.js" +import { AuthProvider } from '@redwoodjs/auth' +import { Auth0Client } from '@auth0/auth0-spa-js' +import { FatalErrorBoundary } from '@redwoodjs/web' +import { RedwoodApolloProvider } from '@redwoodjs/web/apollo' + +import FatalErrorPage from 'src/pages/FatalErrorPage' +import Routes from 'src/Routes' + +import './index.css' + +const auth0 = new Auth0Client({ + domain: process.env.AUTH0_DOMAIN, + client_id: process.env.AUTH0_CLIENT_ID, + redirect_uri: process.env.AUTH0_REDIRECT_URI, + + // ** NOTE ** Storing tokens in browser local storage provides persistence across page refreshes and browser tabs. + // However, if an attacker can achieve running JavaScript in the SPA using a cross-site scripting (XSS) attack, + // they can retrieve the tokens stored in local storage. + // https://auth0.com/docs/libraries/auth0-spa-js#change-storage-options + cacheLocation: 'localstorage', + audience: process.env.AUTH0_AUDIENCE, + + // @MARK: useRefreshTokens is required for automatically extending sessions + // beyond that set in the initial JWT expiration. + // + // @MARK: https://auth0.com/docs/tokens/refresh-tokens + // useRefreshTokens: true, +}) + +const App = () => ( + + + + + + + +) + +export default App +``` + +## Login and Logout Options + +When using the Auth0 client, `login` and `logout` take `options` that can be used to override the client config: + +- `returnTo`: a permitted logout url set in Auth0 +- `redirectTo`: a target url after login + +The latter is helpful when an unauthenticated user visits a Private route, but then is redirected to the `unauthenticated` route. The Redwood router will place the previous requested path in the pathname as a `redirectTo` parameter which can be extracted and set in the Auth0 `appState`. That way, after successfully logging in, the user will be directed to this `targetUrl` rather than the config's callback. + +```jsx +const UserAuthTools = () => { + const { loading, isAuthenticated, logIn, logOut } = useAuth() + + if (loading) { + // auth is rehydrating + return null + } + + return ( + + ) +} +``` + +## Integration + +If you're using Auth0 you must also [create an API](https://auth0.com/docs/quickstart/spa/react/02-calling-an-api#create-an-api) and set the audience parameter, or you'll receive an opaque token instead of a JWT token, and Redwood expects to receive a JWT token. + +### Role-Based Access Control (RBAC) + +[Role-based access control (RBAC)](https://auth0.com/docs/authorization/concepts/rbac) refers to the idea of assigning permissions to users based on their role within an organization. It provides fine-grained control and offers a simple, manageable approach to access management that is less prone to error than assigning permissions to users individually. + +Essentially, a role is a collection of permissions that you can apply to users. A role might be "admin", "editor" or "publisher". This differs from permissions an example of which might be "publish:blog". + +### App Metadata + +Auth0 stores information (such as, support plan subscriptions, security roles, or access control groups) in `app_metadata`. Data stored in `app_metadata` cannot be edited by users. + +Create and manage roles for your application in Auth0's "User & Role" management views. You can then assign these roles to users. + +However, that info is not immediately available on the user's `app_metadata` or to RedwoodJS when authenticating. + +If you assign your user the "admin" role in Auth0, you will want your user's `app_metadata` to look like: + +``` +{ + "roles": [ + "admin" + ] +} +``` + +To set this information and make it available to RedwoodJS, you can use [Auth0 Rules](https://auth0.com/docs/rules). + +### Rules for App Metadata + +RedwoodJS needs `app_metadata` to 1) contain the role information and 2) be present in the JWT that is decoded. + +To accomplish these tasks, you can use [Auth0 Rules](https://auth0.com/docs/rules) to add them as custom claims on your JWT. + +#### Add Authorization Roles to App Metadata Rule + +Your first rule will `Add Authorization Roles to App Metadata`. + +```jsx +/// Add Authorization Roles to App Metadata +function (user, context, callback) { + auth0.users.updateAppMetadata(user.user_id, context.authorization) + .then(function(){ + callback(null, user, context); + }) + .catch(function(err){ + callback(err); + }); + } +``` + +Auth0 exposes the user's roles in `context.authorization`. This rule simply copies that information into the user's `app_metadata`, such as: + +``` +{ + "roles": [ + "admin" + ] +} +``` + +However, now you must include the `app_metadata` on the user's JWT that RedwoodJS will decode. + +#### Add App Metadata to JWT Rule + +Therefore, your second rule will `Add App Metadata to JWT`. + +You can add `app_metadata` to the `idToken` or `accessToken`. + +Adding to `idToken` will make the make app metadata accessible to RedwoodJS `getUserMetadata` which for Auth0 calls the auth client's `getUser`. + +Adding to `accessToken` will make the make app metadata accessible to RedwoodJS when decoding the JWT via `getToken`. + +While adding to `idToken` is optional, you _must_ add to `accessToken`. + +To keep your custom claims from colliding with any reserved claims or claims from other resources, you must give them a [globally unique name using a namespaced format](https://auth0.com/docs/tokens/guides/create-namespaced-custom-claims). Otherwise, Auth0 will _not_ add the information to the token(s). + +Therefore, with a namespace of "https://example.com", the `app_metadata` on your token should look like: + +```jsx +"https://example.com/app_metadata": { + "authorization": { + "roles": [ + "admin" + ] + } +}, +``` + +To set this namespace information, use the following function in your rule: + +```jsx +function (user, context, callback) { + var namespace = 'https://example.com/'; + + // adds to idToken, i.e. userMetadata in RedwoodJS + context.idToken[namespace + 'app_metadata'] = {}; + context.idToken[namespace + 'app_metadata'].authorization = { + groups: user.app_metadata.groups, + roles: user.app_metadata.roles, + permissions: user.app_metadata.permissions + }; + + context.idToken[namespace + 'user_metadata'] = {}; + + // accessToken, i.e. the decoded JWT in RedwoodJS + context.accessToken[namespace + 'app_metadata'] = {}; + context.accessToken[namespace + 'app_metadata'].authorization = { + groups: user.app_metadata.groups, + roles: user.app_metadata.roles, + permissions: user.app_metadata.permissions + }; + + context.accessToken[namespace + 'user_metadata'] = {}; + + return callback(null, user, context); +} +``` + +Now, your `app_metadata` with `authorization` and `role` information will be on the user's JWT after logging in. + +### Application `hasRole` Support + +If you intend to support, RBAC then in your `api/src/lib/auth.js` you need to extract `roles` using the `parseJWT` utility and set these roles on `currentUser`. + +If your roles are on a namespaced `app_metadata` claim, then `parseJWT` provides an option to provide this value. + +```jsx title="api/src/lib/auth.js" +const NAMESPACE = 'https://example.com' + +const currentUserWithRoles = async (decoded) => { + const currentUser = await userByUserId(decoded.sub) + return { + ...currentUser, + roles: parseJWT({ decoded: decoded, namespace: NAMESPACE }).roles, + } +} + +export const getCurrentUser = async (decoded, { type, token }) => { + try { + requireAccessToken(decoded, { type, token }) + return currentUserWithRoles(decoded) + } catch (error) { + return decoded + } +} +``` diff --git a/docs/docs/auth/azure.md b/docs/docs/auth/azure.md new file mode 100644 index 000000000000..69adf5041986 --- /dev/null +++ b/docs/docs/auth/azure.md @@ -0,0 +1,97 @@ +--- +sidebar_label: Azure +--- + +# Azure Active Directory Authentication + +## Installation + +The following CLI command will install required packages and generate boilerplate code and files for Redwood Projects: + +```bash +yarn rw setup auth azureActiveDirectory +``` + +_If you prefer to manually install the package and add code_, run the following command and then add the required code provided in the next section. + +```bash +cd web +yarn add @azure/msal-browser +``` + +## Setup + +To get your application credentials, create an [App Registration](https://docs.microsoft.com/en-us/azure/active-directory/develop/scenario-spa-app-registration) using in your Azure Active Directory tenant and make sure you configure as a [MSAL.js 2.0 with auth code flow](https://docs.microsoft.com/en-us/azure/active-directory/develop/scenario-spa-app-registration#redirect-uri-msaljs-20-with-auth-code-flow) registration. Take a note of your generated _Application ID_ (client), and the _Directory ID_ (tenant). + +[Learn more about authorization code flow](https://docs.microsoft.com/en-us/azure/active-directory/develop/reference-third-party-cookies-spas). + +## Redirect URIs + +Enter allowed redirect urls for the integrations, e.g. `http://localhost:8910/login`. This will be the `AZURE_ACTIVE_DIRECTORY_REDIRECT_URI` environment variable, and suggestively `AZURE_ACTIVE_DIRECTORY_LOGOUT_REDIRECT_URI`. + +## Authority + +The Authority is a URL that indicates a directory that MSAL can request tokens from which you can read about [here](https://docs.microsoft.com/en-us/azure/active-directory/develop/msal-client-application-configuration#authority). However, you most likely want to have e.g. `https://login.microsoftonline.com/` as Authority URL, where `` is the Azure Active Directory tenant id. This will be the `AZURE_ACTIVE_DIRECTORY_AUTHORITY` environment variable. + +```jsx title="web/src/App.js" +import { AuthProvider } from '@redwoodjs/auth' +import { PublicClientApplication } from '@azure/msal-browser' +import { FatalErrorBoundary } from '@redwoodjs/web' +import { RedwoodApolloProvider } from '@redwoodjs/web/apollo' + +import FatalErrorPage from 'src/pages/FatalErrorPage' +import Routes from 'src/Routes' + +import './index.css' + +const azureActiveDirectoryClient = new PublicClientApplication({ + auth: { + clientId: process.env.AZURE_ACTIVE_DIRECTORY_CLIENT_ID, + authority: process.env.AZURE_ACTIVE_DIRECTORY_AUTHORITY, + redirectUri: process.env.AZURE_ACTIVE_DIRECTORY_REDIRECT_URI, + postLogoutRedirectUri: process.env.AZURE_ACTIVE_DIRECTORY_LOGOUT_REDIRECT_URI, + }, +}) + +const App = () => ( + + + + + + + +) + +export default App +``` + +## Integration + +### Roles + +To setup your App Registration with custom roles and have them exposed via the `roles` claim, follow [this documentation](https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-add-app-roles-in-azure-ad-apps). + +### Login Options + +Options in method `logIn(options?)` is of type [RedirectRequest](https://azuread.github.io/microsoft-authentication-library-for-js/ref/modules/_azure_msal_browser.html#redirectrequest) and is a good place to pass in optional [scopes](https://docs.microsoft.com/en-us/graph/permissions-reference#user-permissions) to be authorized. By default, MSAL sets `scopes` to [/.default](https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent#the-default-scope) which is built in for every application that refers to the static list of permissions configured on the application registration. Furthermore, MSAL will add `openid` and `profile` to all requests. In example below we explicit include `User.Read.All` to the login scope. + +```jsx +await logIn({ + scopes: ['User.Read.All'], // becomes ['openid', 'profile', 'User.Read.All'] +}) +``` + +See [loginRedirect](https://azuread.github.io/microsoft-authentication-library-for-js/ref/classes/_azure_msal_browser.publicclientapplication.html#loginredirect), [PublicClientApplication](https://azuread.github.io/microsoft-authentication-library-for-js/ref/classes/_azure_msal_browser.publicclientapplication.html) class and [Scopes Behavior](https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-core/docs/scopes.md#scopes-behavior) for more documentation. + +### getToken Options + +Options in method `getToken(options?)` is of type [RedirectRequest](https://azuread.github.io/microsoft-authentication-library-for-js/ref/modules/_azure_msal_browser.html#redirectrequest). By default, `getToken` will be called with scope `['openid', 'profile']`. As Azure Active Directory apply [incremental consent](https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/resources-and-scopes.md#dynamic-scopes-and-incremental-consent), we can extend the permissions from the login example by including another scope, for example `Mail.Read`. + +```jsx +await getToken({ + scopes: ['Mail.Read'], // becomes ['openid', 'profile', 'User.Read.All', 'Mail.Read'] +}) +``` + +See [acquireTokenSilent](https://azuread.github.io/microsoft-authentication-library-for-js/ref/classes/_azure_msal_browser.publicclientapplication.html#acquiretokensilent), [Resources and Scopes](https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/resources-and-scopes.md#resources-and-scopes) or [full class documentation](https://pub.dev/documentation/msal_js/latest/msal_js/PublicClientApplication-class.html#constructors) for more documentation. diff --git a/docs/docs/auth/clerk.md b/docs/docs/auth/clerk.md new file mode 100644 index 000000000000..61fd753c9cd8 --- /dev/null +++ b/docs/docs/auth/clerk.md @@ -0,0 +1,75 @@ +--- +sidebar_label: Clerk +--- + +# Clerk Authentication + +## Installation + +The following CLI command will install required packages and generate boilerplate code and files for Redwood Projects: + +```bash +yarn rw setup auth clerk +``` + +## Setup + +To get started with Clerk, sign up on [their website](https://clerk.dev/) and create an application, or follow their [RedwoodJS Blog Tutorial with Clerk](https://clerk.dev/tutorials/redwoodjs-blog-tutorial-with-clerk) that has an [example repo](https://github.com/redwoodjs/redwood-tutorial) already setup. + +It's important that the `ClerkAuthProvider` added to your `App.{js|ts}` file during setup is within the `RedwoodProvider` and around Redwood's `AuthProvider`: + +```tsx {4,10} title="web/src/App.{js|ts}" +const App = () => ( + + + + + + + + + + + +) +``` + +The [RedwoodJS Blog Tutorial with Clerk](https://clerk.dev/tutorials/redwoodjs-blog-tutorial-with-clerk) also explains how to use `@clerk/clerk-react` components with Redwood's `useAuth()` hook: + +```tsx +import { UserButton, SignInButton } from '@clerk/clerk-react' + +// ... + +{ + isAuthenticated ? ( + + ) : ( + + + + ) +} +``` + +Applications in Clerk have different instances. By default, there's one for development, one for staging, and one for production. You'll need to pull three values from one of these instances. We recommend storing the development values in your local `.env` file and using the staging and production values in the appropriate env setups for your hosting platform when you deploy. + +The three values you'll need from Clerk are your instance's "Frontend API Key" url, a "Backend API key" and a "JWT verification key", all from your instance's settings under "API Keys". The Frontend API url should be stored in an env variable named `CLERK_FRONTEND_API_URL`. The Backend API key should be named `CLERK_API_KEY`. Finally, the JWT key should be named `CLERK_JWT_KEY` + +Otherwise, feel free to configure your instances however you wish with regards to their appearance and functionality. + +> **Including Environment Variables in Serverless Deploys** +> +> In addition to adding these env vars to your local `.env` file or deployment hosting provider, you _must_ take an additional step to include them in your deployment build process. Using the names exactly as given above, follow the instructions in [this document](environment-variables.md). You need to expose the `CLERK_FRONTEND_API_URL` variable to the `web` side. + +## Login and Logout Options + +When using the Clerk client, `login` and `signUp` take an `options` object that can be used to override the client config. + +For `login` the `options` may contain all the options listed at the Clerk [props documentation for login](https://docs.clerk.dev/reference/clerkjs/clerk#signinprops). + +For `signUp` the `options` may contain all the options listed at the Clerk [props documentation for signup](https://docs.clerk.dev/reference/clerkjs/clerk#signupprops). + +## Avoiding Feature Duplication Confusion + +Redwood's integration of Clerk is based on [Clerk's React SDK](https://docs.clerk.dev/reference/clerk-react). This means there is some duplication between the features available through that SDK and the ones available in the `@redwoodjs/auth` package - such as the alternatives of using Clerk's `SignedOut` component to redirect users away from a private page vs. using Redwood's `Private` route wrapper. In general, we would recommend you use the **Redwood** way of doing things when possible, as that is more likely to function harmoniously with the rest of Redwood. That being said, though, there are some great features in Clerk's SDK that you will be able to now use in your app, such as the `UserButton` and `UserProfile` components. diff --git a/docs/docs/auth/custom.md b/docs/docs/auth/custom.md new file mode 100644 index 000000000000..c3f819394344 --- /dev/null +++ b/docs/docs/auth/custom.md @@ -0,0 +1,23 @@ +--- +sidebar_label: Custom +--- + +# Custom Authentication Client + +## Installation + +The following CLI command (not implemented, see https://github.com/redwoodjs/redwood/issues/1585) will install required packages and generate boilerplate code and files for Redwood Projects: + +```bash +yarn rw setup auth custom +``` + +## Setup + +It is possible to implement a custom provider for Redwood Auth. In which case you might also consider adding the provider to Redwood itself. + +If you are trying to implement your own auth, support is very early and limited at this time. Additionally, there are many considerations and responsibilities when it comes to managing custom auth. For most cases we recommend using an existing provider. + +However, there are examples contributed by developers in the Redwood forums and Discord server. + +The most complete example (although now a bit outdated) is found in [this forum thread](https://community.redwoodjs.com/t/custom-github-jwt-auth-with-redwood-auth/610). Here's another [helpful message in the thread](https://community.redwoodjs.com/t/custom-github-jwt-auth-with-redwood-auth/610/25). diff --git a/docs/docs/auth/dbauth.md b/docs/docs/auth/dbauth.md new file mode 100644 index 000000000000..3061b08062ea --- /dev/null +++ b/docs/docs/auth/dbauth.md @@ -0,0 +1,220 @@ +--- +sidebar_label: Self-hosted (dbAuth) +--- + +# Self-hosted Authentication (dbAuth) + +Redwood's own **dbAuth** provides several benefits: + +- Use your own database for storing user credentials +- Use your own login, signup and forgot password pages (or use Redwood's pre-built ones) +- Customize login session length +- No external dependencies +- No user data ever leaves your servers +- No additional charges/limits based on number of users +- No third party service outages affecting your site + +And potentially one large drawback: + +- Use your own database for storing user credentials + +However, we're following best practices for storing these credentials: + +1. Users' passwords are [salted and hashed](https://auth0.com/blog/adding-salt-to-hashing-a-better-way-to-store-passwords/) with PBKDF2 before being stored +2. Plaintext passwords are never stored anywhere, and only transferred between client and server during the login/signup phase (and hopefully only over HTTPS) +3. Our logger scrubs sensitive parameters (like `password`) before they are output + +Even if you later decide you want to let someone else handle your user data for you, dbAuth is a great option for getting up and running quickly (we even have a generator for creating basic login and signup pages for you). + +## How It Works + +dbAuth relies on good ol' fashioned cookies to determine whether a user is logged in or not. On an attempted login, a serverless function on the api-side checks whether a user exists with the given username (internally, dbAuth refers to this field as _username_ but you can use anything you want, like an email address). If a user with that username is found, does their salted and hashed password match the one in the database? + +If so, an [HttpOnly](https://owasp.org/www-community/HttpOnly), [Secure](https://owasp.org/www-community/controls/SecureCookieAttribute), [SameSite](https://owasp.org/www-community/SameSite) cookie (dbAuth calls this the "session cookie") is sent back to the browser containing the ID of the user. The content of the cookie is a simple string, but AES encrypted with a secret key (more on that later). + +When the user makes a GraphQL call, we decrypt the cookie and make sure that the user ID contained within still exists in the database. If so, the request is allowed to proceed. + +If there are any shenanigans detected (the cookie can't be decrypted properly, or the user ID found in the cookie does not exist in the database) the user is immediately logged out by expiring the session cookie. + +## Setup + +A single CLI command will get you everything you need to get dbAuth working, minus the actual login/signup pages: + + yarn rw setup auth dbAuth + +Read the post-install instructions carefully as they contain instructions for adding database fields for the hashed password and salt, as well as how to configure the auth serverless function based on the name of the table that stores your user data. Here they are, but could change in future releases: + +> You will need to add a couple of fields to your User table in order to store a hashed password and salt: +> +> model User { +> id Int @id @default(autoincrement()) +> email String @unique +> hashedPassword String // <─┐ +> salt String // <─┼─ add these lines +> resetToken String? // <─┤ +> resetTokenExpiresAt DateTime? // <─┘ +> } +> +> If you already have existing user records you will need to provide a default value or Prisma complains, so change those to: +> +> hashedPassword String @default("") +> salt String @default("") +> +> You'll need to let Redwood know what field you're using for your users' `id` and `username` fields In this case we're using `id` and `email`, so update those in the `authFields` config in `/api/src/functions/auth.js` (this is also the place to tell Redwood if you used a different name for the `hashedPassword` or `salt` fields): +> +> authFields: { +> id: 'id', +> username: 'email', +> hashedPassword: 'hashedPassword', +> salt: 'salt', +> resetToken: 'resetToken', +> resetTokenExpiresAt: 'resetTokenExpiresAt', +> }, +> +> To get the actual user that's logged in, take a look at `getCurrentUser()` in `/api/src/lib/auth.js`. We default it to something simple, but you may use different names for your model or unique ID fields, in which case you need to update those calls (instructions are in the comment above the code). +> +> Finally, we created a `SESSION_SECRET` environment variable for you in `.env`. This value should NOT be checked into version control and should be unique for each environment you deploy to. If you ever need to log everyone out of your app at once change this secret to a new value. To create a new secret, run: +> +> yarn rw g secret +> +> Need simple Login, Signup and Forgot Password pages? Of course we have a generator for those: +> +> yarn rw generate dbAuth + +Note that if you change the fields named `hashedPassword` and `salt`, and you have some verbose logging in your app, you'll want to scrub those fields from appearing in your logs. See the [Redaction](logger.md#redaction) docs for info. + +## Scaffolding Login/Signup/Forgot Password Pages + +If you don't want to create your own login, signup and forgot password pages from scratch we've got a generator for that: + + yarn rw g dbAuth + +The default routes will make them available at `/login`, `/signup`, `/forgot-password`, and `/reset-password` but that's easy enough to change. Again, check the post-install instructions for one change you need to make to those pages: where to redirect the user to once their login/signup is successful. + +If you'd rather create your own, you might want to start from the generated pages anyway as they'll contain the other code you need to actually submit the login credentials or signup fields to the server for processing. + +## Configuration + +Almost all config for dbAuth lives in `api/src/functions/auth.js` in the object you give to the `DbAuthHandler` initialization. The comments above each key will explain what goes where. Here's an overview of the more important options: + +### login.handler() + +If you want to do something other than immediately let a user log in if their username/password is correct, you can add additional logic in `login.handler()`. For example, if a user's credentials are correct, but they haven't verified their email address yet, you can throw an error in this function with the appropriate message and then display it to the user. If the login should proceed, simply return the user that was passed as the only argument to the function: + +```jsx +login: { + handler: (user) => { + if (!user.verified) { + throw new Error('Please validate your email first!') + } else { + return user + } + } +} +``` + +### signup.handler() + +This function should contain the code needed to actually create a user in your database. You will receive a single argument which is an object with all of the fields necessary to create the user (`username`, `hashedPassword` and `salt`) as well as any additional fields you included in your signup form in an object called `userAttributes`: + +```jsx +signup: { + handler: ({ username, hashedPassword, salt, userAttributes }) => { + return db.user.create({ + data: { + email: username, + hashedPassword: hashedPassword, + salt: salt, + name: userAttributes.name, + }, + }) + } +} +``` + +Before `signup.handler()` is invoked, dbAuth will check that the username is unique in the database and throw an error if not. + +There are three things you can do within this function depending on how you want the signup to proceed: + +1. If everything is good and the user should be logged in after signup: return the user you just created +2. If the user is safe to create, but you do not want to log them in automatically: return a string, which will be returned by the `signUp()` function you called after destructuring it from `useAuth()` (see code snippet below) +3. If the user should _not_ be able to sign up for whatever reason: throw an error in this function with the message to be displayed + +You can deal with case #2 by doing something like the following in a signup component/page: + +```jsx +const { signUp } = useAuth() + +const onSubmit = async (data) => { + const response = await signUp({ ...data }) + + if (response.message) { + toast.error(response.message) // user created, but not logged in + } else { + toast.success('Welcome!') // user created and logged in + navigate(routes.dashboard()) + } +} +``` + +### forgotPassword.handler() + +This handler is invoked if a user is found with the username/email that they submitted on the Forgot Password page, and that user will be passed as an argument. Inside this function is where you'll send the user a link to reset their password—via an email is most common. The link will, by default, look like: + + https://example.com/reset-password?resetToken=${user.resetToken} + +If you changed the path to the Reset Password page in your routes you'll need to change it here. If you used another name for the `resetToken` database field, you'll need to change that here as well: + + https://example.com/reset-password?resetKey=${user.resetKey} + +### resetPassword.handler() + +This handler is invoked after the password has been successfully changed in the database. Returning something truthy (like `return user`) will automatically log the user in after their password is changed. If you'd like to return them to the login page and make them log in manually, `return false` and redirect the user in the Reset Password page. + +### Cookie config + +These options determine how the cookie that tracks whether the client is authorized is stored in the browser. The default configuration should work for most use cases. If you serve your web and api sides from different domains you'll need to make some changes: set `SameSite` to `None` and then add [CORS configuration](#cors-config). + +```jsx +cookie: { + HttpOnly: true, + Path: '/', + SameSite: 'Strict', + Secure: true, + // Domain: 'example.com', +} +``` + +### CORS config + +If you're using dbAuth and your api and web sides are deployed to different domains then you'll need to configure CORS for both GraphQL in general and dbAuth. You'll also need to enable a couple of options to be sure and send/accept credentials in XHR requests. For more info, see the complete [CORS doc](cors.md#cors-and-authentication). + +### Error Messages + +There are several error messages that can be displayed, including: + +- Username/email not found +- Incorrect password +- Expired reset password token + +We've got some default error messages that sound nice, but may not fit the tone of your site. You can customize these error messages in `api/src/functions/auth.js` in the `errors` prop of each of the `login`, `signup`, `forgotPassword` and `resetPassword` config objects. The generated file contains tons of comments explaining when each particular error message may be shown. + +## Environment Variables + +### Cookie Domain + +By default, the session cookie will not have the `Domain` property set, which a browser will default to be the [current domain only](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#define_where_cookies_are_sent). If your site is spread across multiple domains (for example, your site is at `example.com` but your api-side is deployed to `api.example.com`) you'll need to explicitly set a Domain so that the cookie is accessible to both. + +To do this, create an environment variable named `DBAUTH_COOKIE_DOMAIN` set to the root domain of your site, which will allow it to be read by all subdomains as well. For example: + + DBAUTH_COOKIE_DOMAIN=example.com + +### Session Secret Key + +If you need to change the secret key that's used to encrypt the session cookie, or deploy to a new target (each deploy environment should have its own unique secret key) we've got a CLI tool for creating a new one: + + yarn rw g secret + +Note that the secret that's output is _not_ appended to your `.env` file or anything else, it's merely output to the screen. You'll need to put it in the right place after that. + +> The `.env` file is set to be ignored by git and not committed to version control. There is another file, `.env.defaults`, which is meant to be safe to commit and contain simple ENV vars that your dev team can share. The encryption key for the session cookie is NOT one of these shareable vars! diff --git a/docs/docs/auth/firebase.md b/docs/docs/auth/firebase.md new file mode 100644 index 000000000000..bdcc07b0280e --- /dev/null +++ b/docs/docs/auth/firebase.md @@ -0,0 +1,216 @@ +--- +sidebar_label: Firebase +--- + +# Firebase Authentication + +## Installation + +The following CLI command will install required packages and generate boilerplate code and files for Redwood Projects: + +```bash +yarn rw setup auth firebase +``` + +## Setup + +We're using [Firebase Google Sign-In](https://firebase.google.com/docs/auth/web/google-signin), so you'll have to follow the ["Before you begin"](https://firebase.google.com/docs/auth/web/google-signin#before_you_begin) steps in this guide. **Only** follow the "Before you begin" parts. + +> **Including Environment Variables in Serverless Deployment:** in addition to adding the following env vars to your deployment hosting provider, you _must_ take an additional step to include them in your deployment build process. Using the names exactly as given below, follow the instructions in [this document](https://redwoodjs.com/docs/environment-variables) to "Whitelist them in your `redwood.toml`". + +```jsx title="web/src/App.js" +import { AuthProvider } from '@redwoodjs/auth' +import { initializeApp, getApps, getApp } from '@firebase/app' +import * as firebaseAuth from '@firebase/auth' +import { FatalErrorBoundary } from '@redwoodjs/web' +import { RedwoodApolloProvider } from '@redwoodjs/web/apollo' + +import FatalErrorPage from 'src/pages/FatalErrorPage' +import Routes from 'src/Routes' + +import './index.css' + +const firebaseClientConfig = { + apiKey: process.env.FIREBASE_API_KEY, + authDomain: process.env.FIREBASE_AUTH_DOMAIN, + databaseURL: process.env.FIREBASE_DATABASE_URL, + projectId: process.env.FIREBASE_PROJECT_ID, + storageBucket: process.env.FIREBASE_STORAGE_BUCKET, + messagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID, + appId: process.env.FIREBASE_APP_ID, +} + +const firebaseApp = ((config) => { + const apps = getApps() + if (!apps.length) { + initializeApp(config) + } + return getApp() +})(firebaseConfig) + +export const firebaseClient = { + firebaseAuth, + firebaseApp, +} + +const App = () => ( + + + + + + + +) + +export default App +``` + +## Usage + +```jsx +const UserAuthTools = () => { + const { loading, isAuthenticated, logIn, logOut } = useAuth() + + if (loading) { + return null + } + + return ( + + ) +} +``` + +## Integration + +See the Firebase information within this doc's [Auth Provider Specific Integration](#auth-provider-specific-integration) section. + +You must follow the ["Before you begin"](https://firebase.google.com/docs/auth/web/google-signin) part of the "Authenticate Using Google Sign-In with JavaScript" guide. + +### Role-Based Access Control (RBAC) + +Requires a custom implementation. + +### App Metadata + +None. + +### Add Application `hasRole` Support + +Requires a custom implementation. + +### Auth Providers + +Providers can be configured by specifying `logIn(provider)` and `signUp(provider)`, where `provider` is a **string** of one of the supported providers. + +Supported providers: + +- google.com (Default) +- facebook.com +- github.com +- twitter.com +- microsoft.com +- apple.com + +### Email & Password Auth + +Email/password authentication is supported by calling `login({ email, password })` and `signUp({ email, password })`. + +### Email Link (Password-less Sign-in) + +In Firebase Console, you must enable "Email link (passwordless sign-in)" with the configuration toggle for the email provider. The authentication sequence for passwordless email links has two steps: + +1. First, an email with the link must be generated and sent to the user. Either using using firebase client sdk (web side) [sendSignInLinkToEmail()](https://firebase.google.com/docs/reference/js/auth.emailauthprovider#example_2_2), which generates the link and sends the email to the user on behalf of your application or alternatively, generate the link using backend admin sdk (api side), see ["Generate email link for sign-in](https://firebase.google.com/docs/auth/admin/email-action-links#generate_email_link_for_sign-in) but it is then your responsibility to send an email to the user containing the link. +2. Second, authentication is completed when the user is redirected back to the application and the AuthProvider's logIn({emailLink, email, providerId: 'emailLink'}) method is called. + +For example, users could be redirected to a dedicated route/page to complete authentication: + +```jsx +import { useEffect } from 'react' +import { Redirect, routes } from '@redwoodjs/router' +import { useAuth } from '@redwoodjs/auth' + +const EmailSigninPage = () => { + const { loading, hasError, error, logIn } = useAuth() + + const email = window.localStorage.getItem('emailForSignIn') + // TODO: Prompt the user for email if not found in local storage, for example + // if the user opened the email link on a different device. + + const emailLink = window.location.href + + useEffect(() => { + logIn({ + providerId: 'emailLink', + email, + emailLink, + }) + }, []) + + if (loading) { + return
Auth Loading...
+ } + + if (hasError) { + console.error(error) + return
Auth Error... check console
+ } + + return +} + +export default EmailSigninPage +``` + +### Custom Token + +If you want to [integrate firebase auth with another authentication system](https://firebase.google.com/docs/auth/web/custom-auth), you can use a custom token provider: + +```jsx +logIn({ + providerId: 'customToken', + customToken, +}) +``` + +Some caveats about using custom tokens: + +- make sure it's actually what you want to use +- remember that the client's firebase authentication state has an independent lifetime than the custom token + +If you want to read more, check out [Demystifying Firebase Auth Tokens](https://medium.com/@jwngr/demystifying-firebase-auth-tokens-e0c533ed330c). + +### Custom Parameters & Scopes for Google OAuth Provider + +Both `logIn()` and `signUp()` can accept a single argument of either a **string** or **object**. If a string is provided, it should be any of the supported providers (see above), which will configure the defaults for that provider. + +`logIn()` and `signUp()` also accept a single a configuration object. This object accepts `providerId`, `email`, `password`, and `scope` and `customParameters`. (In fact, passing in any arguments ultimately results in this object). You can use this configuration object to pass in values for the optional Google OAuth Provider methods _setCustomParameters_ and _addScope_. + +Below are the parameters that `logIn()` and `signUp()` accept: + +- `providerId`: Accepts one of the supported auth providers as a **string**. If no arguments are passed to `login() / signUp()` this will default to 'google.com'. Provider strings passed as a single argument to `login() / signUp()` will be cast to this value in the object. +- `email`: Accepts a **string** of a users email address. Used in conjunction with `password` and requires that Firebase has email authentication enabled as an option. +- `password`: Accepts a **string** of a users password. Used in conjunction with `email` and requires that Firebase has email authentication enabled as an option. +- `scope`: Accepts an **array** of strings ([Google OAuth Scopes](https://developers.google.com/identity/protocols/oauth2/scopes)), which can be added to the requested Google OAuth Provider. These will be added using the Google OAuth _addScope_ method. +- `customParameters`: accepts an **object** with the [optional parameters](https://firebase.google.com/docs/reference/js/firebase.auth.GoogleAuthProvider#setcustomparameters) for the Google OAuth Provider _setCustomParameters_ method. [Valid parameters](https://developers.google.com/identity/protocols/oauth2/openid-connect#authenticationuriparameters) include 'hd', 'include_granted_scopes', 'login_hint' and 'prompt'. + +### Firebase Auth Examples + +- `logIn()/signUp()`: Defaults to Google provider. +- `logIn({providerId: 'github.com'})`: Log in using GitHub as auth provider. +- `signUp({email: "someone@email.com", password: 'some_good_password'})`: Creates a firebase user with email/password. +- `logIn({email: "someone@email.com", password: 'some_good_password'})`: Logs in existing firebase user with email/password. +- `logIn({scopes: ['https://www.googleapis.com/auth/calendar']})`: Adds a scope using the [addScope](https://firebase.google.com/docs/reference/js/firebase.auth.GoogleAuthProvider#addscope) method. +- `logIn({ customParameters: { prompt: "consent" } })`: Sets the OAuth custom parameters using [setCustomParameters](https://firebase.google.com/docs/reference/js/firebase.auth.GoogleAuthProvider#addscope) method. diff --git a/docs/docs/auth/gotrue.md b/docs/docs/auth/gotrue.md new file mode 100644 index 000000000000..0d8edb8a323a --- /dev/null +++ b/docs/docs/auth/gotrue.md @@ -0,0 +1,61 @@ +--- +sidebar_label: GoTrue +--- + +# GoTrue Authentication + +## Installation + +The following CLI command will install required packages and generate boilerplate code and files for Redwood Projects: + +```bash +yarn rw setup auth goTrue +``` + +_If you prefer to manually install the package and add code_, run the following command and then add the required code provided in the next section. + +```bash +cd web +yarn add @redwoodjs/auth gotrue-js +``` + +## Setup + +You will need to enable Identity on your Netlify site. + +Add the GoTrue-JS package to the web side: + +```bash +yarn workspace web add gotrue-js +``` + +Instantiate GoTrue and pass in your configuration. Be sure to set APIUrl to the API endpoint found in your Netlify site's Identity tab: + +```jsx title="web/src/App.js" +import { AuthProvider } from '@redwoodjs/auth' +import GoTrue from 'gotrue-js' +import { FatalErrorBoundary } from '@redwoodjs/web' +import { RedwoodApolloProvider } from '@redwoodjs/web/apollo' + +import FatalErrorPage from 'src/pages/FatalErrorPage' +import Routes from 'src/Routes' + +import './index.css' + +const goTrueClient = new GoTrue({ + APIUrl: 'https://MYAPP.netlify.app/.netlify/identity', + setCookie: true, +}) + +const App = () => ( + + + + + + + +) + +export default App +``` diff --git a/docs/docs/auth/magic-link.md b/docs/docs/auth/magic-link.md new file mode 100644 index 000000000000..325d2762cef3 --- /dev/null +++ b/docs/docs/auth/magic-link.md @@ -0,0 +1,107 @@ +--- +sidebar_label: Magic.link +--- + +# Magic.Link Authentication + +## Installation + +The following CLI command will install required packages and generate boilerplate code and files for Redwood Projects: + +```bash +yarn rw setup auth magicLink +``` + +_If you prefer to manually install the package and add code_, run the following command and then add the required code provided in the next section. + +```bash +cd web +yarn add @redwoodjs/auth magic-sdk +``` + +## Setup + +To get your application keys, go to [dashboard.magic.link](https://dashboard.magic.link/) then navigate to the API keys add them to your `.env`. + +> **Including Environment Variables in Serverless Deployment:** in addition to adding the following env vars to your deployment hosting provider, you _must_ take an additional step to include them in your deployment build process. Using the names exactly as given below, follow the instructions in [this document](environment-variables.md) to "Whitelist them in your `redwood.toml`". + +```jsx title="web/src/App.js|tsx" +import { useAuth, AuthProvider } from '@redwoodjs/auth' +import { Magic } from 'magic-sdk' +import { FatalErrorBoundary } from '@redwoodjs/web' +import { RedwoodApolloProvider } from '@redwoodjs/web/apollo' + +import FatalErrorPage from 'src/pages/FatalErrorPage' +import Routes from 'src/Routes' + +import './index.css' + +const m = new Magic(process.env.MAGICLINK_PUBLIC) + +const App = () => ( + + + + + + + +) + +export default App +``` + +```jsx title="web/src/Routes.js|tsx" +import { useAuth } from '@redwoodjs/auth' +import { Router, Route } from '@redwoodjs/router' + +const Routes = () => { + return ( + + + + + ) +} + +export default Routes +``` + +## Integration + +The Redwood API does not include the functionality to decode Magic.link authentication tokens, so the client is initiated and decodes the tokens inside of `getCurrentUser`. + +### Installation + +First, you must manually install the **Magic Admin SDK** in your project's `api/package.json`. + +```bash +yarn workspace api add @magic-sdk/admin +``` + +### Setup + +To get your application running _without setting up_ `Prisma`, get your `SECRET KEY` from [dashboard.magic.link](https://dashboard.magic.link/). Then add `MAGICLINK_SECRET` to your `.env`. + +```jsx title="redwood/api/src/lib/auth.js|ts" +import { Magic } from '@magic-sdk/admin' + +export const getCurrentUser = async (_decoded, { token }) => { + const mAdmin = new Magic(process.env.MAGICLINK_SECRET) + + return await mAdmin.users.getMetadataByToken(token) +} +``` + +Magic.link recommends using the issuer as the userID to retrieve user metadata via `Prisma` + +```jsx title="redwood/api/src/lib/auth.ts" +import { Magic } from '@magic-sdk/admin' + +export const getCurrentUser = async (_decoded, { token }) => { + const mAdmin = new Magic(process.env.MAGICLINK_SECRET) + const { email, publicAddress, issuer } = await mAdmin.users.getMetadataByToken(token) + + return await db.user.findUnique({ where: { issuer } }) +} +``` diff --git a/docs/docs/auth/netlify.md b/docs/docs/auth/netlify.md new file mode 100644 index 000000000000..790ce1b994b7 --- /dev/null +++ b/docs/docs/auth/netlify.md @@ -0,0 +1,88 @@ +--- +sidebar_label: Netlify +--- + +# Netlify Identity Authentication + +## Installation + +The following CLI command will install required packages and generate boilerplate code and files for Redwood Projects: + +```bash +yarn rw setup auth netlify +``` + +_If you prefer to manually install the package and add code_, run the following command and then add the required code provided in the next section. + +```bash +cd web +yarn add @redwoodjs/auth netlify-identity-widget +``` + +## Setup + +You will need to enable Identity on your Netlify site. + +```jsx title="web/src/App.js" +import { AuthProvider } from '@redwoodjs/auth' +import netlifyIdentity from 'netlify-identity-widget' +import { isBrowser } from '@redwoodjs/prerender/browserUtils' +import { FatalErrorBoundary } from '@redwoodjs/web' +import { RedwoodApolloProvider } from '@redwoodjs/web/apollo' + +import FatalErrorPage from 'src/pages/FatalErrorPage' +import Routes from 'src/Routes' + +import './index.css' + +isBrowser && netlifyIdentity.init() + +const App = () => ( + + + + + + + +) + +export default App +``` + +## Netlify Identity Auth Provider Specific Setup + +See the Netlify Identity information within this doc's [Auth Provider Specific Integration](#auth-provider-specific-integration) section. + + +## Integration + +[Netlify Identity](https://docs.netlify.com/visitor-access/identity) offers [Role-based access control (RBAC)](https://docs.netlify.com/visitor-access/identity/manage-existing-users/#user-account-metadata). + +### Role-based access control (RBAC) + +Role-based access control (RBAC) refers to the idea of assigning permissions to users based on their role within an organization. It provides fine-grained control and offers a simple, manageable approach to access management that is less prone to error than assigning permissions to users individually. + +Essentially, a role is a collection of permissions that you can apply to users. A role might be "admin", "editor" or "publisher". This differs from permissions an example of which might be "publish:blog". + +### App Metadata + +Netlify Identity stores information (such as, support plan subscriptions, security roles, or access control groups) in `app_metadata`. Data stored in `app_metadata` cannot be edited by users. + +Create and manage roles for your application in Netlify's "Identity" management views. You can then assign these roles to users. + +### Add Application `hasRole` Support + +If you intend to support, RBAC then in your `api/src/lib/auth.js` you need to extract `roles` using the `parseJWT` utility and set these roles on `currentUser`. + +Netlify will store the user's roles on the `app_metadata` claim and the `parseJWT` function provides an option to extract the roles so they can be assigned to the `currentUser`. + +For example: + +```jsx title="api/src/lib/auth.js" +export const getCurrentUser = async (decoded) => { + return context.currentUser || { ...decoded, roles: parseJWT({ decoded }).roles } +} +``` + +Now your `currentUser.roles` info will be available to both `requireAuth()` on the api side and `hasRole()` on the web side. diff --git a/docs/docs/auth/nhost.md b/docs/docs/auth/nhost.md new file mode 100644 index 000000000000..0c11eb529d2b --- /dev/null +++ b/docs/docs/auth/nhost.md @@ -0,0 +1,47 @@ +--- +sidebar_label: Nhost +--- + +# Nhost Authentication + +## Installation + +The following CLI command will install required packages and generate boilerplate code and files for Redwood Projects: + +```bash +yarn rw setup auth nhost +``` + +## Setup + +Update your .env file with the following setting which can be found on your Nhost project's dashboard. + +- `NHOST_BACKEND_URL` with the unique Nhost Backend URL that can be found in the app's dashboard. +- `NHOST_JWT_SECRET` with the JWT Key secret that you have set in your project's Settings > Hasura "JWT Key" section. + +## Usage + +Nhost supports the following methods: + +- email/password +- passwordless with email +- passwordless with SMS +- OAuth Providers (via GitHub, Google, Facebook, Spotify, Discord, Twitch, Apple, Twitter, Microsoft and Linkedin). + +Depending on the credentials provided: + +- A user can sign in either via email or a supported OAuth provider. +- A user can sign up via email and password. For OAuth simply sign in and the user account will be created if it does not exist. +- Note: You must enable and configure the OAuth provider appropriately. To enable and configure a provider, please navigate to Users -> Login settings, from your app's dashboard. + +For the docs on Authentication, see: + +If you are also **using Nhost as your GraphQL API server**, you will need to pass `skipFetchCurrentUser` as a prop to `AuthProvider` , as follows: + +```jsx + +``` + +This avoids having an additional request to fetch the current user which is meant to work with Apollo Server and Prisma. + +Important: The `skipFetchCurrentUser` attribute is **only** needed if you are **not** using the standard RedwoodJS api side GraphQL Server. diff --git a/docs/docs/auth/supabase.md b/docs/docs/auth/supabase.md new file mode 100644 index 000000000000..d6a687110559 --- /dev/null +++ b/docs/docs/auth/supabase.md @@ -0,0 +1,60 @@ +--- +sidebar_label: Supabase +--- + +# Supabase Authentication + +## Installation + +The following CLI command will install required packages and generate boilerplate code and files for Redwood Projects: + +```bash +yarn rw setup auth supabase +``` + +## Setup + +Update your .env file with the following settings supplied when you created your new Supabase project: + +- `SUPABASE_URL` with the unique Supabase URL for your project +- `SUPABASE_KEY` with the unique Supabase Key that identifies which API KEY to use +- `SUPABASE_JWT_SECRET` with the secret used to sign and verify the JSON Web Token (JWT) + +You can find these values in your project's dashboard under Settings -> API. + +For full Supabase documentation, see: + +## Usage + +Supabase supports several sign in methods: + +- email/password +- passwordless via emailed magiclink +- authenticate via phone with SMS based OTP (One-Time Password) tokens. See: [SMS OTP with Twilio](https://supabase.io/docs/guides/auth/auth-twilio) +- Sign in with redirect. You can control where the user is redirected to after they are logged in via a `redirectTo` option. +- Sign in with a valid refresh token that was returned on login. +- Sign in using third-party providers/OAuth via + - [Apple](https://supabase.io/docs/guides/auth/auth-apple) + - Azure Active Directory + - [Bitbucket](https://supabase.io/docs/guides/auth/auth-bitbucket) + - [Discord](https://supabase.io/docs/guides/auth/auth-discord) + - [Facebook](https://supabase.io/docs/guides/auth/auth-facebook) + - [GitHub](https://supabase.io/docs/guides/auth/auth-github) + - [GitLab](https://supabase.io/docs/guides/auth/auth-gitlab) + - [Google](https://supabase.io/docs/guides/auth/auth-google) + - [Twitch](https://supabase.io/docs/guides/auth/auth-twitch) + - [Twitter](https://supabase.io/docs/guides/auth/auth-twitter) +- Sign in with a [valid refresh token](https://supabase.io/docs/reference/javascript/auth-signin#sign-in-using-a-refresh-token-eg-in-react-native) that was returned on login. Used e.g. in React Native. +- Sign in with scopes. If you need additional data from an OAuth provider, you can include a space-separated list of `scopes` in your request options to get back an OAuth `provider_token`. + +Depending on the credentials provided: + +- A user can sign up either via email or sign in with supported OAuth provider: `'apple' | 'azure' | 'bitbucket' | 'discord' | 'facebook' | 'github' | 'gitlab' | 'google' | 'twitch' | 'twitter'` +- If you sign in with a valid refreshToken, the current user will be updated +- If you provide email without a password, the user will be sent a magic link. +- The magic link's destination URL is determined by the SITE_URL config variable. To change this, you can go to Authentication -> Settings on `app.supabase.io` for your project. +- Specifying an OAuth provider will open the browser to the relevant login page +- Note: You must enable and configure the OAuth provider appropriately. To configure these providers, you can go to Authentication -> Settings on `app.supabase.io` for your project. +- Note: To authenticate using SMS based OTP (One-Time Password) you will need a [Twilio](https://www.twilio.com/try-twilio) account + +For Supabase Authentication documentation, see: diff --git a/docs/docs/auth/wallet-connect.md b/docs/docs/auth/wallet-connect.md new file mode 100644 index 000000000000..8b48202b2ac1 --- /dev/null +++ b/docs/docs/auth/wallet-connect.md @@ -0,0 +1,17 @@ +--- +sidebar_label: WalletConnect +--- + +# WalletConnect Authentication + +## Installation + +The following CLI command will install required packages and generate boilerplate code and files for Redwood Projects: + +```bash +yarn rw setup auth ethereum +``` + +## Setup + +To complete setup, you'll also need to update your `api` server manually. See https://github.com/oneclickdapp/ethereum-auth for instructions. diff --git a/docs/docs/authentication.md b/docs/docs/authentication.md index 8f38daafb5c0..12062f77f72c 100644 --- a/docs/docs/authentication.md +++ b/docs/docs/authentication.md @@ -8,977 +8,51 @@ description: Set up an authentication provider We currently support the following third-party authentication providers: -- Netlify Identity _([Repo on GitHub](https://github.com/netlify/netlify-identity-widget))_ -- Netlify GoTrue-JS _([Repo on GitHub](https://github.com/netlify/gotrue-js))_ - Auth0 _([Repo on GitHub](https://github.com/auth0/auth0-spa-js))_ -- Clerk _([Website](https://clerk.dev))_ - Azure Active Directory _([Repo on GitHub](https://github.com/AzureAD/microsoft-authentication-library-for-js))_ -- Magic Links - Magic.js _([Repo on GitHub](https://github.com/MagicHQ/magic-js))_ +- Clerk _([Website](https://clerk.dev))_ - Firebase _([Documentation Website](https://firebase.google.com/docs/auth))_ -- Supabase _([Documentation Website](https://supabase.io/docs/guides/auth))_ -- Ethereum _([Repo on GitHub](https://github.com/oneclickdapp/ethereum-auth))_ -- Nhost _([Documentation Website](https://docs.nhost.io/platform/authentication))_ -- Custom -- [Contribute one](https://github.com/redwoodjs/redwood/tree/main/packages/auth), it's SuperEasy™! - -> 👉 Check out the [Auth Playground](https://github.com/redwoodjs/playground-auth). - -## Self-hosted Auth Installation and Setup - -Redwood's own **dbAuth** provides several benefits: - -- Use your own database for storing user credentials -- Use your own login, signup and forgot password pages (or use Redwood's pre-built ones) -- Customize login session length -- No external dependencies -- No user data ever leaves your servers -- No additional charges/limits based on number of users -- No third party service outages affecting your site - -And potentially one large drawback: - -- Use your own database for storing user credentials - -However, we're following best practices for storing these credentials: - -1. Users' passwords are [salted and hashed](https://auth0.com/blog/adding-salt-to-hashing-a-better-way-to-store-passwords/) with PBKDF2 before being stored -2. Plaintext passwords are never stored anywhere, and only transferred between client and server during the login/signup phase (and hopefully only over HTTPS) -3. Our logger scrubs sensitive parameters (like `password`) before they are output - -Even if you later decide you want to let someone else handle your user data for you, dbAuth is a great option for getting up and running quickly (we even have a generator for creating basic login and signup pages for you). - -### How It Works - -dbAuth relies on good ol' fashioned cookies to determine whether a user is logged in or not. On an attempted login, a serverless function on the api-side checks whether a user exists with the given username (internally, dbAuth refers to this field as _username_ but you can use anything you want, like an email address). If a user with that username is found, does their salted and hashed password match the one in the database? - -If so, an [HttpOnly](https://owasp.org/www-community/HttpOnly), [Secure](https://owasp.org/www-community/controls/SecureCookieAttribute), [SameSite](https://owasp.org/www-community/SameSite) cookie (dbAuth calls this the "session cookie") is sent back to the browser containing the ID of the user. The content of the cookie is a simple string, but AES encrypted with a secret key (more on that later). - -When the user makes a GraphQL call, we decrypt the cookie and make sure that the user ID contained within still exists in the database. If so, the request is allowed to proceed. - -If there are any shenanigans detected (the cookie can't be decrypted properly, or the user ID found in the cookie does not exist in the database) the user is immediately logged out by expiring the session cookie. - -### Setup - -A single CLI command will get you everything you need to get dbAuth working, minus the actual login/signup pages: - - yarn rw setup auth dbAuth - -Read the post-install instructions carefully as they contain instructions for adding database fields for the hashed password and salt, as well as how to configure the auth serverless function based on the name of the table that stores your user data. Here they are, but could change in future releases: - -> You will need to add a couple of fields to your User table in order to store a hashed password and salt: -> -> model User { -> id Int @id @default(autoincrement()) -> email String @unique -> hashedPassword String // <─┐ -> salt String // <─┼─ add these lines -> resetToken String? // <─┤ -> resetTokenExpiresAt DateTime? // <─┘ -> } -> -> If you already have existing user records you will need to provide a default value or Prisma complains, so change those to: -> -> hashedPassword String @default("") -> salt String @default("") -> -> You'll need to let Redwood know what field you're using for your users' `id` and `username` fields In this case we're using `id` and `email`, so update those in the `authFields` config in `/api/src/functions/auth.js` (this is also the place to tell Redwood if you used a different name for the `hashedPassword` or `salt` fields): -> -> authFields: { -> id: 'id', -> username: 'email', -> hashedPassword: 'hashedPassword', -> salt: 'salt', -> resetToken: 'resetToken', -> resetTokenExpiresAt: 'resetTokenExpiresAt', -> }, -> -> To get the actual user that's logged in, take a look at `getCurrentUser()` in `/api/src/lib/auth.js`. We default it to something simple, but you may use different names for your model or unique ID fields, in which case you need to update those calls (instructions are in the comment above the code). -> -> Finally, we created a `SESSION_SECRET` environment variable for you in `.env`. This value should NOT be checked into version control and should be unique for each environment you deploy to. If you ever need to log everyone out of your app at once change this secret to a new value. To create a new secret, run: -> -> yarn rw g secret -> -> Need simple Login, Signup and Forgot Password pages? Of course we have a generator for those: -> -> yarn rw generate dbAuth - -Note that if you change the fields named `hashedPassword` and `salt`, and you have some verbose logging in your app, you'll want to scrub those fields from appearing in your logs. See the [Redaction](logger.md#redaction) docs for info. - -### Scaffolding Login/Signup/Forgot Password Pages - -If you don't want to create your own login, signup and forgot password pages from scratch we've got a generator for that: - - yarn rw g dbAuth - -The default routes will make them available at `/login`, `/signup`, `/forgot-password`, and `/reset-password` but that's easy enough to change. Again, check the post-install instructions for one change you need to make to those pages: where to redirect the user to once their login/signup is successful. - -If you'd rather create your own, you might want to start from the generated pages anyway as they'll contain the other code you need to actually submit the login credentials or signup fields to the server for processing. - -### Configuration - -Almost all config for dbAuth lives in `api/src/functions/auth.js` in the object you give to the `DbAuthHandler` initialization. The comments above each key will explain what goes where. Here's an overview of the more important options: - -#### login.handler() - -If you want to do something other than immediately let a user log in if their username/password is correct, you can add additional logic in `login.handler()`. For example, if a user's credentials are correct, but they haven't verified their email address yet, you can throw an error in this function with the appropriate message and then display it to the user. If the login should proceed, simply return the user that was passed as the only argument to the function: - -```jsx -login: { - handler: (user) => { - if (!user.verified) { - throw new Error('Please validate your email first!') - } else { - return user - } - } -} -``` - -#### signup.handler() - -This function should contain the code needed to actually create a user in your database. You will receive a single argument which is an object with all of the fields necessary to create the user (`username`, `hashedPassword` and `salt`) as well as any additional fields you included in your signup form in an object called `userAttributes`: - -```jsx -signup: { - handler: ({ username, hashedPassword, salt, userAttributes }) => { - return db.user.create({ - data: { - email: username, - hashedPassword: hashedPassword, - salt: salt, - name: userAttributes.name, - }, - }) - } -} -``` - -Before `signup.handler()` is invoked, dbAuth will check that the username is unique in the database and throw an error if not. - -There are three things you can do within this function depending on how you want the signup to proceed: - -1. If everything is good and the user should be logged in after signup: return the user you just created -2. If the user is safe to create, but you do not want to log them in automatically: return a string, which will be returned by the `signUp()` function you called after destructuring it from `useAuth()` (see code snippet below) -3. If the user should _not_ be able to sign up for whatever reason: throw an error in this function with the message to be displayed - -You can deal with case #2 by doing something like the following in a signup component/page: - -```jsx -const { signUp } = useAuth() - -const onSubmit = async (data) => { - const response = await signUp({ ...data }) - - if (response.message) { - toast.error(response.message) // user created, but not logged in - } else { - toast.success('Welcome!') // user created and logged in - navigate(routes.dashboard()) - } -} -``` - -#### forgotPassword.handler() - -This handler is invoked if a user is found with the username/email that they submitted on the Forgot Password page, and that user will be passed as an argument. Inside this function is where you'll send the user a link to reset their password—via an email is most common. The link will, by default, look like: - - https://example.com/reset-password?resetToken=${user.resetToken} - -If you changed the path to the Reset Password page in your routes you'll need to change it here. If you used another name for the `resetToken` database field, you'll need to change that here as well: - - https://example.com/reset-password?resetKey=${user.resetKey} - -#### resetPassword.handler() - -This handler is invoked after the password has been successfully changed in the database. Returning something truthy (like `return user`) will automatically log the user in after their password is changed. If you'd like to return them to the login page and make them log in manually, `return false` and redirect the user in the Reset Password page. - -#### Cookie config - -These options determine how the cookie that tracks whether the client is authorized is stored in the browser. The default configuration should work for most use cases. If you serve your web and api sides from different domains you'll need to make some changes: set `SameSite` to `None` and then add [CORS configuration](#cors-config). - -```jsx -cookie: { - HttpOnly: true, - Path: '/', - SameSite: 'Strict', - Secure: true, - // Domain: 'example.com', -} -``` - -#### CORS config - -If you're using dbAuth and your api and web sides are deployed to different domains then you'll need to configure CORS for both GraphQL in general and dbAuth. You'll also need to enable a couple of options to be sure and send/accept credentials in XHR requests. For more info, see the complete [CORS doc](cors.md#cors-and-authentication). - -#### Error Messages - -There are several error messages that can be displayed, including: - -- Username/email not found -- Incorrect password -- Expired reset password token - -We've got some default error messages that sound nice, but may not fit the tone of your site. You can customize these error messages in `api/src/functions/auth.js` in the `errors` prop of each of the `login`, `signup`, `forgotPassword` and `resetPassword` config objects. The generated file contains tons of comments explaining when each particular error message may be shown. - -### Environment Variables - -#### Cookie Domain - -By default, the session cookie will not have the `Domain` property set, which a browser will default to be the [current domain only](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#define_where_cookies_are_sent). If your site is spread across multiple domains (for example, your site is at `example.com` but your api-side is deployed to `api.example.com`) you'll need to explicitly set a Domain so that the cookie is accessible to both. - -To do this, create an environment variable named `DBAUTH_COOKIE_DOMAIN` set to the root domain of your site, which will allow it to be read by all subdomains as well. For example: - - DBAUTH_COOKIE_DOMAIN=example.com - -#### Session Secret Key - -If you need to change the secret key that's used to encrypt the session cookie, or deploy to a new target (each deploy environment should have its own unique secret key) we've got a CLI tool for creating a new one: - - yarn rw g secret - -Note that the secret that's output is _not_ appended to your `.env` file or anything else, it's merely output to the screen. You'll need to put it in the right place after that. - -> The `.env` file is set to be ignored by git and not committed to version control. There is another file, `.env.defaults`, which is meant to be safe to commit and contain simple ENV vars that your dev team can share. The encryption key for the session cookie is NOT one of these shareable vars! - -## Third Party Providers Installation and Setup - -You will need to instantiate your authentication client and pass it to the ``. See instructions below for your specific provider. - -### Netlify Identity Widget - -+++ View Installation and Setup - -#### Installation - -The following CLI command will install required packages and generate boilerplate code and files for Redwood Projects: - -```bash -yarn rw setup auth netlify -``` - -_If you prefer to manually install the package and add code_, run the following command and then add the required code provided in the next section. - -```bash -cd web -yarn add @redwoodjs/auth netlify-identity-widget -``` - -#### Setup - -You will need to enable Identity on your Netlify site. - - -```jsx title="web/src/App.js" -import { AuthProvider } from '@redwoodjs/auth' -import netlifyIdentity from 'netlify-identity-widget' -import { isBrowser } from '@redwoodjs/prerender/browserUtils' -import { FatalErrorBoundary } from '@redwoodjs/web' -import { RedwoodApolloProvider } from '@redwoodjs/web/apollo' - -import FatalErrorPage from 'src/pages/FatalErrorPage' -import Routes from 'src/Routes' - -import './index.css' - -isBrowser && netlifyIdentity.init() - -const App = () => ( - - - - - - - -) - -export default App -``` - -#### Netlify Identity Auth Provider Specific Setup - -See the Netlify Identity information within this doc's [Auth Provider Specific Integration](#auth-provider-specific-integration) section. - -+++ - -### GoTrue-JS - -+++ View Installation and Setup - -#### Installation - -The following CLI command will install required packages and generate boilerplate code and files for Redwood Projects: - -```bash -yarn rw setup auth goTrue -``` - -_If you prefer to manually install the package and add code_, run the following command and then add the required code provided in the next section. - -```bash -cd web -yarn add @redwoodjs/auth gotrue-js -``` - -#### Setup - -You will need to enable Identity on your Netlify site. - - -Add the GoTrue-JS package to the web side: - -```bash -yarn workspace web add gotrue-js -``` - -Instantiate GoTrue and pass in your configuration. Be sure to set APIUrl to the API endpoint found in your Netlify site's Identity tab: - -```jsx title="web/src/App.js" -import { AuthProvider } from '@redwoodjs/auth' -import GoTrue from 'gotrue-js' -import { FatalErrorBoundary } from '@redwoodjs/web' -import { RedwoodApolloProvider } from '@redwoodjs/web/apollo' - -import FatalErrorPage from 'src/pages/FatalErrorPage' -import Routes from 'src/Routes' - -import './index.css' - -const goTrueClient = new GoTrue({ - APIUrl: 'https://MYAPP.netlify.app/.netlify/identity', - setCookie: true, -}) - -const App = () => ( - - - - - - - -) - -export default App -``` - -+++ - -### Auth0 - -+++ View Installation and Setup - -#### Installation - -The following CLI command will install required packages and generate boilerplate code and files for Redwood Projects: - -```bash -yarn rw setup auth auth0 -``` - -_If you prefer to manually install the package and add code_, run the following command and then add the required code provided in the next section. - -```bash -cd web -yarn add @redwoodjs/auth @auth0/auth0-spa-js -``` - -#### Setup - -To get your application keys, only complete the ["Configure Auth0"](https://auth0.com/docs/quickstart/spa/react#get-your-application-keys) section of the SPA Quickstart guide. - -**NOTE** If you're using Auth0 with Redwood then you must also [create an API](https://auth0.com/docs/quickstart/spa/react/02-calling-an-api#create-an-api) and set the audience parameter, or you'll receive an opaque token instead of the required JWT token. - -The `useRefreshTokens` options is required for automatically extending sessions beyond that set in the initial JWT expiration (often 3600/1 hour or 86400/1 day). - -If you want to allow users to get refresh tokens while offline, you must also enable the Allow Offline Access switch in your Auth0 API Settings as part of setup configuration. See: [https://auth0.com/docs/tokens/refresh-tokens](https://auth0.com/docs/tokens/refresh-tokens) - -You can increase security by using refresh token rotation which issues a new refresh token and invalidates the predecessor token with each request made to Auth0 for a new access token. - -Rotating the refresh token reduces the risk of a compromised refresh token. For more information, see: [https://auth0.com/docs/tokens/refresh-tokens/refresh-token-rotation](https://auth0.com/docs/tokens/refresh-tokens/refresh-token-rotation). - -> **Including Environment Variables in Serverless Deployment:** in addition to adding the following env vars to your deployment hosting provider, you _must_ take an additional step to include them in your deployment build process. Using the names exactly as given below, follow the instructions in [this document](environment-variables.md) to include them in your `redwood.toml`. - -```jsx title="web/src/App.js" -import { AuthProvider } from '@redwoodjs/auth' -import { Auth0Client } from '@auth0/auth0-spa-js' -import { FatalErrorBoundary } from '@redwoodjs/web' -import { RedwoodApolloProvider } from '@redwoodjs/web/apollo' - -import FatalErrorPage from 'src/pages/FatalErrorPage' -import Routes from 'src/Routes' - -import './index.css' - -const auth0 = new Auth0Client({ - domain: process.env.AUTH0_DOMAIN, - client_id: process.env.AUTH0_CLIENT_ID, - redirect_uri: process.env.AUTH0_REDIRECT_URI, - - // ** NOTE ** Storing tokens in browser local storage provides persistence across page refreshes and browser tabs. - // However, if an attacker can achieve running JavaScript in the SPA using a cross-site scripting (XSS) attack, - // they can retrieve the tokens stored in local storage. - // https://auth0.com/docs/libraries/auth0-spa-js#change-storage-options - cacheLocation: 'localstorage', - audience: process.env.AUTH0_AUDIENCE, - - // @MARK: useRefreshTokens is required for automatically extending sessions - // beyond that set in the initial JWT expiration. - // - // @MARK: https://auth0.com/docs/tokens/refresh-tokens - // useRefreshTokens: true, -}) - -const App = () => ( - - - - - - - -) - -export default App -``` - -#### Login and Logout Options - -When using the Auth0 client, `login` and `logout` take `options` that can be used to override the client config: - -- `returnTo`: a permitted logout url set in Auth0 -- `redirectTo`: a target url after login - -The latter is helpful when an unauthenticated user visits a Private route, but then is redirected to the `unauthenticated` route. The Redwood router will place the previous requested path in the pathname as a `redirectTo` parameter which can be extracted and set in the Auth0 `appState`. That way, after successfully logging in, the user will be directed to this `targetUrl` rather than the config's callback. - -```jsx -const UserAuthTools = () => { - const { loading, isAuthenticated, logIn, logOut } = useAuth() - - if (loading) { - // auth is rehydrating - return null - } - - return ( - - ) -} -``` - -#### Auth0 Auth Provider Specific Setup - -See the Auth0 information within this doc's [Auth Provider Specific Integration](#auth-provider-specific-integration) section. - -+++ - -### Clerk - -+++ View Installation and Setup - -#### Installation - -The following CLI command will install required packages and generate boilerplate code and files for Redwood Projects: - -```bash -yarn rw setup auth clerk -``` - -#### Setup - -To get started with Clerk, sign up on [their website](https://clerk.dev/) and create an application, or follow their [RedwoodJS Blog Tutorial with Clerk](https://clerk.dev/tutorials/redwoodjs-blog-tutorial-with-clerk) that has an [example repo](https://github.com/redwoodjs/redwood-tutorial) already setup. - -It's important that the `ClerkAuthProvider` added to your `App.{js|ts}` file during setup is within the `RedwoodProvider` and around Redwood's `AuthProvider`: - -```tsx {4,10} title="web/src/App.{js|ts}" -const App = () => ( - - - - - - - - - - - -) -``` - -The [RedwoodJS Blog Tutorial with Clerk](https://clerk.dev/tutorials/redwoodjs-blog-tutorial-with-clerk) also explains how to use `@clerk/clerk-react` components with Redwood's `useAuth()` hook: - -```tsx -import { UserButton, SignInButton } from '@clerk/clerk-react' - -// ... - -{ - isAuthenticated ? ( - - ) : ( - - - - ) -} -``` - -Applications in Clerk have different instances. By default, there's one for development, one for staging, and one for production. You'll need to pull three values from one of these instances. We recommend storing the development values in your local `.env` file and using the staging and production values in the appropriate env setups for your hosting platform when you deploy. - -The three values you'll need from Clerk are your instance's "Frontend API Key" url, a "Backend API key" and a "JWT verification key", all from your instance's settings under "API Keys". The Frontend API url should be stored in an env variable named `CLERK_FRONTEND_API_URL`. The Backend API key should be named `CLERK_API_KEY`. Finally, the JWT key should be named `CLERK_JWT_KEY` - -Otherwise, feel free to configure your instances however you wish with regards to their appearance and functionality. - -> **Including Environment Variables in Serverless Deploys** -> -> In addition to adding these env vars to your local `.env` file or deployment hosting provider, you _must_ take an additional step to include them in your deployment build process. Using the names exactly as given above, follow the instructions in [this document](environment-variables.md). You need to expose the `CLERK_FRONTEND_API_URL` variable to the `web` side. - -#### Login and Logout Options - -When using the Clerk client, `login` and `signUp` take an `options` object that can be used to override the client config. - -For `login` the `options` may contain all the options listed at the Clerk [props documentation for login](https://docs.clerk.dev/reference/clerkjs/clerk#signinprops). - -For `signUp` the `options` may contain all the options listed at the Clerk [props documentation for signup](https://docs.clerk.dev/reference/clerkjs/clerk#signupprops). - -#### Avoiding Feature Duplication Confusion - -Redwood's integration of Clerk is based on [Clerk's React SDK](https://docs.clerk.dev/reference/clerk-react). This means there is some duplication between the features available through that SDK and the ones available in the `@redwoodjs/auth` package - such as the alternatives of using Clerk's `SignedOut` component to redirect users away from a private page vs. using Redwood's `Private` route wrapper. In general, we would recommend you use the **Redwood** way of doing things when possible, as that is more likely to function harmoniously with the rest of Redwood. That being said, though, there are some great features in Clerk's SDK that you will be able to now use in your app, such as the `UserButton` and `UserProfile` components. - -+++ - -### Azure Active Directory - -+++ View Installation and Setup - -#### Installation - -The following CLI command will install required packages and generate boilerplate code and files for Redwood Projects: - -```bash -yarn rw setup auth azureActiveDirectory -``` - -_If you prefer to manually install the package and add code_, run the following command and then add the required code provided in the next section. - -```bash -cd web -yarn add @azure/msal-browser -``` - -#### Setup - -To get your application credentials, create an [App Registration](https://docs.microsoft.com/en-us/azure/active-directory/develop/scenario-spa-app-registration) using in your Azure Active Directory tenant and make sure you configure as a [MSAL.js 2.0 with auth code flow](https://docs.microsoft.com/en-us/azure/active-directory/develop/scenario-spa-app-registration#redirect-uri-msaljs-20-with-auth-code-flow) registration. Take a note of your generated _Application ID_ (client), and the _Directory ID_ (tenant). - -[Learn more about authorization code flow](https://docs.microsoft.com/en-us/azure/active-directory/develop/reference-third-party-cookies-spas). - -##### Redirect URIs - -Enter allowed redirect urls for the integrations, e.g. `http://localhost:8910/login`. This will be the `AZURE_ACTIVE_DIRECTORY_REDIRECT_URI` environment variable, and suggestively `AZURE_ACTIVE_DIRECTORY_LOGOUT_REDIRECT_URI`. - -#### Authority - -The Authority is a URL that indicates a directory that MSAL can request tokens from which you can read about [here](https://docs.microsoft.com/en-us/azure/active-directory/develop/msal-client-application-configuration#authority). However, you most likely want to have e.g. `https://login.microsoftonline.com/` as Authority URL, where `` is the Azure Active Directory tenant id. This will be the `AZURE_ACTIVE_DIRECTORY_AUTHORITY` environment variable. - -```jsx title="web/src/App.js" -import { AuthProvider } from '@redwoodjs/auth' -import { PublicClientApplication } from '@azure/msal-browser' -import { FatalErrorBoundary } from '@redwoodjs/web' -import { RedwoodApolloProvider } from '@redwoodjs/web/apollo' - -import FatalErrorPage from 'src/pages/FatalErrorPage' -import Routes from 'src/Routes' - -import './index.css' - -const azureActiveDirectoryClient = new PublicClientApplication({ - auth: { - clientId: process.env.AZURE_ACTIVE_DIRECTORY_CLIENT_ID, - authority: process.env.AZURE_ACTIVE_DIRECTORY_AUTHORITY, - redirectUri: process.env.AZURE_ACTIVE_DIRECTORY_REDIRECT_URI, - postLogoutRedirectUri: process.env.AZURE_ACTIVE_DIRECTORY_LOGOUT_REDIRECT_URI, - }, -}) - -const App = () => ( - - - - - - - -) - -export default App -``` - -#### Roles - -To setup your App Registration with custom roles and have them exposed via the `roles` claim, follow [this documentation](https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-add-app-roles-in-azure-ad-apps). - -#### Login Options - -Options in method `logIn(options?)` is of type [RedirectRequest](https://azuread.github.io/microsoft-authentication-library-for-js/ref/modules/_azure_msal_browser.html#redirectrequest) and is a good place to pass in optional [scopes](https://docs.microsoft.com/en-us/graph/permissions-reference#user-permissions) to be authorized. By default, MSAL sets `scopes` to [/.default](https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent#the-default-scope) which is built in for every application that refers to the static list of permissions configured on the application registration. Furthermore, MSAL will add `openid` and `profile` to all requests. In example below we explicit include `User.Read.All` to the login scope. - -```jsx -await logIn({ - scopes: ['User.Read.All'], // becomes ['openid', 'profile', 'User.Read.All'] -}) -``` - -See [loginRedirect](https://azuread.github.io/microsoft-authentication-library-for-js/ref/classes/_azure_msal_browser.publicclientapplication.html#loginredirect), [PublicClientApplication](https://azuread.github.io/microsoft-authentication-library-for-js/ref/classes/_azure_msal_browser.publicclientapplication.html) class and [Scopes Behavior](https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-core/docs/scopes.md#scopes-behavior) for more documentation. - -#### getToken Options - -Options in method `getToken(options?)` is of type [RedirectRequest](https://azuread.github.io/microsoft-authentication-library-for-js/ref/modules/_azure_msal_browser.html#redirectrequest). By default, `getToken` will be called with scope `['openid', 'profile']`. As Azure Active Directory apply [incremental consent](https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/resources-and-scopes.md#dynamic-scopes-and-incremental-consent), we can extend the permissions from the login example by including another scope, for example `Mail.Read`. - -```jsx -await getToken({ - scopes: ['Mail.Read'], // becomes ['openid', 'profile', 'User.Read.All', 'Mail.Read'] -}) -``` - -See [acquireTokenSilent](https://azuread.github.io/microsoft-authentication-library-for-js/ref/classes/_azure_msal_browser.publicclientapplication.html#acquiretokensilent), [Resources and Scopes](https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/resources-and-scopes.md#resources-and-scopes) or [full class documentation](https://pub.dev/documentation/msal_js/latest/msal_js/PublicClientApplication-class.html#constructors) for more documentation. - -+++ - -### Magic.Link - -+++ View Installation and Setup - -#### Installation - -The following CLI command will install required packages and generate boilerplate code and files for Redwood Projects: - -```bash -yarn rw setup auth magicLink -``` - -_If you prefer to manually install the package and add code_, run the following command and then add the required code provided in the next section. - -```bash -cd web -yarn add @redwoodjs/auth magic-sdk -``` - -#### Setup - -To get your application keys, go to [dashboard.magic.link](https://dashboard.magic.link/) then navigate to the API keys add them to your `.env`. - -> **Including Environment Variables in Serverless Deployment:** in addition to adding the following env vars to your deployment hosting provider, you _must_ take an additional step to include them in your deployment build process. Using the names exactly as given below, follow the instructions in [this document](environment-variables.md) to "Whitelist them in your `redwood.toml`". - -```jsx title="web/src/App.js|tsx" -import { useAuth, AuthProvider } from '@redwoodjs/auth' -import { Magic } from 'magic-sdk' -import { FatalErrorBoundary } from '@redwoodjs/web' -import { RedwoodApolloProvider } from '@redwoodjs/web/apollo' - -import FatalErrorPage from 'src/pages/FatalErrorPage' -import Routes from 'src/Routes' - -import './index.css' - -const m = new Magic(process.env.MAGICLINK_PUBLIC) - -const App = () => ( - - - - - - - -) - -export default App -``` - -```jsx title="web/src/Routes.js|tsx" -import { useAuth } from '@redwoodjs/auth' -import { Router, Route } from '@redwoodjs/router' - -const Routes = () => { - return ( - - - - - ) -} - -export default Routes -``` - -#### Magic.Link Auth Provider Specific Integration - -See the Magic.Link information within this doc's [Auth Provider Specific Integration](#auth-provider-specific-integration) section. -+++ - -### Firebase - -+++ View Installation and Setup - -#### Installation - -The following CLI command will install required packages and generate boilerplate code and files for Redwood Projects: - -```bash -yarn rw setup auth firebase -``` - -#### Setup - -We're using [Firebase Google Sign-In](https://firebase.google.com/docs/auth/web/google-signin), so you'll have to follow the ["Before you begin"](https://firebase.google.com/docs/auth/web/google-signin#before_you_begin) steps in this guide. **Only** follow the "Before you begin" parts. - -> **Including Environment Variables in Serverless Deployment:** in addition to adding the following env vars to your deployment hosting provider, you _must_ take an additional step to include them in your deployment build process. Using the names exactly as given below, follow the instructions in [this document](https://redwoodjs.com/docs/environment-variables) to "Whitelist them in your `redwood.toml`". - -```jsx title="web/src/App.js" -import { AuthProvider } from '@redwoodjs/auth' -import { initializeApp, getApps, getApp } from '@firebase/app' -import * as firebaseAuth from '@firebase/auth' -import { FatalErrorBoundary } from '@redwoodjs/web' -import { RedwoodApolloProvider } from '@redwoodjs/web/apollo' - -import FatalErrorPage from 'src/pages/FatalErrorPage' -import Routes from 'src/Routes' - -import './index.css' - -const firebaseClientConfig = { - apiKey: process.env.FIREBASE_API_KEY, - authDomain: process.env.FIREBASE_AUTH_DOMAIN, - databaseURL: process.env.FIREBASE_DATABASE_URL, - projectId: process.env.FIREBASE_PROJECT_ID, - storageBucket: process.env.FIREBASE_STORAGE_BUCKET, - messagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID, - appId: process.env.FIREBASE_APP_ID, -} - -const firebaseApp = ((config) => { - const apps = getApps() - if (!apps.length) { - initializeApp(config) - } - return getApp() -})(firebaseConfig) - -export const firebaseClient = { - firebaseAuth, - firebaseApp, -} - -const App = () => ( - - - - - - - -) - -export default App -``` - -#### Usage - -```jsx -const UserAuthTools = () => { - const { loading, isAuthenticated, logIn, logOut } = useAuth() - - if (loading) { - return null - } - - return ( - - ) -} -``` - -#### Firebase Auth Provider Specific Integration - -See the Firebase information within this doc's [Auth Provider Specific Integration](#auth-provider-specific-integration) section. -+++ - -### Supabase - -+++ View Installation and Setup - -#### Installation - -The following CLI command will install required packages and generate boilerplate code and files for Redwood Projects: - -```bash -yarn rw setup auth supabase -``` - -#### Setup - -Update your .env file with the following settings supplied when you created your new Supabase project: - -- `SUPABASE_URL` with the unique Supabase URL for your project -- `SUPABASE_KEY` with the unique Supabase Key that identifies which API KEY to use -- `SUPABASE_JWT_SECRET` with the secret used to sign and verify the JSON Web Token (JWT) - -You can find these values in your project's dashboard under Settings -> API. - -For full Supabase documentation, see: - -#### Usage - -Supabase supports several sign in methods: - -- email/password -- passwordless via emailed magiclink -- authenticate via phone with SMS based OTP (One-Time Password) tokens. See: [SMS OTP with Twilio](https://supabase.io/docs/guides/auth/auth-twilio) -- Sign in with redirect. You can control where the user is redirected to after they are logged in via a `redirectTo` option. -- Sign in with a valid refresh token that was returned on login. -- Sign in using third-party providers/OAuth via - - [Apple](https://supabase.io/docs/guides/auth/auth-apple) - - Azure Active Directory - - [Bitbucket](https://supabase.io/docs/guides/auth/auth-bitbucket) - - [Discord](https://supabase.io/docs/guides/auth/auth-discord) - - [Facebook](https://supabase.io/docs/guides/auth/auth-facebook) - - [GitHub](https://supabase.io/docs/guides/auth/auth-github) - - [GitLab](https://supabase.io/docs/guides/auth/auth-gitlab) - - [Google](https://supabase.io/docs/guides/auth/auth-google) - - [Twitch](https://supabase.io/docs/guides/auth/auth-twitch) - - [Twitter](https://supabase.io/docs/guides/auth/auth-twitter) -- Sign in with a [valid refresh token](https://supabase.io/docs/reference/javascript/auth-signin#sign-in-using-a-refresh-token-eg-in-react-native) that was returned on login. Used e.g. in React Native. -- Sign in with scopes. If you need additional data from an OAuth provider, you can include a space-separated list of `scopes` in your request options to get back an OAuth `provider_token`. - -Depending on the credentials provided: - -- A user can sign up either via email or sign in with supported OAuth provider: `'apple' | 'azure' | 'bitbucket' | 'discord' | 'facebook' | 'github' | 'gitlab' | 'google' | 'twitch' | 'twitter'` -- If you sign in with a valid refreshToken, the current user will be updated -- If you provide email without a password, the user will be sent a magic link. -- The magic link's destination URL is determined by the SITE_URL config variable. To change this, you can go to Authentication -> Settings on `app.supabase.io` for your project. -- Specifying an OAuth provider will open the browser to the relevant login page -- Note: You must enable and configure the OAuth provider appropriately. To configure these providers, you can go to Authentication -> Settings on `app.supabase.io` for your project. -- Note: To authenticate using SMS based OTP (One-Time Password) you will need a [Twilio](https://www.twilio.com/try-twilio) account - -For Supabase Authentication documentation, see: - -+++ - -### Ethereum - -+++ View Installation and Setup - -#### Installation - -The following CLI command will install required packages and generate boilerplate code and files for Redwood Projects: - -```bash -yarn rw setup auth ethereum -``` - -#### Setup - -To complete setup, you'll also need to update your `api` server manually. See https://github.com/oneclickdapp/ethereum-auth for instructions. - -+++ - -### Nhost - -+++ View Installation and Setup - -#### Installation - -The following CLI command will install required packages and generate boilerplate code and files for Redwood Projects: - -```bash -yarn rw setup auth nhost -``` - -#### Setup - -Update your .env file with the following setting which can be found on your Nhost project's dashboard. +- Magic Links - Magic.js _([Repo on GitHub](https://github.com/MagicHQ/magic-js))_ +- Netlify Identity _([Repo on GitHub](https://github.com/netlify/netlify-identity-widget))_ +- Netlify GoTrue-JS _([Repo on GitHub](https://github.com/netlify/gotrue-js))_ +- Nhost _([Documentation Website](https://docs.nhost.io/platform/authentication))_ +- Supabase _([Documentation Website](https://supabase.io/docs/guides/auth))_ +- WalletConnect _([Repo on GitHub](https://github.com/oneclickdapp/ethereum-auth))_ -- `NHOST_BACKEND_URL` with the unique Nhost Backend URL that can be found in the app's dashboard. -- `NHOST_JWT_SECRET` with the JWT Key secret that you have set in your project's Settings > Hasura "JWT Key" section. +You can also implement your own custom auth client. Check out the [Custom docs](auth/custom) for more info. -#### Usage +:::info Auth Playground -Nhost supports the following methods: +Check out the [Auth Playground](https://redwood-playground-auth.netlify.app/) for examples of the auth experience with each provider or check out [the source code](https://redwood-playground-auth.netlify.app/). -- email/password -- passwordless with email -- passwordless with SMS -- OAuth Providers (via GitHub, Google, Facebook, Spotify, Discord, Twitch, Apple, Twitter, Microsoft and Linkedin). +::: -Depending on the credentials provided: +## Auth Installation and Setup -- A user can sign in either via email or a supported OAuth provider. -- A user can sign up via email and password. For OAuth simply sign in and the user account will be created if it does not exist. -- Note: You must enable and configure the OAuth provider appropriately. To enable and configure a provider, please navigate to Users -> Login settings, from your app's dashboard. +You will need to instantiate your authentication client and pass it to the ``. See individual auth docs in the menu for your specific provider. -For the docs on Authentication, see: +## Authentication on the Web Side -If you are also **using Nhost as your GraphQL API server**, you will need to pass `skipFetchCurrentUser` as a prop to `AuthProvider` , as follows: +Once your auth provider is set up you'll get access to the various authentication variables and functions by destructuring them from the `useAuth` hook: ```jsx - -``` - -This avoids having an additional request to fetch the current user which is meant to work with Apollo Server and Prisma. - -Important: The `skipFetchCurrentUser` attribute is **only** needed if you are **not** using the standard RedwoodJS api side GraphQL Server. -+++ - -### Custom - -+++ View Installation and Setup - -#### Installation - -The following CLI command (not implemented, see https://github.com/redwoodjs/redwood/issues/1585) will install required packages and generate boilerplate code and files for Redwood Projects: +import { useAuth } from '@redwoodjs/auth' -```bash -yarn rw setup auth custom +export const MyComponent = () => { + const { currentUser, isAuthenticated, logIn, logOut } = useAuth() + + return ( +
    +
  • The current user is: {currentUser}
  • +
  • Is the user logged in? {isAuthenticated}
  • +
  • Click to
  • +
  • Click to
  • +
+ ) +} ``` -#### Setup - -It is possible to implement a custom provider for Redwood Auth. In which case you might also consider adding the provider to Redwood itself. - -If you are trying to implement your own auth, support is very early and limited at this time. Additionally, there are many considerations and responsibilities when it comes to managing custom auth. For most cases we recommend using an existing provider. - -However, there are examples contributed by developers in the Redwood forums and Discord server. - -The most complete example (although now a bit outdated) is found in [this forum thread](https://community.redwoodjs.com/t/custom-github-jwt-auth-with-redwood-auth/610). Here's another [helpful message in the thread](https://community.redwoodjs.com/t/custom-github-jwt-auth-with-redwood-auth/610/25). -+++ - -## API - -The following values are available from the `useAuth` hook: +The following variables and functions are available from the `useAuth` hook: - async `logIn(options?)`: Differs based on the client library, with Netlify Identity a pop-up is shown, and with Auth0 the user is redirected. Options are passed to the client. - async `logOut(options?)`: Log the current user out. Options are passed to the client. @@ -992,447 +66,29 @@ The following values are available from the `useAuth` hook: - `hasRole(['admin'])`: Determines if the current user is assigned a role like `"admin"` or assigned to any of the roles in a list such as `['editor', 'author']`. - `loading`: The auth state is restored asynchronously when the user visits the site for the first time, use this to determine if you have the correct state. -## Usage in Redwood - -Redwood provides a zeroconf experience when using our Auth package! - -### GraphQL Query and Mutations - -GraphQL requests automatically receive an `Authorization` JWT header when a user is authenticated. - -### Auth Provider API - -If a user is signed in, the `Authorization` token is verified, decoded and available in `context.currentUser` - -```jsx -import { context } from '@redwoodjs/api' - -console.log(context.currentUser) -// { -// sub: ' -// email: 'user@example.com', -// [...] -// } -``` - -You can map the "raw decoded JWT" into a real user object by passing a `getCurrentUser` function to `createGraphQLHandler` - -Our recommendation is to create a `src/lib/auth.js|ts` file that exports a `getCurrentUser`. (Note: You may already have stub functions.) - -```jsx -import { getCurrentUser } from 'src/lib/auth' -// Example: -// export const getCurrentUser = async (decoded) => { -// return await db.user.findUnique({ where: { decoded.email } }) -// } -// - -export const handler = createGraphQLHandler({ - schema: makeMergedSchema({ - schemas, - services: makeServices({ services }), - }), - getCurrentUser, -}) -``` - -The value returned by `getCurrentUser` is available in `context.currentUser` - -Use `requireAuth` in your services to check that a user is logged in, -whether or not they are assigned a role, and optionally raise an -error if they're not. - -```jsx -export const requireAuth = ({ roles }) => { - if (!isAuthenticated()) { - throw new AuthenticationError("You don't have permission to do that.") - } - - if (roles && !hasRole(roles)) { - throw new ForbiddenError("You don't have access to do that.") - } -}} -} -``` - -### Auth Provider Specific Integration - -#### Auth0 - -If you're using Auth0 you must also [create an API](https://auth0.com/docs/quickstart/spa/react/02-calling-an-api#create-an-api) and set the audience parameter, or you'll receive an opaque token instead of a JWT token, and Redwood expects to receive a JWT token. - -+++ View Auth0 Options - -#### Role-based access control (RBAC) in Auth0 - -[Role-based access control (RBAC)](https://auth0.com/docs/authorization/concepts/rbac) refers to the idea of assigning permissions to users based on their role within an organization. It provides fine-grained control and offers a simple, manageable approach to access management that is less prone to error than assigning permissions to users individually. - -Essentially, a role is a collection of permissions that you can apply to users. A role might be "admin", "editor" or "publisher". This differs from permissions an example of which might be "publish:blog". - -#### App metadata in Auth0 - -Auth0 stores information (such as, support plan subscriptions, security roles, or access control groups) in `app_metadata`. Data stored in `app_metadata` cannot be edited by users. - -Create and manage roles for your application in Auth0's "User & Role" management views. You can then assign these roles to users. - -However, that info is not immediately available on the user's `app_metadata` or to RedwoodJS when authenticating. - -If you assign your user the "admin" role in Auth0, you will want your user's `app_metadata` to look like: - -``` -{ - "roles": [ - "admin" - ] -} -``` - -To set this information and make it available to RedwoodJS, you can use [Auth0 Rules](https://auth0.com/docs/rules). - -#### Auth0 Rules for App Metadata - -RedwoodJS needs `app_metadata` to 1) contain the role information and 2) be present in the JWT that is decoded. - -To accomplish these tasks, you can use [Auth0 Rules](https://auth0.com/docs/rules) to add them as custom claims on your JWT. - -#### Add Authorization Roles to App Metadata Rule - -Your first rule will `Add Authorization Roles to App Metadata`. - -```jsx -/// Add Authorization Roles to App Metadata -function (user, context, callback) { - auth0.users.updateAppMetadata(user.user_id, context.authorization) - .then(function(){ - callback(null, user, context); - }) - .catch(function(err){ - callback(err); - }); - } -``` - -Auth0 exposes the user's roles in `context.authorization`. This rule simply copies that information into the user's `app_metadata`, such as: - -``` -{ - "roles": [ - "admin" - ] -} -``` - -However, now you must include the `app_metadata` on the user's JWT that RedwoodJS will decode. - -#### Add App Metadata to JWT Rule in Auth0 - -Therefore, your second rule will `Add App Metadata to JWT`. - -You can add `app_metadata` to the `idToken` or `accessToken`. - -Adding to `idToken` will make the make app metadata accessible to RedwoodJS `getUserMetadata` which for Auth0 calls the auth client's `getUser`. - -Adding to `accessToken` will make the make app metadata accessible to RedwoodJS when decoding the JWT via `getToken`. - -While adding to `idToken` is optional, you _must_ add to `accessToken`. - -To keep your custom claims from colliding with any reserved claims or claims from other resources, you must give them a [globally unique name using a namespaced format](https://auth0.com/docs/tokens/guides/create-namespaced-custom-claims). Otherwise, Auth0 will _not_ add the information to the token(s). - -Therefore, with a namespace of "https://example.com", the `app_metadata` on your token should look like: - -```jsx -"https://example.com/app_metadata": { - "authorization": { - "roles": [ - "admin" - ] - } -}, -``` - -To set this namespace information, use the following function in your rule: - -```jsx -function (user, context, callback) { - var namespace = 'https://example.com/'; - - // adds to idToken, i.e. userMetadata in RedwoodJS - context.idToken[namespace + 'app_metadata'] = {}; - context.idToken[namespace + 'app_metadata'].authorization = { - groups: user.app_metadata.groups, - roles: user.app_metadata.roles, - permissions: user.app_metadata.permissions - }; - - context.idToken[namespace + 'user_metadata'] = {}; - - // accessToken, i.e. the decoded JWT in RedwoodJS - context.accessToken[namespace + 'app_metadata'] = {}; - context.accessToken[namespace + 'app_metadata'].authorization = { - groups: user.app_metadata.groups, - roles: user.app_metadata.roles, - permissions: user.app_metadata.permissions - }; - - context.accessToken[namespace + 'user_metadata'] = {}; - - return callback(null, user, context); -} -``` - -Now, your `app_metadata` with `authorization` and `role` information will be on the user's JWT after logging in. - -#### Add Application hasRole Support in Auth0 - -If you intend to support, RBAC then in your `api/src/lib/auth.js` you need to extract `roles` using the `parseJWT` utility and set these roles on `currentUser`. - -If your roles are on a namespaced `app_metadata` claim, then `parseJWT` provides an option to provide this value. - -```jsx title="api/src/lib/auth.js" -const NAMESPACE = 'https://example.com' - -const currentUserWithRoles = async (decoded) => { - const currentUser = await userByUserId(decoded.sub) - return { - ...currentUser, - roles: parseJWT({ decoded: decoded, namespace: NAMESPACE }).roles, - } -} - -export const getCurrentUser = async (decoded, { type, token }) => { - try { - requireAccessToken(decoded, { type, token }) - return currentUserWithRoles(decoded) - } catch (error) { - return decoded - } -} -``` - -+++ +### Role Protection -#### Magic.Link - -The Redwood API does not include the functionality to decode Magic.link authentication tokens, so the client is initiated and decodes the tokens inside of `getCurrentUser`. - -+++ View Magic.link Options - -##### Installation - -First, you must manually install the **Magic Admin SDK** in your project's `api/package.json`. - -```bash -yarn workspace api add @magic-sdk/admin -``` - -##### Setup - -To get your application running _without setting up_ `Prisma`, get your `SECRET KEY` from [dashboard.magic.link](https://dashboard.magic.link/). Then add `MAGICLINK_SECRET` to your `.env`. - -```jsx title="redwood/api/src/lib/auth.js|ts" -import { Magic } from '@magic-sdk/admin' - -export const getCurrentUser = async (_decoded, { token }) => { - const mAdmin = new Magic(process.env.MAGICLINK_SECRET) - - return await mAdmin.users.getMetadataByToken(token) -} -``` - -Magic.link recommends using the issuer as the userID to retrieve user metadata via `Prisma` - -```jsx title="redwood/api/src/lib/auth.ts" -import { Magic } from '@magic-sdk/admin' - -export const getCurrentUser = async (_decoded, { token }) => { - const mAdmin = new Magic(process.env.MAGICLINK_SECRET) - const { email, publicAddress, issuer } = await mAdmin.users.getMetadataByToken(token) - - return await db.user.findUnique({ where: { issuer } }) -} -``` - -+++ - -#### Firebase - -You must follow the ["Before you begin"](https://firebase.google.com/docs/auth/web/google-signin) part of the "Authenticate Using Google Sign-In with JavaScript" guide. - -+++ View Firebase Options - -#### Role-based access control (RBAC) in Firebase - -Requires a custom implementation. - -#### App metadata in Firebase - -None. - -#### Add Application hasRole Support in Firebase - -Requires a custom implementation. - -#### Auth Providers - -Providers can be configured by specifying `logIn(provider)` and `signUp(provider)`, where `provider` is a **string** of one of the supported providers. - -Supported providers: - -- google.com (Default) -- facebook.com -- github.com -- twitter.com -- microsoft.com -- apple.com - -#### Email & Password Auth in Firebase - -Email/password authentication is supported by calling `login({ email, password })` and `signUp({ email, password })`. - -#### Email link (passwordless sign-in) in Firebase - -In Firebase Console, you must enable "Email link (passwordless sign-in)" with the configuration toggle for the email provider. The authentication sequence for passwordless email links has two steps: - -1. First, an email with the link must be generated and sent to the user. Either using using firebase client sdk (web side) [sendSignInLinkToEmail()](https://firebase.google.com/docs/reference/js/auth.emailauthprovider#example_2_2), which generates the link and sends the email to the user on behalf of your application or alternatively, generate the link using backend admin sdk (api side), see ["Generate email link for sign-in](https://firebase.google.com/docs/auth/admin/email-action-links#generate_email_link_for_sign-in) but it is then your responsibility to send an email to the user containing the link. -2. Second, authentication is completed when the user is redirected back to the application and the AuthProvider's logIn({emailLink, email, providerId: 'emailLink'}) method is called. - -For example, users could be redirected to a dedicated route/page to complete authentication: - -```jsx -import { useEffect } from 'react' -import { Redirect, routes } from '@redwoodjs/router' -import { useAuth } from '@redwoodjs/auth' - -const EmailSigninPage = () => { - const { loading, hasError, error, logIn } = useAuth() - - const email = window.localStorage.getItem('emailForSignIn') - // TODO: Prompt the user for email if not found in local storage, for example - // if the user opened the email link on a different device. - - const emailLink = window.location.href - - useEffect(() => { - logIn({ - providerId: 'emailLink', - email, - emailLink, - }) - }, []) - - if (loading) { - return
Auth Loading...
- } - - if (hasError) { - console.error(error) - return
Auth Error... check console
- } - - return -} - -export default EmailSigninPage -``` - -#### Custom Token in Firebase - -If you want to [integrate firebase auth with another authentication system](https://firebase.google.com/docs/auth/web/custom-auth), you can use a custom token provider: +The `hasRole()` function can be used to implement basic role-based authorization control (RBAC). This assumes that your `getCurrentUser()` function adds a `roles` property to the returned object. ```jsx -logIn({ - providerId: 'customToken', - customToken, -}) -``` - -Some caveats about using custom tokens: - -- make sure it's actually what you want to use -- remember that the client's firebase authentication state has an independent lifetime than the custom token - -If you want to read more, check out [Demystifying Firebase Auth Tokens](https://medium.com/@jwngr/demystifying-firebase-auth-tokens-e0c533ed330c). - -#### Custom Parameters & Scopes for Google OAuth Provider - -Both `logIn()` and `signUp()` can accept a single argument of either a **string** or **object**. If a string is provided, it should be any of the supported providers (see above), which will configure the defaults for that provider. - -`logIn()` and `signUp()` also accept a single a configuration object. This object accepts `providerId`, `email`, `password`, and `scope` and `customParameters`. (In fact, passing in any arguments ultimately results in this object). You can use this configuration object to pass in values for the optional Google OAuth Provider methods _setCustomParameters_ and _addScope_. - -Below are the parameters that `logIn()` and `signUp()` accept: - -- `providerId`: Accepts one of the supported auth providers as a **string**. If no arguments are passed to `login() / signUp()` this will default to 'google.com'. Provider strings passed as a single argument to `login() / signUp()` will be cast to this value in the object. -- `email`: Accepts a **string** of a users email address. Used in conjunction with `password` and requires that Firebase has email authentication enabled as an option. -- `password`: Accepts a **string** of a users password. Used in conjunction with `email` and requires that Firebase has email authentication enabled as an option. -- `scope`: Accepts an **array** of strings ([Google OAuth Scopes](https://developers.google.com/identity/protocols/oauth2/scopes)), which can be added to the requested Google OAuth Provider. These will be added using the Google OAuth _addScope_ method. -- `customParameters`: accepts an **object** with the [optional parameters](https://firebase.google.com/docs/reference/js/firebase.auth.GoogleAuthProvider#setcustomparameters) for the Google OAuth Provider _setCustomParameters_ method. [Valid parameters](https://developers.google.com/identity/protocols/oauth2/openid-connect#authenticationuriparameters) include 'hd', 'include_granted_scopes', 'login_hint' and 'prompt'. +export const MyComponent = () => { + const { isAuthenticated, hasRole } = useAuth() -#### Firebase Auth Examples - -- `logIn()/signUp()`: Defaults to Google provider. -- `logIn({providerId: 'github.com'})`: Log in using GitHub as auth provider. -- `signUp({email: "someone@email.com", password: 'some_good_password'})`: Creates a firebase user with email/password. -- `logIn({email: "someone@email.com", password: 'some_good_password'})`: Logs in existing firebase user with email/password. -- `logIn({scopes: ['https://www.googleapis.com/auth/calendar']})`: Adds a scope using the [addScope](https://firebase.google.com/docs/reference/js/firebase.auth.GoogleAuthProvider#addscope) method. -- `logIn({ customParameters: { prompt: "consent" } })`: Sets the OAuth custom parameters using [setCustomParameters](https://firebase.google.com/docs/reference/js/firebase.auth.GoogleAuthProvider#addscope) method. - -+++ - -#### Netlify Identity - -[Netlify Identity](https://docs.netlify.com/visitor-access/identity) offers [Role-based access control (RBAC)](https://docs.netlify.com/visitor-access/identity/manage-existing-users/#user-account-metadata). - -+++ View Netlify Identity Options - -#### Role-based access control (RBAC) in Netlify Identity - -Role-based access control (RBAC) refers to the idea of assigning permissions to users based on their role within an organization. It provides fine-grained control and offers a simple, manageable approach to access management that is less prone to error than assigning permissions to users individually. - -Essentially, a role is a collection of permissions that you can apply to users. A role might be "admin", "editor" or "publisher". This differs from permissions an example of which might be "publish:blog". - -#### App metadata in Netlify Identity - -Netlify Identity stores information (such as, support plan subscriptions, security roles, or access control groups) in `app_metadata`. Data stored in `app_metadata` cannot be edited by users. - -Create and manage roles for your application in Netlify's "Identity" management views. You can then assign these roles to users. - -#### Add Application hasRole Support in Netlify Identity - -If you intend to support, RBAC then in your `api/src/lib/auth.js` you need to extract `roles` using the `parseJWT` utility and set these roles on `currentUser`. - -Netlify will store the user's roles on the `app_metadata` claim and the `parseJWT` function provides an option to extract the roles so they can be assigned to the `currentUser`. - -For example: - -```jsx title="api/src/lib/auth.js" -export const getCurrentUser = async (decoded) => { - return context.currentUser || { ...decoded, roles: parseJWT({ decoded }).roles } + return ( + <> + {hasRole('admin') && ( + Admin + )} + + {hasRole(['author', 'editor']) && ( + Admin + )} + + ) } ``` -Now your `currentUser.roles` info will be available to both `requireAuth()` on the api side and `hasRole()` on the web side. - -+++ - -### Role Protection on Web - -You can protect content by role in pages or components via the `useAuth()` hook: - -```jsx -const { isAuthenticated, hasRole } = useAuth() - -... - -{hasRole('admin') && ( - Admin -)} - -{hasRole(['author', 'editor']) && ( - Admin -)} -``` - -### Routes +### Route Protection Routes can require authentication by wrapping them in a `` component. An unauthenticated user will be redirected to the page specified in `unauthenticated`. @@ -1482,6 +138,46 @@ const Routes = () => { } ``` +## Authentication on the API Side + +GraphQL requests automatically receive an `Authorization` header when a user is authenticated and Redwood will decode and verify the header, making the user available (if they are logged in) in `context.currentUser`. + +```jsx +import { context } from '@redwoodjs/api' + +console.log(context.currentUser) +// { +// sub: ' +// email: 'user@example.com', +// [...] +// } +``` + +You can map the "raw decoded JWT" into a real user object by passing a `getCurrentUser` function to `createGraphQLHandler` + +Our recommendation is to create a `src/lib/auth.js|ts` file that exports a `getCurrentUser`. (Note: You may already have stub functions.) + +```jsx +import { getCurrentUser } from 'src/lib/auth' +// Example: +// export const getCurrentUser = async (decoded) => { +// return await db.user.findUnique({ where: { decoded.email } }) +// } +// + +export const handler = createGraphQLHandler({ + schema: makeMergedSchema({ + schemas, + services: makeServices({ services }), + }), + getCurrentUser, +}) +``` + +The value returned by `getCurrentUser()` is available in `context.currentUser` + +Use the `requireAuth` and `skipAuth` [GraphQL directives](directives#secure-by-default-with-built-in-directives) to provide protection to individual GraphQL calls. + ## Contributing If you are interested in contributing to the Redwood Auth Package, please [start here](https://github.com/redwoodjs/redwood/blob/main/packages/auth/README.md). diff --git a/docs/sidebars.js b/docs/sidebars.js index 00c5d1737463..77ceedeb795f 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -72,7 +72,28 @@ module.exports = { 'a11y', 'app-configuration-redwood-toml', 'assets-and-files', - 'authentication', + { + type: 'category', + label: 'Authentication', + link: { + type: 'doc', + id: 'authentication', + }, + items: [ + { type: 'doc', id: 'auth/dbauth' }, + { type: 'doc', id: 'auth/auth0' }, + { type: 'doc', id: 'auth/azure' }, + { type: 'doc', id: 'auth/clerk' }, + { type: 'doc', id: 'auth/custom' }, + { type: 'doc', id: 'auth/firebase' }, + { type: 'doc', id: 'auth/gotrue' }, + { type: 'doc', id: 'auth/magic-link' }, + { type: 'doc', id: 'auth/netlify' }, + { type: 'doc', id: 'auth/nhost' }, + { type: 'doc', id: 'auth/supabase' }, + { type: 'doc', id: 'auth/wallet-connect' }, + ], + }, 'builds', 'cells', 'cli-commands', diff --git a/docs/versioned_docs/version-2.0/auth/auth0.md b/docs/versioned_docs/version-2.0/auth/auth0.md new file mode 100644 index 000000000000..ac9eae3b47e3 --- /dev/null +++ b/docs/versioned_docs/version-2.0/auth/auth0.md @@ -0,0 +1,264 @@ +--- +sidebar_label: Auth0 +--- + +# Auth0 Authentication + +The following CLI command will install required packages and generate boilerplate code and files for Redwood Projects: + +```bash +yarn rw setup auth auth0 +``` + +_If you prefer to manually install the package and add code_, run the following command and then add the required code provided in the next section. + +```bash +cd web +yarn add @redwoodjs/auth @auth0/auth0-spa-js +``` + +## Setup + +To get your application keys, only complete the ["Configure Auth0"](https://auth0.com/docs/quickstart/spa/react#get-your-application-keys) section of the SPA Quickstart guide. + +**NOTE** If you're using Auth0 with Redwood then you must also [create an API](https://auth0.com/docs/quickstart/spa/react/02-calling-an-api#create-an-api) and set the audience parameter, or you'll receive an opaque token instead of the required JWT token. + +The `useRefreshTokens` options is required for automatically extending sessions beyond that set in the initial JWT expiration (often 3600/1 hour or 86400/1 day). + +If you want to allow users to get refresh tokens while offline, you must also enable the Allow Offline Access switch in your Auth0 API Settings as part of setup configuration. See: [https://auth0.com/docs/tokens/refresh-tokens](https://auth0.com/docs/tokens/refresh-tokens) + +You can increase security by using refresh token rotation which issues a new refresh token and invalidates the predecessor token with each request made to Auth0 for a new access token. + +Rotating the refresh token reduces the risk of a compromised refresh token. For more information, see: [https://auth0.com/docs/tokens/refresh-tokens/refresh-token-rotation](https://auth0.com/docs/tokens/refresh-tokens/refresh-token-rotation). + +> **Including Environment Variables in Serverless Deployment:** in addition to adding the following env vars to your deployment hosting provider, you _must_ take an additional step to include them in your deployment build process. Using the names exactly as given below, follow the instructions in [this document](environment-variables.md) to include them in your `redwood.toml`. + +```jsx title="web/src/App.js" +import { AuthProvider } from '@redwoodjs/auth' +import { Auth0Client } from '@auth0/auth0-spa-js' +import { FatalErrorBoundary } from '@redwoodjs/web' +import { RedwoodApolloProvider } from '@redwoodjs/web/apollo' + +import FatalErrorPage from 'src/pages/FatalErrorPage' +import Routes from 'src/Routes' + +import './index.css' + +const auth0 = new Auth0Client({ + domain: process.env.AUTH0_DOMAIN, + client_id: process.env.AUTH0_CLIENT_ID, + redirect_uri: process.env.AUTH0_REDIRECT_URI, + + // ** NOTE ** Storing tokens in browser local storage provides persistence across page refreshes and browser tabs. + // However, if an attacker can achieve running JavaScript in the SPA using a cross-site scripting (XSS) attack, + // they can retrieve the tokens stored in local storage. + // https://auth0.com/docs/libraries/auth0-spa-js#change-storage-options + cacheLocation: 'localstorage', + audience: process.env.AUTH0_AUDIENCE, + + // @MARK: useRefreshTokens is required for automatically extending sessions + // beyond that set in the initial JWT expiration. + // + // @MARK: https://auth0.com/docs/tokens/refresh-tokens + // useRefreshTokens: true, +}) + +const App = () => ( + + + + + + + +) + +export default App +``` + +## Login and Logout Options + +When using the Auth0 client, `login` and `logout` take `options` that can be used to override the client config: + +- `returnTo`: a permitted logout url set in Auth0 +- `redirectTo`: a target url after login + +The latter is helpful when an unauthenticated user visits a Private route, but then is redirected to the `unauthenticated` route. The Redwood router will place the previous requested path in the pathname as a `redirectTo` parameter which can be extracted and set in the Auth0 `appState`. That way, after successfully logging in, the user will be directed to this `targetUrl` rather than the config's callback. + +```jsx +const UserAuthTools = () => { + const { loading, isAuthenticated, logIn, logOut } = useAuth() + + if (loading) { + // auth is rehydrating + return null + } + + return ( + + ) +} +``` + +## Integration + +If you're using Auth0 you must also [create an API](https://auth0.com/docs/quickstart/spa/react/02-calling-an-api#create-an-api) and set the audience parameter, or you'll receive an opaque token instead of a JWT token, and Redwood expects to receive a JWT token. + +### Role-Based Access Control (RBAC) + +[Role-based access control (RBAC)](https://auth0.com/docs/authorization/concepts/rbac) refers to the idea of assigning permissions to users based on their role within an organization. It provides fine-grained control and offers a simple, manageable approach to access management that is less prone to error than assigning permissions to users individually. + +Essentially, a role is a collection of permissions that you can apply to users. A role might be "admin", "editor" or "publisher". This differs from permissions an example of which might be "publish:blog". + +### App Metadata + +Auth0 stores information (such as, support plan subscriptions, security roles, or access control groups) in `app_metadata`. Data stored in `app_metadata` cannot be edited by users. + +Create and manage roles for your application in Auth0's "User & Role" management views. You can then assign these roles to users. + +However, that info is not immediately available on the user's `app_metadata` or to RedwoodJS when authenticating. + +If you assign your user the "admin" role in Auth0, you will want your user's `app_metadata` to look like: + +``` +{ + "roles": [ + "admin" + ] +} +``` + +To set this information and make it available to RedwoodJS, you can use [Auth0 Rules](https://auth0.com/docs/rules). + +### Rules for App Metadata + +RedwoodJS needs `app_metadata` to 1) contain the role information and 2) be present in the JWT that is decoded. + +To accomplish these tasks, you can use [Auth0 Rules](https://auth0.com/docs/rules) to add them as custom claims on your JWT. + +#### Add Authorization Roles to App Metadata Rule + +Your first rule will `Add Authorization Roles to App Metadata`. + +```jsx +/// Add Authorization Roles to App Metadata +function (user, context, callback) { + auth0.users.updateAppMetadata(user.user_id, context.authorization) + .then(function(){ + callback(null, user, context); + }) + .catch(function(err){ + callback(err); + }); + } +``` + +Auth0 exposes the user's roles in `context.authorization`. This rule simply copies that information into the user's `app_metadata`, such as: + +``` +{ + "roles": [ + "admin" + ] +} +``` + +However, now you must include the `app_metadata` on the user's JWT that RedwoodJS will decode. + +#### Add App Metadata to JWT Rule + +Therefore, your second rule will `Add App Metadata to JWT`. + +You can add `app_metadata` to the `idToken` or `accessToken`. + +Adding to `idToken` will make the make app metadata accessible to RedwoodJS `getUserMetadata` which for Auth0 calls the auth client's `getUser`. + +Adding to `accessToken` will make the make app metadata accessible to RedwoodJS when decoding the JWT via `getToken`. + +While adding to `idToken` is optional, you _must_ add to `accessToken`. + +To keep your custom claims from colliding with any reserved claims or claims from other resources, you must give them a [globally unique name using a namespaced format](https://auth0.com/docs/tokens/guides/create-namespaced-custom-claims). Otherwise, Auth0 will _not_ add the information to the token(s). + +Therefore, with a namespace of "https://example.com", the `app_metadata` on your token should look like: + +```jsx +"https://example.com/app_metadata": { + "authorization": { + "roles": [ + "admin" + ] + } +}, +``` + +To set this namespace information, use the following function in your rule: + +```jsx +function (user, context, callback) { + var namespace = 'https://example.com/'; + + // adds to idToken, i.e. userMetadata in RedwoodJS + context.idToken[namespace + 'app_metadata'] = {}; + context.idToken[namespace + 'app_metadata'].authorization = { + groups: user.app_metadata.groups, + roles: user.app_metadata.roles, + permissions: user.app_metadata.permissions + }; + + context.idToken[namespace + 'user_metadata'] = {}; + + // accessToken, i.e. the decoded JWT in RedwoodJS + context.accessToken[namespace + 'app_metadata'] = {}; + context.accessToken[namespace + 'app_metadata'].authorization = { + groups: user.app_metadata.groups, + roles: user.app_metadata.roles, + permissions: user.app_metadata.permissions + }; + + context.accessToken[namespace + 'user_metadata'] = {}; + + return callback(null, user, context); +} +``` + +Now, your `app_metadata` with `authorization` and `role` information will be on the user's JWT after logging in. + +### Application `hasRole` Support + +If you intend to support, RBAC then in your `api/src/lib/auth.js` you need to extract `roles` using the `parseJWT` utility and set these roles on `currentUser`. + +If your roles are on a namespaced `app_metadata` claim, then `parseJWT` provides an option to provide this value. + +```jsx title="api/src/lib/auth.js" +const NAMESPACE = 'https://example.com' + +const currentUserWithRoles = async (decoded) => { + const currentUser = await userByUserId(decoded.sub) + return { + ...currentUser, + roles: parseJWT({ decoded: decoded, namespace: NAMESPACE }).roles, + } +} + +export const getCurrentUser = async (decoded, { type, token }) => { + try { + requireAccessToken(decoded, { type, token }) + return currentUserWithRoles(decoded) + } catch (error) { + return decoded + } +} +``` diff --git a/docs/versioned_docs/version-2.0/auth/azure.md b/docs/versioned_docs/version-2.0/auth/azure.md new file mode 100644 index 000000000000..69adf5041986 --- /dev/null +++ b/docs/versioned_docs/version-2.0/auth/azure.md @@ -0,0 +1,97 @@ +--- +sidebar_label: Azure +--- + +# Azure Active Directory Authentication + +## Installation + +The following CLI command will install required packages and generate boilerplate code and files for Redwood Projects: + +```bash +yarn rw setup auth azureActiveDirectory +``` + +_If you prefer to manually install the package and add code_, run the following command and then add the required code provided in the next section. + +```bash +cd web +yarn add @azure/msal-browser +``` + +## Setup + +To get your application credentials, create an [App Registration](https://docs.microsoft.com/en-us/azure/active-directory/develop/scenario-spa-app-registration) using in your Azure Active Directory tenant and make sure you configure as a [MSAL.js 2.0 with auth code flow](https://docs.microsoft.com/en-us/azure/active-directory/develop/scenario-spa-app-registration#redirect-uri-msaljs-20-with-auth-code-flow) registration. Take a note of your generated _Application ID_ (client), and the _Directory ID_ (tenant). + +[Learn more about authorization code flow](https://docs.microsoft.com/en-us/azure/active-directory/develop/reference-third-party-cookies-spas). + +## Redirect URIs + +Enter allowed redirect urls for the integrations, e.g. `http://localhost:8910/login`. This will be the `AZURE_ACTIVE_DIRECTORY_REDIRECT_URI` environment variable, and suggestively `AZURE_ACTIVE_DIRECTORY_LOGOUT_REDIRECT_URI`. + +## Authority + +The Authority is a URL that indicates a directory that MSAL can request tokens from which you can read about [here](https://docs.microsoft.com/en-us/azure/active-directory/develop/msal-client-application-configuration#authority). However, you most likely want to have e.g. `https://login.microsoftonline.com/` as Authority URL, where `` is the Azure Active Directory tenant id. This will be the `AZURE_ACTIVE_DIRECTORY_AUTHORITY` environment variable. + +```jsx title="web/src/App.js" +import { AuthProvider } from '@redwoodjs/auth' +import { PublicClientApplication } from '@azure/msal-browser' +import { FatalErrorBoundary } from '@redwoodjs/web' +import { RedwoodApolloProvider } from '@redwoodjs/web/apollo' + +import FatalErrorPage from 'src/pages/FatalErrorPage' +import Routes from 'src/Routes' + +import './index.css' + +const azureActiveDirectoryClient = new PublicClientApplication({ + auth: { + clientId: process.env.AZURE_ACTIVE_DIRECTORY_CLIENT_ID, + authority: process.env.AZURE_ACTIVE_DIRECTORY_AUTHORITY, + redirectUri: process.env.AZURE_ACTIVE_DIRECTORY_REDIRECT_URI, + postLogoutRedirectUri: process.env.AZURE_ACTIVE_DIRECTORY_LOGOUT_REDIRECT_URI, + }, +}) + +const App = () => ( + + + + + + + +) + +export default App +``` + +## Integration + +### Roles + +To setup your App Registration with custom roles and have them exposed via the `roles` claim, follow [this documentation](https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-add-app-roles-in-azure-ad-apps). + +### Login Options + +Options in method `logIn(options?)` is of type [RedirectRequest](https://azuread.github.io/microsoft-authentication-library-for-js/ref/modules/_azure_msal_browser.html#redirectrequest) and is a good place to pass in optional [scopes](https://docs.microsoft.com/en-us/graph/permissions-reference#user-permissions) to be authorized. By default, MSAL sets `scopes` to [/.default](https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent#the-default-scope) which is built in for every application that refers to the static list of permissions configured on the application registration. Furthermore, MSAL will add `openid` and `profile` to all requests. In example below we explicit include `User.Read.All` to the login scope. + +```jsx +await logIn({ + scopes: ['User.Read.All'], // becomes ['openid', 'profile', 'User.Read.All'] +}) +``` + +See [loginRedirect](https://azuread.github.io/microsoft-authentication-library-for-js/ref/classes/_azure_msal_browser.publicclientapplication.html#loginredirect), [PublicClientApplication](https://azuread.github.io/microsoft-authentication-library-for-js/ref/classes/_azure_msal_browser.publicclientapplication.html) class and [Scopes Behavior](https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-core/docs/scopes.md#scopes-behavior) for more documentation. + +### getToken Options + +Options in method `getToken(options?)` is of type [RedirectRequest](https://azuread.github.io/microsoft-authentication-library-for-js/ref/modules/_azure_msal_browser.html#redirectrequest). By default, `getToken` will be called with scope `['openid', 'profile']`. As Azure Active Directory apply [incremental consent](https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/resources-and-scopes.md#dynamic-scopes-and-incremental-consent), we can extend the permissions from the login example by including another scope, for example `Mail.Read`. + +```jsx +await getToken({ + scopes: ['Mail.Read'], // becomes ['openid', 'profile', 'User.Read.All', 'Mail.Read'] +}) +``` + +See [acquireTokenSilent](https://azuread.github.io/microsoft-authentication-library-for-js/ref/classes/_azure_msal_browser.publicclientapplication.html#acquiretokensilent), [Resources and Scopes](https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/resources-and-scopes.md#resources-and-scopes) or [full class documentation](https://pub.dev/documentation/msal_js/latest/msal_js/PublicClientApplication-class.html#constructors) for more documentation. diff --git a/docs/versioned_docs/version-2.0/auth/clerk.md b/docs/versioned_docs/version-2.0/auth/clerk.md new file mode 100644 index 000000000000..61fd753c9cd8 --- /dev/null +++ b/docs/versioned_docs/version-2.0/auth/clerk.md @@ -0,0 +1,75 @@ +--- +sidebar_label: Clerk +--- + +# Clerk Authentication + +## Installation + +The following CLI command will install required packages and generate boilerplate code and files for Redwood Projects: + +```bash +yarn rw setup auth clerk +``` + +## Setup + +To get started with Clerk, sign up on [their website](https://clerk.dev/) and create an application, or follow their [RedwoodJS Blog Tutorial with Clerk](https://clerk.dev/tutorials/redwoodjs-blog-tutorial-with-clerk) that has an [example repo](https://github.com/redwoodjs/redwood-tutorial) already setup. + +It's important that the `ClerkAuthProvider` added to your `App.{js|ts}` file during setup is within the `RedwoodProvider` and around Redwood's `AuthProvider`: + +```tsx {4,10} title="web/src/App.{js|ts}" +const App = () => ( + + + + + + + + + + + +) +``` + +The [RedwoodJS Blog Tutorial with Clerk](https://clerk.dev/tutorials/redwoodjs-blog-tutorial-with-clerk) also explains how to use `@clerk/clerk-react` components with Redwood's `useAuth()` hook: + +```tsx +import { UserButton, SignInButton } from '@clerk/clerk-react' + +// ... + +{ + isAuthenticated ? ( + + ) : ( + + + + ) +} +``` + +Applications in Clerk have different instances. By default, there's one for development, one for staging, and one for production. You'll need to pull three values from one of these instances. We recommend storing the development values in your local `.env` file and using the staging and production values in the appropriate env setups for your hosting platform when you deploy. + +The three values you'll need from Clerk are your instance's "Frontend API Key" url, a "Backend API key" and a "JWT verification key", all from your instance's settings under "API Keys". The Frontend API url should be stored in an env variable named `CLERK_FRONTEND_API_URL`. The Backend API key should be named `CLERK_API_KEY`. Finally, the JWT key should be named `CLERK_JWT_KEY` + +Otherwise, feel free to configure your instances however you wish with regards to their appearance and functionality. + +> **Including Environment Variables in Serverless Deploys** +> +> In addition to adding these env vars to your local `.env` file or deployment hosting provider, you _must_ take an additional step to include them in your deployment build process. Using the names exactly as given above, follow the instructions in [this document](environment-variables.md). You need to expose the `CLERK_FRONTEND_API_URL` variable to the `web` side. + +## Login and Logout Options + +When using the Clerk client, `login` and `signUp` take an `options` object that can be used to override the client config. + +For `login` the `options` may contain all the options listed at the Clerk [props documentation for login](https://docs.clerk.dev/reference/clerkjs/clerk#signinprops). + +For `signUp` the `options` may contain all the options listed at the Clerk [props documentation for signup](https://docs.clerk.dev/reference/clerkjs/clerk#signupprops). + +## Avoiding Feature Duplication Confusion + +Redwood's integration of Clerk is based on [Clerk's React SDK](https://docs.clerk.dev/reference/clerk-react). This means there is some duplication between the features available through that SDK and the ones available in the `@redwoodjs/auth` package - such as the alternatives of using Clerk's `SignedOut` component to redirect users away from a private page vs. using Redwood's `Private` route wrapper. In general, we would recommend you use the **Redwood** way of doing things when possible, as that is more likely to function harmoniously with the rest of Redwood. That being said, though, there are some great features in Clerk's SDK that you will be able to now use in your app, such as the `UserButton` and `UserProfile` components. diff --git a/docs/versioned_docs/version-2.0/auth/custom.md b/docs/versioned_docs/version-2.0/auth/custom.md new file mode 100644 index 000000000000..c3f819394344 --- /dev/null +++ b/docs/versioned_docs/version-2.0/auth/custom.md @@ -0,0 +1,23 @@ +--- +sidebar_label: Custom +--- + +# Custom Authentication Client + +## Installation + +The following CLI command (not implemented, see https://github.com/redwoodjs/redwood/issues/1585) will install required packages and generate boilerplate code and files for Redwood Projects: + +```bash +yarn rw setup auth custom +``` + +## Setup + +It is possible to implement a custom provider for Redwood Auth. In which case you might also consider adding the provider to Redwood itself. + +If you are trying to implement your own auth, support is very early and limited at this time. Additionally, there are many considerations and responsibilities when it comes to managing custom auth. For most cases we recommend using an existing provider. + +However, there are examples contributed by developers in the Redwood forums and Discord server. + +The most complete example (although now a bit outdated) is found in [this forum thread](https://community.redwoodjs.com/t/custom-github-jwt-auth-with-redwood-auth/610). Here's another [helpful message in the thread](https://community.redwoodjs.com/t/custom-github-jwt-auth-with-redwood-auth/610/25). diff --git a/docs/versioned_docs/version-2.0/auth/dbauth.md b/docs/versioned_docs/version-2.0/auth/dbauth.md new file mode 100644 index 000000000000..3061b08062ea --- /dev/null +++ b/docs/versioned_docs/version-2.0/auth/dbauth.md @@ -0,0 +1,220 @@ +--- +sidebar_label: Self-hosted (dbAuth) +--- + +# Self-hosted Authentication (dbAuth) + +Redwood's own **dbAuth** provides several benefits: + +- Use your own database for storing user credentials +- Use your own login, signup and forgot password pages (or use Redwood's pre-built ones) +- Customize login session length +- No external dependencies +- No user data ever leaves your servers +- No additional charges/limits based on number of users +- No third party service outages affecting your site + +And potentially one large drawback: + +- Use your own database for storing user credentials + +However, we're following best practices for storing these credentials: + +1. Users' passwords are [salted and hashed](https://auth0.com/blog/adding-salt-to-hashing-a-better-way-to-store-passwords/) with PBKDF2 before being stored +2. Plaintext passwords are never stored anywhere, and only transferred between client and server during the login/signup phase (and hopefully only over HTTPS) +3. Our logger scrubs sensitive parameters (like `password`) before they are output + +Even if you later decide you want to let someone else handle your user data for you, dbAuth is a great option for getting up and running quickly (we even have a generator for creating basic login and signup pages for you). + +## How It Works + +dbAuth relies on good ol' fashioned cookies to determine whether a user is logged in or not. On an attempted login, a serverless function on the api-side checks whether a user exists with the given username (internally, dbAuth refers to this field as _username_ but you can use anything you want, like an email address). If a user with that username is found, does their salted and hashed password match the one in the database? + +If so, an [HttpOnly](https://owasp.org/www-community/HttpOnly), [Secure](https://owasp.org/www-community/controls/SecureCookieAttribute), [SameSite](https://owasp.org/www-community/SameSite) cookie (dbAuth calls this the "session cookie") is sent back to the browser containing the ID of the user. The content of the cookie is a simple string, but AES encrypted with a secret key (more on that later). + +When the user makes a GraphQL call, we decrypt the cookie and make sure that the user ID contained within still exists in the database. If so, the request is allowed to proceed. + +If there are any shenanigans detected (the cookie can't be decrypted properly, or the user ID found in the cookie does not exist in the database) the user is immediately logged out by expiring the session cookie. + +## Setup + +A single CLI command will get you everything you need to get dbAuth working, minus the actual login/signup pages: + + yarn rw setup auth dbAuth + +Read the post-install instructions carefully as they contain instructions for adding database fields for the hashed password and salt, as well as how to configure the auth serverless function based on the name of the table that stores your user data. Here they are, but could change in future releases: + +> You will need to add a couple of fields to your User table in order to store a hashed password and salt: +> +> model User { +> id Int @id @default(autoincrement()) +> email String @unique +> hashedPassword String // <─┐ +> salt String // <─┼─ add these lines +> resetToken String? // <─┤ +> resetTokenExpiresAt DateTime? // <─┘ +> } +> +> If you already have existing user records you will need to provide a default value or Prisma complains, so change those to: +> +> hashedPassword String @default("") +> salt String @default("") +> +> You'll need to let Redwood know what field you're using for your users' `id` and `username` fields In this case we're using `id` and `email`, so update those in the `authFields` config in `/api/src/functions/auth.js` (this is also the place to tell Redwood if you used a different name for the `hashedPassword` or `salt` fields): +> +> authFields: { +> id: 'id', +> username: 'email', +> hashedPassword: 'hashedPassword', +> salt: 'salt', +> resetToken: 'resetToken', +> resetTokenExpiresAt: 'resetTokenExpiresAt', +> }, +> +> To get the actual user that's logged in, take a look at `getCurrentUser()` in `/api/src/lib/auth.js`. We default it to something simple, but you may use different names for your model or unique ID fields, in which case you need to update those calls (instructions are in the comment above the code). +> +> Finally, we created a `SESSION_SECRET` environment variable for you in `.env`. This value should NOT be checked into version control and should be unique for each environment you deploy to. If you ever need to log everyone out of your app at once change this secret to a new value. To create a new secret, run: +> +> yarn rw g secret +> +> Need simple Login, Signup and Forgot Password pages? Of course we have a generator for those: +> +> yarn rw generate dbAuth + +Note that if you change the fields named `hashedPassword` and `salt`, and you have some verbose logging in your app, you'll want to scrub those fields from appearing in your logs. See the [Redaction](logger.md#redaction) docs for info. + +## Scaffolding Login/Signup/Forgot Password Pages + +If you don't want to create your own login, signup and forgot password pages from scratch we've got a generator for that: + + yarn rw g dbAuth + +The default routes will make them available at `/login`, `/signup`, `/forgot-password`, and `/reset-password` but that's easy enough to change. Again, check the post-install instructions for one change you need to make to those pages: where to redirect the user to once their login/signup is successful. + +If you'd rather create your own, you might want to start from the generated pages anyway as they'll contain the other code you need to actually submit the login credentials or signup fields to the server for processing. + +## Configuration + +Almost all config for dbAuth lives in `api/src/functions/auth.js` in the object you give to the `DbAuthHandler` initialization. The comments above each key will explain what goes where. Here's an overview of the more important options: + +### login.handler() + +If you want to do something other than immediately let a user log in if their username/password is correct, you can add additional logic in `login.handler()`. For example, if a user's credentials are correct, but they haven't verified their email address yet, you can throw an error in this function with the appropriate message and then display it to the user. If the login should proceed, simply return the user that was passed as the only argument to the function: + +```jsx +login: { + handler: (user) => { + if (!user.verified) { + throw new Error('Please validate your email first!') + } else { + return user + } + } +} +``` + +### signup.handler() + +This function should contain the code needed to actually create a user in your database. You will receive a single argument which is an object with all of the fields necessary to create the user (`username`, `hashedPassword` and `salt`) as well as any additional fields you included in your signup form in an object called `userAttributes`: + +```jsx +signup: { + handler: ({ username, hashedPassword, salt, userAttributes }) => { + return db.user.create({ + data: { + email: username, + hashedPassword: hashedPassword, + salt: salt, + name: userAttributes.name, + }, + }) + } +} +``` + +Before `signup.handler()` is invoked, dbAuth will check that the username is unique in the database and throw an error if not. + +There are three things you can do within this function depending on how you want the signup to proceed: + +1. If everything is good and the user should be logged in after signup: return the user you just created +2. If the user is safe to create, but you do not want to log them in automatically: return a string, which will be returned by the `signUp()` function you called after destructuring it from `useAuth()` (see code snippet below) +3. If the user should _not_ be able to sign up for whatever reason: throw an error in this function with the message to be displayed + +You can deal with case #2 by doing something like the following in a signup component/page: + +```jsx +const { signUp } = useAuth() + +const onSubmit = async (data) => { + const response = await signUp({ ...data }) + + if (response.message) { + toast.error(response.message) // user created, but not logged in + } else { + toast.success('Welcome!') // user created and logged in + navigate(routes.dashboard()) + } +} +``` + +### forgotPassword.handler() + +This handler is invoked if a user is found with the username/email that they submitted on the Forgot Password page, and that user will be passed as an argument. Inside this function is where you'll send the user a link to reset their password—via an email is most common. The link will, by default, look like: + + https://example.com/reset-password?resetToken=${user.resetToken} + +If you changed the path to the Reset Password page in your routes you'll need to change it here. If you used another name for the `resetToken` database field, you'll need to change that here as well: + + https://example.com/reset-password?resetKey=${user.resetKey} + +### resetPassword.handler() + +This handler is invoked after the password has been successfully changed in the database. Returning something truthy (like `return user`) will automatically log the user in after their password is changed. If you'd like to return them to the login page and make them log in manually, `return false` and redirect the user in the Reset Password page. + +### Cookie config + +These options determine how the cookie that tracks whether the client is authorized is stored in the browser. The default configuration should work for most use cases. If you serve your web and api sides from different domains you'll need to make some changes: set `SameSite` to `None` and then add [CORS configuration](#cors-config). + +```jsx +cookie: { + HttpOnly: true, + Path: '/', + SameSite: 'Strict', + Secure: true, + // Domain: 'example.com', +} +``` + +### CORS config + +If you're using dbAuth and your api and web sides are deployed to different domains then you'll need to configure CORS for both GraphQL in general and dbAuth. You'll also need to enable a couple of options to be sure and send/accept credentials in XHR requests. For more info, see the complete [CORS doc](cors.md#cors-and-authentication). + +### Error Messages + +There are several error messages that can be displayed, including: + +- Username/email not found +- Incorrect password +- Expired reset password token + +We've got some default error messages that sound nice, but may not fit the tone of your site. You can customize these error messages in `api/src/functions/auth.js` in the `errors` prop of each of the `login`, `signup`, `forgotPassword` and `resetPassword` config objects. The generated file contains tons of comments explaining when each particular error message may be shown. + +## Environment Variables + +### Cookie Domain + +By default, the session cookie will not have the `Domain` property set, which a browser will default to be the [current domain only](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#define_where_cookies_are_sent). If your site is spread across multiple domains (for example, your site is at `example.com` but your api-side is deployed to `api.example.com`) you'll need to explicitly set a Domain so that the cookie is accessible to both. + +To do this, create an environment variable named `DBAUTH_COOKIE_DOMAIN` set to the root domain of your site, which will allow it to be read by all subdomains as well. For example: + + DBAUTH_COOKIE_DOMAIN=example.com + +### Session Secret Key + +If you need to change the secret key that's used to encrypt the session cookie, or deploy to a new target (each deploy environment should have its own unique secret key) we've got a CLI tool for creating a new one: + + yarn rw g secret + +Note that the secret that's output is _not_ appended to your `.env` file or anything else, it's merely output to the screen. You'll need to put it in the right place after that. + +> The `.env` file is set to be ignored by git and not committed to version control. There is another file, `.env.defaults`, which is meant to be safe to commit and contain simple ENV vars that your dev team can share. The encryption key for the session cookie is NOT one of these shareable vars! diff --git a/docs/versioned_docs/version-2.0/auth/firebase.md b/docs/versioned_docs/version-2.0/auth/firebase.md new file mode 100644 index 000000000000..bdcc07b0280e --- /dev/null +++ b/docs/versioned_docs/version-2.0/auth/firebase.md @@ -0,0 +1,216 @@ +--- +sidebar_label: Firebase +--- + +# Firebase Authentication + +## Installation + +The following CLI command will install required packages and generate boilerplate code and files for Redwood Projects: + +```bash +yarn rw setup auth firebase +``` + +## Setup + +We're using [Firebase Google Sign-In](https://firebase.google.com/docs/auth/web/google-signin), so you'll have to follow the ["Before you begin"](https://firebase.google.com/docs/auth/web/google-signin#before_you_begin) steps in this guide. **Only** follow the "Before you begin" parts. + +> **Including Environment Variables in Serverless Deployment:** in addition to adding the following env vars to your deployment hosting provider, you _must_ take an additional step to include them in your deployment build process. Using the names exactly as given below, follow the instructions in [this document](https://redwoodjs.com/docs/environment-variables) to "Whitelist them in your `redwood.toml`". + +```jsx title="web/src/App.js" +import { AuthProvider } from '@redwoodjs/auth' +import { initializeApp, getApps, getApp } from '@firebase/app' +import * as firebaseAuth from '@firebase/auth' +import { FatalErrorBoundary } from '@redwoodjs/web' +import { RedwoodApolloProvider } from '@redwoodjs/web/apollo' + +import FatalErrorPage from 'src/pages/FatalErrorPage' +import Routes from 'src/Routes' + +import './index.css' + +const firebaseClientConfig = { + apiKey: process.env.FIREBASE_API_KEY, + authDomain: process.env.FIREBASE_AUTH_DOMAIN, + databaseURL: process.env.FIREBASE_DATABASE_URL, + projectId: process.env.FIREBASE_PROJECT_ID, + storageBucket: process.env.FIREBASE_STORAGE_BUCKET, + messagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID, + appId: process.env.FIREBASE_APP_ID, +} + +const firebaseApp = ((config) => { + const apps = getApps() + if (!apps.length) { + initializeApp(config) + } + return getApp() +})(firebaseConfig) + +export const firebaseClient = { + firebaseAuth, + firebaseApp, +} + +const App = () => ( + + + + + + + +) + +export default App +``` + +## Usage + +```jsx +const UserAuthTools = () => { + const { loading, isAuthenticated, logIn, logOut } = useAuth() + + if (loading) { + return null + } + + return ( + + ) +} +``` + +## Integration + +See the Firebase information within this doc's [Auth Provider Specific Integration](#auth-provider-specific-integration) section. + +You must follow the ["Before you begin"](https://firebase.google.com/docs/auth/web/google-signin) part of the "Authenticate Using Google Sign-In with JavaScript" guide. + +### Role-Based Access Control (RBAC) + +Requires a custom implementation. + +### App Metadata + +None. + +### Add Application `hasRole` Support + +Requires a custom implementation. + +### Auth Providers + +Providers can be configured by specifying `logIn(provider)` and `signUp(provider)`, where `provider` is a **string** of one of the supported providers. + +Supported providers: + +- google.com (Default) +- facebook.com +- github.com +- twitter.com +- microsoft.com +- apple.com + +### Email & Password Auth + +Email/password authentication is supported by calling `login({ email, password })` and `signUp({ email, password })`. + +### Email Link (Password-less Sign-in) + +In Firebase Console, you must enable "Email link (passwordless sign-in)" with the configuration toggle for the email provider. The authentication sequence for passwordless email links has two steps: + +1. First, an email with the link must be generated and sent to the user. Either using using firebase client sdk (web side) [sendSignInLinkToEmail()](https://firebase.google.com/docs/reference/js/auth.emailauthprovider#example_2_2), which generates the link and sends the email to the user on behalf of your application or alternatively, generate the link using backend admin sdk (api side), see ["Generate email link for sign-in](https://firebase.google.com/docs/auth/admin/email-action-links#generate_email_link_for_sign-in) but it is then your responsibility to send an email to the user containing the link. +2. Second, authentication is completed when the user is redirected back to the application and the AuthProvider's logIn({emailLink, email, providerId: 'emailLink'}) method is called. + +For example, users could be redirected to a dedicated route/page to complete authentication: + +```jsx +import { useEffect } from 'react' +import { Redirect, routes } from '@redwoodjs/router' +import { useAuth } from '@redwoodjs/auth' + +const EmailSigninPage = () => { + const { loading, hasError, error, logIn } = useAuth() + + const email = window.localStorage.getItem('emailForSignIn') + // TODO: Prompt the user for email if not found in local storage, for example + // if the user opened the email link on a different device. + + const emailLink = window.location.href + + useEffect(() => { + logIn({ + providerId: 'emailLink', + email, + emailLink, + }) + }, []) + + if (loading) { + return
Auth Loading...
+ } + + if (hasError) { + console.error(error) + return
Auth Error... check console
+ } + + return +} + +export default EmailSigninPage +``` + +### Custom Token + +If you want to [integrate firebase auth with another authentication system](https://firebase.google.com/docs/auth/web/custom-auth), you can use a custom token provider: + +```jsx +logIn({ + providerId: 'customToken', + customToken, +}) +``` + +Some caveats about using custom tokens: + +- make sure it's actually what you want to use +- remember that the client's firebase authentication state has an independent lifetime than the custom token + +If you want to read more, check out [Demystifying Firebase Auth Tokens](https://medium.com/@jwngr/demystifying-firebase-auth-tokens-e0c533ed330c). + +### Custom Parameters & Scopes for Google OAuth Provider + +Both `logIn()` and `signUp()` can accept a single argument of either a **string** or **object**. If a string is provided, it should be any of the supported providers (see above), which will configure the defaults for that provider. + +`logIn()` and `signUp()` also accept a single a configuration object. This object accepts `providerId`, `email`, `password`, and `scope` and `customParameters`. (In fact, passing in any arguments ultimately results in this object). You can use this configuration object to pass in values for the optional Google OAuth Provider methods _setCustomParameters_ and _addScope_. + +Below are the parameters that `logIn()` and `signUp()` accept: + +- `providerId`: Accepts one of the supported auth providers as a **string**. If no arguments are passed to `login() / signUp()` this will default to 'google.com'. Provider strings passed as a single argument to `login() / signUp()` will be cast to this value in the object. +- `email`: Accepts a **string** of a users email address. Used in conjunction with `password` and requires that Firebase has email authentication enabled as an option. +- `password`: Accepts a **string** of a users password. Used in conjunction with `email` and requires that Firebase has email authentication enabled as an option. +- `scope`: Accepts an **array** of strings ([Google OAuth Scopes](https://developers.google.com/identity/protocols/oauth2/scopes)), which can be added to the requested Google OAuth Provider. These will be added using the Google OAuth _addScope_ method. +- `customParameters`: accepts an **object** with the [optional parameters](https://firebase.google.com/docs/reference/js/firebase.auth.GoogleAuthProvider#setcustomparameters) for the Google OAuth Provider _setCustomParameters_ method. [Valid parameters](https://developers.google.com/identity/protocols/oauth2/openid-connect#authenticationuriparameters) include 'hd', 'include_granted_scopes', 'login_hint' and 'prompt'. + +### Firebase Auth Examples + +- `logIn()/signUp()`: Defaults to Google provider. +- `logIn({providerId: 'github.com'})`: Log in using GitHub as auth provider. +- `signUp({email: "someone@email.com", password: 'some_good_password'})`: Creates a firebase user with email/password. +- `logIn({email: "someone@email.com", password: 'some_good_password'})`: Logs in existing firebase user with email/password. +- `logIn({scopes: ['https://www.googleapis.com/auth/calendar']})`: Adds a scope using the [addScope](https://firebase.google.com/docs/reference/js/firebase.auth.GoogleAuthProvider#addscope) method. +- `logIn({ customParameters: { prompt: "consent" } })`: Sets the OAuth custom parameters using [setCustomParameters](https://firebase.google.com/docs/reference/js/firebase.auth.GoogleAuthProvider#addscope) method. diff --git a/docs/versioned_docs/version-2.0/auth/gotrue.md b/docs/versioned_docs/version-2.0/auth/gotrue.md new file mode 100644 index 000000000000..0d8edb8a323a --- /dev/null +++ b/docs/versioned_docs/version-2.0/auth/gotrue.md @@ -0,0 +1,61 @@ +--- +sidebar_label: GoTrue +--- + +# GoTrue Authentication + +## Installation + +The following CLI command will install required packages and generate boilerplate code and files for Redwood Projects: + +```bash +yarn rw setup auth goTrue +``` + +_If you prefer to manually install the package and add code_, run the following command and then add the required code provided in the next section. + +```bash +cd web +yarn add @redwoodjs/auth gotrue-js +``` + +## Setup + +You will need to enable Identity on your Netlify site. + +Add the GoTrue-JS package to the web side: + +```bash +yarn workspace web add gotrue-js +``` + +Instantiate GoTrue and pass in your configuration. Be sure to set APIUrl to the API endpoint found in your Netlify site's Identity tab: + +```jsx title="web/src/App.js" +import { AuthProvider } from '@redwoodjs/auth' +import GoTrue from 'gotrue-js' +import { FatalErrorBoundary } from '@redwoodjs/web' +import { RedwoodApolloProvider } from '@redwoodjs/web/apollo' + +import FatalErrorPage from 'src/pages/FatalErrorPage' +import Routes from 'src/Routes' + +import './index.css' + +const goTrueClient = new GoTrue({ + APIUrl: 'https://MYAPP.netlify.app/.netlify/identity', + setCookie: true, +}) + +const App = () => ( + + + + + + + +) + +export default App +``` diff --git a/docs/versioned_docs/version-2.0/auth/magic-link.md b/docs/versioned_docs/version-2.0/auth/magic-link.md new file mode 100644 index 000000000000..325d2762cef3 --- /dev/null +++ b/docs/versioned_docs/version-2.0/auth/magic-link.md @@ -0,0 +1,107 @@ +--- +sidebar_label: Magic.link +--- + +# Magic.Link Authentication + +## Installation + +The following CLI command will install required packages and generate boilerplate code and files for Redwood Projects: + +```bash +yarn rw setup auth magicLink +``` + +_If you prefer to manually install the package and add code_, run the following command and then add the required code provided in the next section. + +```bash +cd web +yarn add @redwoodjs/auth magic-sdk +``` + +## Setup + +To get your application keys, go to [dashboard.magic.link](https://dashboard.magic.link/) then navigate to the API keys add them to your `.env`. + +> **Including Environment Variables in Serverless Deployment:** in addition to adding the following env vars to your deployment hosting provider, you _must_ take an additional step to include them in your deployment build process. Using the names exactly as given below, follow the instructions in [this document](environment-variables.md) to "Whitelist them in your `redwood.toml`". + +```jsx title="web/src/App.js|tsx" +import { useAuth, AuthProvider } from '@redwoodjs/auth' +import { Magic } from 'magic-sdk' +import { FatalErrorBoundary } from '@redwoodjs/web' +import { RedwoodApolloProvider } from '@redwoodjs/web/apollo' + +import FatalErrorPage from 'src/pages/FatalErrorPage' +import Routes from 'src/Routes' + +import './index.css' + +const m = new Magic(process.env.MAGICLINK_PUBLIC) + +const App = () => ( + + + + + + + +) + +export default App +``` + +```jsx title="web/src/Routes.js|tsx" +import { useAuth } from '@redwoodjs/auth' +import { Router, Route } from '@redwoodjs/router' + +const Routes = () => { + return ( + + + + + ) +} + +export default Routes +``` + +## Integration + +The Redwood API does not include the functionality to decode Magic.link authentication tokens, so the client is initiated and decodes the tokens inside of `getCurrentUser`. + +### Installation + +First, you must manually install the **Magic Admin SDK** in your project's `api/package.json`. + +```bash +yarn workspace api add @magic-sdk/admin +``` + +### Setup + +To get your application running _without setting up_ `Prisma`, get your `SECRET KEY` from [dashboard.magic.link](https://dashboard.magic.link/). Then add `MAGICLINK_SECRET` to your `.env`. + +```jsx title="redwood/api/src/lib/auth.js|ts" +import { Magic } from '@magic-sdk/admin' + +export const getCurrentUser = async (_decoded, { token }) => { + const mAdmin = new Magic(process.env.MAGICLINK_SECRET) + + return await mAdmin.users.getMetadataByToken(token) +} +``` + +Magic.link recommends using the issuer as the userID to retrieve user metadata via `Prisma` + +```jsx title="redwood/api/src/lib/auth.ts" +import { Magic } from '@magic-sdk/admin' + +export const getCurrentUser = async (_decoded, { token }) => { + const mAdmin = new Magic(process.env.MAGICLINK_SECRET) + const { email, publicAddress, issuer } = await mAdmin.users.getMetadataByToken(token) + + return await db.user.findUnique({ where: { issuer } }) +} +``` diff --git a/docs/versioned_docs/version-2.0/auth/netlify.md b/docs/versioned_docs/version-2.0/auth/netlify.md new file mode 100644 index 000000000000..790ce1b994b7 --- /dev/null +++ b/docs/versioned_docs/version-2.0/auth/netlify.md @@ -0,0 +1,88 @@ +--- +sidebar_label: Netlify +--- + +# Netlify Identity Authentication + +## Installation + +The following CLI command will install required packages and generate boilerplate code and files for Redwood Projects: + +```bash +yarn rw setup auth netlify +``` + +_If you prefer to manually install the package and add code_, run the following command and then add the required code provided in the next section. + +```bash +cd web +yarn add @redwoodjs/auth netlify-identity-widget +``` + +## Setup + +You will need to enable Identity on your Netlify site. + +```jsx title="web/src/App.js" +import { AuthProvider } from '@redwoodjs/auth' +import netlifyIdentity from 'netlify-identity-widget' +import { isBrowser } from '@redwoodjs/prerender/browserUtils' +import { FatalErrorBoundary } from '@redwoodjs/web' +import { RedwoodApolloProvider } from '@redwoodjs/web/apollo' + +import FatalErrorPage from 'src/pages/FatalErrorPage' +import Routes from 'src/Routes' + +import './index.css' + +isBrowser && netlifyIdentity.init() + +const App = () => ( + + + + + + + +) + +export default App +``` + +## Netlify Identity Auth Provider Specific Setup + +See the Netlify Identity information within this doc's [Auth Provider Specific Integration](#auth-provider-specific-integration) section. + + +## Integration + +[Netlify Identity](https://docs.netlify.com/visitor-access/identity) offers [Role-based access control (RBAC)](https://docs.netlify.com/visitor-access/identity/manage-existing-users/#user-account-metadata). + +### Role-based access control (RBAC) + +Role-based access control (RBAC) refers to the idea of assigning permissions to users based on their role within an organization. It provides fine-grained control and offers a simple, manageable approach to access management that is less prone to error than assigning permissions to users individually. + +Essentially, a role is a collection of permissions that you can apply to users. A role might be "admin", "editor" or "publisher". This differs from permissions an example of which might be "publish:blog". + +### App Metadata + +Netlify Identity stores information (such as, support plan subscriptions, security roles, or access control groups) in `app_metadata`. Data stored in `app_metadata` cannot be edited by users. + +Create and manage roles for your application in Netlify's "Identity" management views. You can then assign these roles to users. + +### Add Application `hasRole` Support + +If you intend to support, RBAC then in your `api/src/lib/auth.js` you need to extract `roles` using the `parseJWT` utility and set these roles on `currentUser`. + +Netlify will store the user's roles on the `app_metadata` claim and the `parseJWT` function provides an option to extract the roles so they can be assigned to the `currentUser`. + +For example: + +```jsx title="api/src/lib/auth.js" +export const getCurrentUser = async (decoded) => { + return context.currentUser || { ...decoded, roles: parseJWT({ decoded }).roles } +} +``` + +Now your `currentUser.roles` info will be available to both `requireAuth()` on the api side and `hasRole()` on the web side. diff --git a/docs/versioned_docs/version-2.0/auth/nhost.md b/docs/versioned_docs/version-2.0/auth/nhost.md new file mode 100644 index 000000000000..0c11eb529d2b --- /dev/null +++ b/docs/versioned_docs/version-2.0/auth/nhost.md @@ -0,0 +1,47 @@ +--- +sidebar_label: Nhost +--- + +# Nhost Authentication + +## Installation + +The following CLI command will install required packages and generate boilerplate code and files for Redwood Projects: + +```bash +yarn rw setup auth nhost +``` + +## Setup + +Update your .env file with the following setting which can be found on your Nhost project's dashboard. + +- `NHOST_BACKEND_URL` with the unique Nhost Backend URL that can be found in the app's dashboard. +- `NHOST_JWT_SECRET` with the JWT Key secret that you have set in your project's Settings > Hasura "JWT Key" section. + +## Usage + +Nhost supports the following methods: + +- email/password +- passwordless with email +- passwordless with SMS +- OAuth Providers (via GitHub, Google, Facebook, Spotify, Discord, Twitch, Apple, Twitter, Microsoft and Linkedin). + +Depending on the credentials provided: + +- A user can sign in either via email or a supported OAuth provider. +- A user can sign up via email and password. For OAuth simply sign in and the user account will be created if it does not exist. +- Note: You must enable and configure the OAuth provider appropriately. To enable and configure a provider, please navigate to Users -> Login settings, from your app's dashboard. + +For the docs on Authentication, see: + +If you are also **using Nhost as your GraphQL API server**, you will need to pass `skipFetchCurrentUser` as a prop to `AuthProvider` , as follows: + +```jsx + +``` + +This avoids having an additional request to fetch the current user which is meant to work with Apollo Server and Prisma. + +Important: The `skipFetchCurrentUser` attribute is **only** needed if you are **not** using the standard RedwoodJS api side GraphQL Server. diff --git a/docs/versioned_docs/version-2.0/auth/supabase.md b/docs/versioned_docs/version-2.0/auth/supabase.md new file mode 100644 index 000000000000..d6a687110559 --- /dev/null +++ b/docs/versioned_docs/version-2.0/auth/supabase.md @@ -0,0 +1,60 @@ +--- +sidebar_label: Supabase +--- + +# Supabase Authentication + +## Installation + +The following CLI command will install required packages and generate boilerplate code and files for Redwood Projects: + +```bash +yarn rw setup auth supabase +``` + +## Setup + +Update your .env file with the following settings supplied when you created your new Supabase project: + +- `SUPABASE_URL` with the unique Supabase URL for your project +- `SUPABASE_KEY` with the unique Supabase Key that identifies which API KEY to use +- `SUPABASE_JWT_SECRET` with the secret used to sign and verify the JSON Web Token (JWT) + +You can find these values in your project's dashboard under Settings -> API. + +For full Supabase documentation, see: + +## Usage + +Supabase supports several sign in methods: + +- email/password +- passwordless via emailed magiclink +- authenticate via phone with SMS based OTP (One-Time Password) tokens. See: [SMS OTP with Twilio](https://supabase.io/docs/guides/auth/auth-twilio) +- Sign in with redirect. You can control where the user is redirected to after they are logged in via a `redirectTo` option. +- Sign in with a valid refresh token that was returned on login. +- Sign in using third-party providers/OAuth via + - [Apple](https://supabase.io/docs/guides/auth/auth-apple) + - Azure Active Directory + - [Bitbucket](https://supabase.io/docs/guides/auth/auth-bitbucket) + - [Discord](https://supabase.io/docs/guides/auth/auth-discord) + - [Facebook](https://supabase.io/docs/guides/auth/auth-facebook) + - [GitHub](https://supabase.io/docs/guides/auth/auth-github) + - [GitLab](https://supabase.io/docs/guides/auth/auth-gitlab) + - [Google](https://supabase.io/docs/guides/auth/auth-google) + - [Twitch](https://supabase.io/docs/guides/auth/auth-twitch) + - [Twitter](https://supabase.io/docs/guides/auth/auth-twitter) +- Sign in with a [valid refresh token](https://supabase.io/docs/reference/javascript/auth-signin#sign-in-using-a-refresh-token-eg-in-react-native) that was returned on login. Used e.g. in React Native. +- Sign in with scopes. If you need additional data from an OAuth provider, you can include a space-separated list of `scopes` in your request options to get back an OAuth `provider_token`. + +Depending on the credentials provided: + +- A user can sign up either via email or sign in with supported OAuth provider: `'apple' | 'azure' | 'bitbucket' | 'discord' | 'facebook' | 'github' | 'gitlab' | 'google' | 'twitch' | 'twitter'` +- If you sign in with a valid refreshToken, the current user will be updated +- If you provide email without a password, the user will be sent a magic link. +- The magic link's destination URL is determined by the SITE_URL config variable. To change this, you can go to Authentication -> Settings on `app.supabase.io` for your project. +- Specifying an OAuth provider will open the browser to the relevant login page +- Note: You must enable and configure the OAuth provider appropriately. To configure these providers, you can go to Authentication -> Settings on `app.supabase.io` for your project. +- Note: To authenticate using SMS based OTP (One-Time Password) you will need a [Twilio](https://www.twilio.com/try-twilio) account + +For Supabase Authentication documentation, see: diff --git a/docs/versioned_docs/version-2.0/auth/wallet-connect.md b/docs/versioned_docs/version-2.0/auth/wallet-connect.md new file mode 100644 index 000000000000..8b48202b2ac1 --- /dev/null +++ b/docs/versioned_docs/version-2.0/auth/wallet-connect.md @@ -0,0 +1,17 @@ +--- +sidebar_label: WalletConnect +--- + +# WalletConnect Authentication + +## Installation + +The following CLI command will install required packages and generate boilerplate code and files for Redwood Projects: + +```bash +yarn rw setup auth ethereum +``` + +## Setup + +To complete setup, you'll also need to update your `api` server manually. See https://github.com/oneclickdapp/ethereum-auth for instructions. diff --git a/docs/versioned_docs/version-2.0/authentication.md b/docs/versioned_docs/version-2.0/authentication.md index 8f38daafb5c0..12062f77f72c 100644 --- a/docs/versioned_docs/version-2.0/authentication.md +++ b/docs/versioned_docs/version-2.0/authentication.md @@ -8,977 +8,51 @@ description: Set up an authentication provider We currently support the following third-party authentication providers: -- Netlify Identity _([Repo on GitHub](https://github.com/netlify/netlify-identity-widget))_ -- Netlify GoTrue-JS _([Repo on GitHub](https://github.com/netlify/gotrue-js))_ - Auth0 _([Repo on GitHub](https://github.com/auth0/auth0-spa-js))_ -- Clerk _([Website](https://clerk.dev))_ - Azure Active Directory _([Repo on GitHub](https://github.com/AzureAD/microsoft-authentication-library-for-js))_ -- Magic Links - Magic.js _([Repo on GitHub](https://github.com/MagicHQ/magic-js))_ +- Clerk _([Website](https://clerk.dev))_ - Firebase _([Documentation Website](https://firebase.google.com/docs/auth))_ -- Supabase _([Documentation Website](https://supabase.io/docs/guides/auth))_ -- Ethereum _([Repo on GitHub](https://github.com/oneclickdapp/ethereum-auth))_ -- Nhost _([Documentation Website](https://docs.nhost.io/platform/authentication))_ -- Custom -- [Contribute one](https://github.com/redwoodjs/redwood/tree/main/packages/auth), it's SuperEasy™! - -> 👉 Check out the [Auth Playground](https://github.com/redwoodjs/playground-auth). - -## Self-hosted Auth Installation and Setup - -Redwood's own **dbAuth** provides several benefits: - -- Use your own database for storing user credentials -- Use your own login, signup and forgot password pages (or use Redwood's pre-built ones) -- Customize login session length -- No external dependencies -- No user data ever leaves your servers -- No additional charges/limits based on number of users -- No third party service outages affecting your site - -And potentially one large drawback: - -- Use your own database for storing user credentials - -However, we're following best practices for storing these credentials: - -1. Users' passwords are [salted and hashed](https://auth0.com/blog/adding-salt-to-hashing-a-better-way-to-store-passwords/) with PBKDF2 before being stored -2. Plaintext passwords are never stored anywhere, and only transferred between client and server during the login/signup phase (and hopefully only over HTTPS) -3. Our logger scrubs sensitive parameters (like `password`) before they are output - -Even if you later decide you want to let someone else handle your user data for you, dbAuth is a great option for getting up and running quickly (we even have a generator for creating basic login and signup pages for you). - -### How It Works - -dbAuth relies on good ol' fashioned cookies to determine whether a user is logged in or not. On an attempted login, a serverless function on the api-side checks whether a user exists with the given username (internally, dbAuth refers to this field as _username_ but you can use anything you want, like an email address). If a user with that username is found, does their salted and hashed password match the one in the database? - -If so, an [HttpOnly](https://owasp.org/www-community/HttpOnly), [Secure](https://owasp.org/www-community/controls/SecureCookieAttribute), [SameSite](https://owasp.org/www-community/SameSite) cookie (dbAuth calls this the "session cookie") is sent back to the browser containing the ID of the user. The content of the cookie is a simple string, but AES encrypted with a secret key (more on that later). - -When the user makes a GraphQL call, we decrypt the cookie and make sure that the user ID contained within still exists in the database. If so, the request is allowed to proceed. - -If there are any shenanigans detected (the cookie can't be decrypted properly, or the user ID found in the cookie does not exist in the database) the user is immediately logged out by expiring the session cookie. - -### Setup - -A single CLI command will get you everything you need to get dbAuth working, minus the actual login/signup pages: - - yarn rw setup auth dbAuth - -Read the post-install instructions carefully as they contain instructions for adding database fields for the hashed password and salt, as well as how to configure the auth serverless function based on the name of the table that stores your user data. Here they are, but could change in future releases: - -> You will need to add a couple of fields to your User table in order to store a hashed password and salt: -> -> model User { -> id Int @id @default(autoincrement()) -> email String @unique -> hashedPassword String // <─┐ -> salt String // <─┼─ add these lines -> resetToken String? // <─┤ -> resetTokenExpiresAt DateTime? // <─┘ -> } -> -> If you already have existing user records you will need to provide a default value or Prisma complains, so change those to: -> -> hashedPassword String @default("") -> salt String @default("") -> -> You'll need to let Redwood know what field you're using for your users' `id` and `username` fields In this case we're using `id` and `email`, so update those in the `authFields` config in `/api/src/functions/auth.js` (this is also the place to tell Redwood if you used a different name for the `hashedPassword` or `salt` fields): -> -> authFields: { -> id: 'id', -> username: 'email', -> hashedPassword: 'hashedPassword', -> salt: 'salt', -> resetToken: 'resetToken', -> resetTokenExpiresAt: 'resetTokenExpiresAt', -> }, -> -> To get the actual user that's logged in, take a look at `getCurrentUser()` in `/api/src/lib/auth.js`. We default it to something simple, but you may use different names for your model or unique ID fields, in which case you need to update those calls (instructions are in the comment above the code). -> -> Finally, we created a `SESSION_SECRET` environment variable for you in `.env`. This value should NOT be checked into version control and should be unique for each environment you deploy to. If you ever need to log everyone out of your app at once change this secret to a new value. To create a new secret, run: -> -> yarn rw g secret -> -> Need simple Login, Signup and Forgot Password pages? Of course we have a generator for those: -> -> yarn rw generate dbAuth - -Note that if you change the fields named `hashedPassword` and `salt`, and you have some verbose logging in your app, you'll want to scrub those fields from appearing in your logs. See the [Redaction](logger.md#redaction) docs for info. - -### Scaffolding Login/Signup/Forgot Password Pages - -If you don't want to create your own login, signup and forgot password pages from scratch we've got a generator for that: - - yarn rw g dbAuth - -The default routes will make them available at `/login`, `/signup`, `/forgot-password`, and `/reset-password` but that's easy enough to change. Again, check the post-install instructions for one change you need to make to those pages: where to redirect the user to once their login/signup is successful. - -If you'd rather create your own, you might want to start from the generated pages anyway as they'll contain the other code you need to actually submit the login credentials or signup fields to the server for processing. - -### Configuration - -Almost all config for dbAuth lives in `api/src/functions/auth.js` in the object you give to the `DbAuthHandler` initialization. The comments above each key will explain what goes where. Here's an overview of the more important options: - -#### login.handler() - -If you want to do something other than immediately let a user log in if their username/password is correct, you can add additional logic in `login.handler()`. For example, if a user's credentials are correct, but they haven't verified their email address yet, you can throw an error in this function with the appropriate message and then display it to the user. If the login should proceed, simply return the user that was passed as the only argument to the function: - -```jsx -login: { - handler: (user) => { - if (!user.verified) { - throw new Error('Please validate your email first!') - } else { - return user - } - } -} -``` - -#### signup.handler() - -This function should contain the code needed to actually create a user in your database. You will receive a single argument which is an object with all of the fields necessary to create the user (`username`, `hashedPassword` and `salt`) as well as any additional fields you included in your signup form in an object called `userAttributes`: - -```jsx -signup: { - handler: ({ username, hashedPassword, salt, userAttributes }) => { - return db.user.create({ - data: { - email: username, - hashedPassword: hashedPassword, - salt: salt, - name: userAttributes.name, - }, - }) - } -} -``` - -Before `signup.handler()` is invoked, dbAuth will check that the username is unique in the database and throw an error if not. - -There are three things you can do within this function depending on how you want the signup to proceed: - -1. If everything is good and the user should be logged in after signup: return the user you just created -2. If the user is safe to create, but you do not want to log them in automatically: return a string, which will be returned by the `signUp()` function you called after destructuring it from `useAuth()` (see code snippet below) -3. If the user should _not_ be able to sign up for whatever reason: throw an error in this function with the message to be displayed - -You can deal with case #2 by doing something like the following in a signup component/page: - -```jsx -const { signUp } = useAuth() - -const onSubmit = async (data) => { - const response = await signUp({ ...data }) - - if (response.message) { - toast.error(response.message) // user created, but not logged in - } else { - toast.success('Welcome!') // user created and logged in - navigate(routes.dashboard()) - } -} -``` - -#### forgotPassword.handler() - -This handler is invoked if a user is found with the username/email that they submitted on the Forgot Password page, and that user will be passed as an argument. Inside this function is where you'll send the user a link to reset their password—via an email is most common. The link will, by default, look like: - - https://example.com/reset-password?resetToken=${user.resetToken} - -If you changed the path to the Reset Password page in your routes you'll need to change it here. If you used another name for the `resetToken` database field, you'll need to change that here as well: - - https://example.com/reset-password?resetKey=${user.resetKey} - -#### resetPassword.handler() - -This handler is invoked after the password has been successfully changed in the database. Returning something truthy (like `return user`) will automatically log the user in after their password is changed. If you'd like to return them to the login page and make them log in manually, `return false` and redirect the user in the Reset Password page. - -#### Cookie config - -These options determine how the cookie that tracks whether the client is authorized is stored in the browser. The default configuration should work for most use cases. If you serve your web and api sides from different domains you'll need to make some changes: set `SameSite` to `None` and then add [CORS configuration](#cors-config). - -```jsx -cookie: { - HttpOnly: true, - Path: '/', - SameSite: 'Strict', - Secure: true, - // Domain: 'example.com', -} -``` - -#### CORS config - -If you're using dbAuth and your api and web sides are deployed to different domains then you'll need to configure CORS for both GraphQL in general and dbAuth. You'll also need to enable a couple of options to be sure and send/accept credentials in XHR requests. For more info, see the complete [CORS doc](cors.md#cors-and-authentication). - -#### Error Messages - -There are several error messages that can be displayed, including: - -- Username/email not found -- Incorrect password -- Expired reset password token - -We've got some default error messages that sound nice, but may not fit the tone of your site. You can customize these error messages in `api/src/functions/auth.js` in the `errors` prop of each of the `login`, `signup`, `forgotPassword` and `resetPassword` config objects. The generated file contains tons of comments explaining when each particular error message may be shown. - -### Environment Variables - -#### Cookie Domain - -By default, the session cookie will not have the `Domain` property set, which a browser will default to be the [current domain only](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#define_where_cookies_are_sent). If your site is spread across multiple domains (for example, your site is at `example.com` but your api-side is deployed to `api.example.com`) you'll need to explicitly set a Domain so that the cookie is accessible to both. - -To do this, create an environment variable named `DBAUTH_COOKIE_DOMAIN` set to the root domain of your site, which will allow it to be read by all subdomains as well. For example: - - DBAUTH_COOKIE_DOMAIN=example.com - -#### Session Secret Key - -If you need to change the secret key that's used to encrypt the session cookie, or deploy to a new target (each deploy environment should have its own unique secret key) we've got a CLI tool for creating a new one: - - yarn rw g secret - -Note that the secret that's output is _not_ appended to your `.env` file or anything else, it's merely output to the screen. You'll need to put it in the right place after that. - -> The `.env` file is set to be ignored by git and not committed to version control. There is another file, `.env.defaults`, which is meant to be safe to commit and contain simple ENV vars that your dev team can share. The encryption key for the session cookie is NOT one of these shareable vars! - -## Third Party Providers Installation and Setup - -You will need to instantiate your authentication client and pass it to the ``. See instructions below for your specific provider. - -### Netlify Identity Widget - -+++ View Installation and Setup - -#### Installation - -The following CLI command will install required packages and generate boilerplate code and files for Redwood Projects: - -```bash -yarn rw setup auth netlify -``` - -_If you prefer to manually install the package and add code_, run the following command and then add the required code provided in the next section. - -```bash -cd web -yarn add @redwoodjs/auth netlify-identity-widget -``` - -#### Setup - -You will need to enable Identity on your Netlify site. - - -```jsx title="web/src/App.js" -import { AuthProvider } from '@redwoodjs/auth' -import netlifyIdentity from 'netlify-identity-widget' -import { isBrowser } from '@redwoodjs/prerender/browserUtils' -import { FatalErrorBoundary } from '@redwoodjs/web' -import { RedwoodApolloProvider } from '@redwoodjs/web/apollo' - -import FatalErrorPage from 'src/pages/FatalErrorPage' -import Routes from 'src/Routes' - -import './index.css' - -isBrowser && netlifyIdentity.init() - -const App = () => ( - - - - - - - -) - -export default App -``` - -#### Netlify Identity Auth Provider Specific Setup - -See the Netlify Identity information within this doc's [Auth Provider Specific Integration](#auth-provider-specific-integration) section. - -+++ - -### GoTrue-JS - -+++ View Installation and Setup - -#### Installation - -The following CLI command will install required packages and generate boilerplate code and files for Redwood Projects: - -```bash -yarn rw setup auth goTrue -``` - -_If you prefer to manually install the package and add code_, run the following command and then add the required code provided in the next section. - -```bash -cd web -yarn add @redwoodjs/auth gotrue-js -``` - -#### Setup - -You will need to enable Identity on your Netlify site. - - -Add the GoTrue-JS package to the web side: - -```bash -yarn workspace web add gotrue-js -``` - -Instantiate GoTrue and pass in your configuration. Be sure to set APIUrl to the API endpoint found in your Netlify site's Identity tab: - -```jsx title="web/src/App.js" -import { AuthProvider } from '@redwoodjs/auth' -import GoTrue from 'gotrue-js' -import { FatalErrorBoundary } from '@redwoodjs/web' -import { RedwoodApolloProvider } from '@redwoodjs/web/apollo' - -import FatalErrorPage from 'src/pages/FatalErrorPage' -import Routes from 'src/Routes' - -import './index.css' - -const goTrueClient = new GoTrue({ - APIUrl: 'https://MYAPP.netlify.app/.netlify/identity', - setCookie: true, -}) - -const App = () => ( - - - - - - - -) - -export default App -``` - -+++ - -### Auth0 - -+++ View Installation and Setup - -#### Installation - -The following CLI command will install required packages and generate boilerplate code and files for Redwood Projects: - -```bash -yarn rw setup auth auth0 -``` - -_If you prefer to manually install the package and add code_, run the following command and then add the required code provided in the next section. - -```bash -cd web -yarn add @redwoodjs/auth @auth0/auth0-spa-js -``` - -#### Setup - -To get your application keys, only complete the ["Configure Auth0"](https://auth0.com/docs/quickstart/spa/react#get-your-application-keys) section of the SPA Quickstart guide. - -**NOTE** If you're using Auth0 with Redwood then you must also [create an API](https://auth0.com/docs/quickstart/spa/react/02-calling-an-api#create-an-api) and set the audience parameter, or you'll receive an opaque token instead of the required JWT token. - -The `useRefreshTokens` options is required for automatically extending sessions beyond that set in the initial JWT expiration (often 3600/1 hour or 86400/1 day). - -If you want to allow users to get refresh tokens while offline, you must also enable the Allow Offline Access switch in your Auth0 API Settings as part of setup configuration. See: [https://auth0.com/docs/tokens/refresh-tokens](https://auth0.com/docs/tokens/refresh-tokens) - -You can increase security by using refresh token rotation which issues a new refresh token and invalidates the predecessor token with each request made to Auth0 for a new access token. - -Rotating the refresh token reduces the risk of a compromised refresh token. For more information, see: [https://auth0.com/docs/tokens/refresh-tokens/refresh-token-rotation](https://auth0.com/docs/tokens/refresh-tokens/refresh-token-rotation). - -> **Including Environment Variables in Serverless Deployment:** in addition to adding the following env vars to your deployment hosting provider, you _must_ take an additional step to include them in your deployment build process. Using the names exactly as given below, follow the instructions in [this document](environment-variables.md) to include them in your `redwood.toml`. - -```jsx title="web/src/App.js" -import { AuthProvider } from '@redwoodjs/auth' -import { Auth0Client } from '@auth0/auth0-spa-js' -import { FatalErrorBoundary } from '@redwoodjs/web' -import { RedwoodApolloProvider } from '@redwoodjs/web/apollo' - -import FatalErrorPage from 'src/pages/FatalErrorPage' -import Routes from 'src/Routes' - -import './index.css' - -const auth0 = new Auth0Client({ - domain: process.env.AUTH0_DOMAIN, - client_id: process.env.AUTH0_CLIENT_ID, - redirect_uri: process.env.AUTH0_REDIRECT_URI, - - // ** NOTE ** Storing tokens in browser local storage provides persistence across page refreshes and browser tabs. - // However, if an attacker can achieve running JavaScript in the SPA using a cross-site scripting (XSS) attack, - // they can retrieve the tokens stored in local storage. - // https://auth0.com/docs/libraries/auth0-spa-js#change-storage-options - cacheLocation: 'localstorage', - audience: process.env.AUTH0_AUDIENCE, - - // @MARK: useRefreshTokens is required for automatically extending sessions - // beyond that set in the initial JWT expiration. - // - // @MARK: https://auth0.com/docs/tokens/refresh-tokens - // useRefreshTokens: true, -}) - -const App = () => ( - - - - - - - -) - -export default App -``` - -#### Login and Logout Options - -When using the Auth0 client, `login` and `logout` take `options` that can be used to override the client config: - -- `returnTo`: a permitted logout url set in Auth0 -- `redirectTo`: a target url after login - -The latter is helpful when an unauthenticated user visits a Private route, but then is redirected to the `unauthenticated` route. The Redwood router will place the previous requested path in the pathname as a `redirectTo` parameter which can be extracted and set in the Auth0 `appState`. That way, after successfully logging in, the user will be directed to this `targetUrl` rather than the config's callback. - -```jsx -const UserAuthTools = () => { - const { loading, isAuthenticated, logIn, logOut } = useAuth() - - if (loading) { - // auth is rehydrating - return null - } - - return ( - - ) -} -``` - -#### Auth0 Auth Provider Specific Setup - -See the Auth0 information within this doc's [Auth Provider Specific Integration](#auth-provider-specific-integration) section. - -+++ - -### Clerk - -+++ View Installation and Setup - -#### Installation - -The following CLI command will install required packages and generate boilerplate code and files for Redwood Projects: - -```bash -yarn rw setup auth clerk -``` - -#### Setup - -To get started with Clerk, sign up on [their website](https://clerk.dev/) and create an application, or follow their [RedwoodJS Blog Tutorial with Clerk](https://clerk.dev/tutorials/redwoodjs-blog-tutorial-with-clerk) that has an [example repo](https://github.com/redwoodjs/redwood-tutorial) already setup. - -It's important that the `ClerkAuthProvider` added to your `App.{js|ts}` file during setup is within the `RedwoodProvider` and around Redwood's `AuthProvider`: - -```tsx {4,10} title="web/src/App.{js|ts}" -const App = () => ( - - - - - - - - - - - -) -``` - -The [RedwoodJS Blog Tutorial with Clerk](https://clerk.dev/tutorials/redwoodjs-blog-tutorial-with-clerk) also explains how to use `@clerk/clerk-react` components with Redwood's `useAuth()` hook: - -```tsx -import { UserButton, SignInButton } from '@clerk/clerk-react' - -// ... - -{ - isAuthenticated ? ( - - ) : ( - - - - ) -} -``` - -Applications in Clerk have different instances. By default, there's one for development, one for staging, and one for production. You'll need to pull three values from one of these instances. We recommend storing the development values in your local `.env` file and using the staging and production values in the appropriate env setups for your hosting platform when you deploy. - -The three values you'll need from Clerk are your instance's "Frontend API Key" url, a "Backend API key" and a "JWT verification key", all from your instance's settings under "API Keys". The Frontend API url should be stored in an env variable named `CLERK_FRONTEND_API_URL`. The Backend API key should be named `CLERK_API_KEY`. Finally, the JWT key should be named `CLERK_JWT_KEY` - -Otherwise, feel free to configure your instances however you wish with regards to their appearance and functionality. - -> **Including Environment Variables in Serverless Deploys** -> -> In addition to adding these env vars to your local `.env` file or deployment hosting provider, you _must_ take an additional step to include them in your deployment build process. Using the names exactly as given above, follow the instructions in [this document](environment-variables.md). You need to expose the `CLERK_FRONTEND_API_URL` variable to the `web` side. - -#### Login and Logout Options - -When using the Clerk client, `login` and `signUp` take an `options` object that can be used to override the client config. - -For `login` the `options` may contain all the options listed at the Clerk [props documentation for login](https://docs.clerk.dev/reference/clerkjs/clerk#signinprops). - -For `signUp` the `options` may contain all the options listed at the Clerk [props documentation for signup](https://docs.clerk.dev/reference/clerkjs/clerk#signupprops). - -#### Avoiding Feature Duplication Confusion - -Redwood's integration of Clerk is based on [Clerk's React SDK](https://docs.clerk.dev/reference/clerk-react). This means there is some duplication between the features available through that SDK and the ones available in the `@redwoodjs/auth` package - such as the alternatives of using Clerk's `SignedOut` component to redirect users away from a private page vs. using Redwood's `Private` route wrapper. In general, we would recommend you use the **Redwood** way of doing things when possible, as that is more likely to function harmoniously with the rest of Redwood. That being said, though, there are some great features in Clerk's SDK that you will be able to now use in your app, such as the `UserButton` and `UserProfile` components. - -+++ - -### Azure Active Directory - -+++ View Installation and Setup - -#### Installation - -The following CLI command will install required packages and generate boilerplate code and files for Redwood Projects: - -```bash -yarn rw setup auth azureActiveDirectory -``` - -_If you prefer to manually install the package and add code_, run the following command and then add the required code provided in the next section. - -```bash -cd web -yarn add @azure/msal-browser -``` - -#### Setup - -To get your application credentials, create an [App Registration](https://docs.microsoft.com/en-us/azure/active-directory/develop/scenario-spa-app-registration) using in your Azure Active Directory tenant and make sure you configure as a [MSAL.js 2.0 with auth code flow](https://docs.microsoft.com/en-us/azure/active-directory/develop/scenario-spa-app-registration#redirect-uri-msaljs-20-with-auth-code-flow) registration. Take a note of your generated _Application ID_ (client), and the _Directory ID_ (tenant). - -[Learn more about authorization code flow](https://docs.microsoft.com/en-us/azure/active-directory/develop/reference-third-party-cookies-spas). - -##### Redirect URIs - -Enter allowed redirect urls for the integrations, e.g. `http://localhost:8910/login`. This will be the `AZURE_ACTIVE_DIRECTORY_REDIRECT_URI` environment variable, and suggestively `AZURE_ACTIVE_DIRECTORY_LOGOUT_REDIRECT_URI`. - -#### Authority - -The Authority is a URL that indicates a directory that MSAL can request tokens from which you can read about [here](https://docs.microsoft.com/en-us/azure/active-directory/develop/msal-client-application-configuration#authority). However, you most likely want to have e.g. `https://login.microsoftonline.com/` as Authority URL, where `` is the Azure Active Directory tenant id. This will be the `AZURE_ACTIVE_DIRECTORY_AUTHORITY` environment variable. - -```jsx title="web/src/App.js" -import { AuthProvider } from '@redwoodjs/auth' -import { PublicClientApplication } from '@azure/msal-browser' -import { FatalErrorBoundary } from '@redwoodjs/web' -import { RedwoodApolloProvider } from '@redwoodjs/web/apollo' - -import FatalErrorPage from 'src/pages/FatalErrorPage' -import Routes from 'src/Routes' - -import './index.css' - -const azureActiveDirectoryClient = new PublicClientApplication({ - auth: { - clientId: process.env.AZURE_ACTIVE_DIRECTORY_CLIENT_ID, - authority: process.env.AZURE_ACTIVE_DIRECTORY_AUTHORITY, - redirectUri: process.env.AZURE_ACTIVE_DIRECTORY_REDIRECT_URI, - postLogoutRedirectUri: process.env.AZURE_ACTIVE_DIRECTORY_LOGOUT_REDIRECT_URI, - }, -}) - -const App = () => ( - - - - - - - -) - -export default App -``` - -#### Roles - -To setup your App Registration with custom roles and have them exposed via the `roles` claim, follow [this documentation](https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-add-app-roles-in-azure-ad-apps). - -#### Login Options - -Options in method `logIn(options?)` is of type [RedirectRequest](https://azuread.github.io/microsoft-authentication-library-for-js/ref/modules/_azure_msal_browser.html#redirectrequest) and is a good place to pass in optional [scopes](https://docs.microsoft.com/en-us/graph/permissions-reference#user-permissions) to be authorized. By default, MSAL sets `scopes` to [/.default](https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent#the-default-scope) which is built in for every application that refers to the static list of permissions configured on the application registration. Furthermore, MSAL will add `openid` and `profile` to all requests. In example below we explicit include `User.Read.All` to the login scope. - -```jsx -await logIn({ - scopes: ['User.Read.All'], // becomes ['openid', 'profile', 'User.Read.All'] -}) -``` - -See [loginRedirect](https://azuread.github.io/microsoft-authentication-library-for-js/ref/classes/_azure_msal_browser.publicclientapplication.html#loginredirect), [PublicClientApplication](https://azuread.github.io/microsoft-authentication-library-for-js/ref/classes/_azure_msal_browser.publicclientapplication.html) class and [Scopes Behavior](https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-core/docs/scopes.md#scopes-behavior) for more documentation. - -#### getToken Options - -Options in method `getToken(options?)` is of type [RedirectRequest](https://azuread.github.io/microsoft-authentication-library-for-js/ref/modules/_azure_msal_browser.html#redirectrequest). By default, `getToken` will be called with scope `['openid', 'profile']`. As Azure Active Directory apply [incremental consent](https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/resources-and-scopes.md#dynamic-scopes-and-incremental-consent), we can extend the permissions from the login example by including another scope, for example `Mail.Read`. - -```jsx -await getToken({ - scopes: ['Mail.Read'], // becomes ['openid', 'profile', 'User.Read.All', 'Mail.Read'] -}) -``` - -See [acquireTokenSilent](https://azuread.github.io/microsoft-authentication-library-for-js/ref/classes/_azure_msal_browser.publicclientapplication.html#acquiretokensilent), [Resources and Scopes](https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/resources-and-scopes.md#resources-and-scopes) or [full class documentation](https://pub.dev/documentation/msal_js/latest/msal_js/PublicClientApplication-class.html#constructors) for more documentation. - -+++ - -### Magic.Link - -+++ View Installation and Setup - -#### Installation - -The following CLI command will install required packages and generate boilerplate code and files for Redwood Projects: - -```bash -yarn rw setup auth magicLink -``` - -_If you prefer to manually install the package and add code_, run the following command and then add the required code provided in the next section. - -```bash -cd web -yarn add @redwoodjs/auth magic-sdk -``` - -#### Setup - -To get your application keys, go to [dashboard.magic.link](https://dashboard.magic.link/) then navigate to the API keys add them to your `.env`. - -> **Including Environment Variables in Serverless Deployment:** in addition to adding the following env vars to your deployment hosting provider, you _must_ take an additional step to include them in your deployment build process. Using the names exactly as given below, follow the instructions in [this document](environment-variables.md) to "Whitelist them in your `redwood.toml`". - -```jsx title="web/src/App.js|tsx" -import { useAuth, AuthProvider } from '@redwoodjs/auth' -import { Magic } from 'magic-sdk' -import { FatalErrorBoundary } from '@redwoodjs/web' -import { RedwoodApolloProvider } from '@redwoodjs/web/apollo' - -import FatalErrorPage from 'src/pages/FatalErrorPage' -import Routes from 'src/Routes' - -import './index.css' - -const m = new Magic(process.env.MAGICLINK_PUBLIC) - -const App = () => ( - - - - - - - -) - -export default App -``` - -```jsx title="web/src/Routes.js|tsx" -import { useAuth } from '@redwoodjs/auth' -import { Router, Route } from '@redwoodjs/router' - -const Routes = () => { - return ( - - - - - ) -} - -export default Routes -``` - -#### Magic.Link Auth Provider Specific Integration - -See the Magic.Link information within this doc's [Auth Provider Specific Integration](#auth-provider-specific-integration) section. -+++ - -### Firebase - -+++ View Installation and Setup - -#### Installation - -The following CLI command will install required packages and generate boilerplate code and files for Redwood Projects: - -```bash -yarn rw setup auth firebase -``` - -#### Setup - -We're using [Firebase Google Sign-In](https://firebase.google.com/docs/auth/web/google-signin), so you'll have to follow the ["Before you begin"](https://firebase.google.com/docs/auth/web/google-signin#before_you_begin) steps in this guide. **Only** follow the "Before you begin" parts. - -> **Including Environment Variables in Serverless Deployment:** in addition to adding the following env vars to your deployment hosting provider, you _must_ take an additional step to include them in your deployment build process. Using the names exactly as given below, follow the instructions in [this document](https://redwoodjs.com/docs/environment-variables) to "Whitelist them in your `redwood.toml`". - -```jsx title="web/src/App.js" -import { AuthProvider } from '@redwoodjs/auth' -import { initializeApp, getApps, getApp } from '@firebase/app' -import * as firebaseAuth from '@firebase/auth' -import { FatalErrorBoundary } from '@redwoodjs/web' -import { RedwoodApolloProvider } from '@redwoodjs/web/apollo' - -import FatalErrorPage from 'src/pages/FatalErrorPage' -import Routes from 'src/Routes' - -import './index.css' - -const firebaseClientConfig = { - apiKey: process.env.FIREBASE_API_KEY, - authDomain: process.env.FIREBASE_AUTH_DOMAIN, - databaseURL: process.env.FIREBASE_DATABASE_URL, - projectId: process.env.FIREBASE_PROJECT_ID, - storageBucket: process.env.FIREBASE_STORAGE_BUCKET, - messagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID, - appId: process.env.FIREBASE_APP_ID, -} - -const firebaseApp = ((config) => { - const apps = getApps() - if (!apps.length) { - initializeApp(config) - } - return getApp() -})(firebaseConfig) - -export const firebaseClient = { - firebaseAuth, - firebaseApp, -} - -const App = () => ( - - - - - - - -) - -export default App -``` - -#### Usage - -```jsx -const UserAuthTools = () => { - const { loading, isAuthenticated, logIn, logOut } = useAuth() - - if (loading) { - return null - } - - return ( - - ) -} -``` - -#### Firebase Auth Provider Specific Integration - -See the Firebase information within this doc's [Auth Provider Specific Integration](#auth-provider-specific-integration) section. -+++ - -### Supabase - -+++ View Installation and Setup - -#### Installation - -The following CLI command will install required packages and generate boilerplate code and files for Redwood Projects: - -```bash -yarn rw setup auth supabase -``` - -#### Setup - -Update your .env file with the following settings supplied when you created your new Supabase project: - -- `SUPABASE_URL` with the unique Supabase URL for your project -- `SUPABASE_KEY` with the unique Supabase Key that identifies which API KEY to use -- `SUPABASE_JWT_SECRET` with the secret used to sign and verify the JSON Web Token (JWT) - -You can find these values in your project's dashboard under Settings -> API. - -For full Supabase documentation, see: - -#### Usage - -Supabase supports several sign in methods: - -- email/password -- passwordless via emailed magiclink -- authenticate via phone with SMS based OTP (One-Time Password) tokens. See: [SMS OTP with Twilio](https://supabase.io/docs/guides/auth/auth-twilio) -- Sign in with redirect. You can control where the user is redirected to after they are logged in via a `redirectTo` option. -- Sign in with a valid refresh token that was returned on login. -- Sign in using third-party providers/OAuth via - - [Apple](https://supabase.io/docs/guides/auth/auth-apple) - - Azure Active Directory - - [Bitbucket](https://supabase.io/docs/guides/auth/auth-bitbucket) - - [Discord](https://supabase.io/docs/guides/auth/auth-discord) - - [Facebook](https://supabase.io/docs/guides/auth/auth-facebook) - - [GitHub](https://supabase.io/docs/guides/auth/auth-github) - - [GitLab](https://supabase.io/docs/guides/auth/auth-gitlab) - - [Google](https://supabase.io/docs/guides/auth/auth-google) - - [Twitch](https://supabase.io/docs/guides/auth/auth-twitch) - - [Twitter](https://supabase.io/docs/guides/auth/auth-twitter) -- Sign in with a [valid refresh token](https://supabase.io/docs/reference/javascript/auth-signin#sign-in-using-a-refresh-token-eg-in-react-native) that was returned on login. Used e.g. in React Native. -- Sign in with scopes. If you need additional data from an OAuth provider, you can include a space-separated list of `scopes` in your request options to get back an OAuth `provider_token`. - -Depending on the credentials provided: - -- A user can sign up either via email or sign in with supported OAuth provider: `'apple' | 'azure' | 'bitbucket' | 'discord' | 'facebook' | 'github' | 'gitlab' | 'google' | 'twitch' | 'twitter'` -- If you sign in with a valid refreshToken, the current user will be updated -- If you provide email without a password, the user will be sent a magic link. -- The magic link's destination URL is determined by the SITE_URL config variable. To change this, you can go to Authentication -> Settings on `app.supabase.io` for your project. -- Specifying an OAuth provider will open the browser to the relevant login page -- Note: You must enable and configure the OAuth provider appropriately. To configure these providers, you can go to Authentication -> Settings on `app.supabase.io` for your project. -- Note: To authenticate using SMS based OTP (One-Time Password) you will need a [Twilio](https://www.twilio.com/try-twilio) account - -For Supabase Authentication documentation, see: - -+++ - -### Ethereum - -+++ View Installation and Setup - -#### Installation - -The following CLI command will install required packages and generate boilerplate code and files for Redwood Projects: - -```bash -yarn rw setup auth ethereum -``` - -#### Setup - -To complete setup, you'll also need to update your `api` server manually. See https://github.com/oneclickdapp/ethereum-auth for instructions. - -+++ - -### Nhost - -+++ View Installation and Setup - -#### Installation - -The following CLI command will install required packages and generate boilerplate code and files for Redwood Projects: - -```bash -yarn rw setup auth nhost -``` - -#### Setup - -Update your .env file with the following setting which can be found on your Nhost project's dashboard. +- Magic Links - Magic.js _([Repo on GitHub](https://github.com/MagicHQ/magic-js))_ +- Netlify Identity _([Repo on GitHub](https://github.com/netlify/netlify-identity-widget))_ +- Netlify GoTrue-JS _([Repo on GitHub](https://github.com/netlify/gotrue-js))_ +- Nhost _([Documentation Website](https://docs.nhost.io/platform/authentication))_ +- Supabase _([Documentation Website](https://supabase.io/docs/guides/auth))_ +- WalletConnect _([Repo on GitHub](https://github.com/oneclickdapp/ethereum-auth))_ -- `NHOST_BACKEND_URL` with the unique Nhost Backend URL that can be found in the app's dashboard. -- `NHOST_JWT_SECRET` with the JWT Key secret that you have set in your project's Settings > Hasura "JWT Key" section. +You can also implement your own custom auth client. Check out the [Custom docs](auth/custom) for more info. -#### Usage +:::info Auth Playground -Nhost supports the following methods: +Check out the [Auth Playground](https://redwood-playground-auth.netlify.app/) for examples of the auth experience with each provider or check out [the source code](https://redwood-playground-auth.netlify.app/). -- email/password -- passwordless with email -- passwordless with SMS -- OAuth Providers (via GitHub, Google, Facebook, Spotify, Discord, Twitch, Apple, Twitter, Microsoft and Linkedin). +::: -Depending on the credentials provided: +## Auth Installation and Setup -- A user can sign in either via email or a supported OAuth provider. -- A user can sign up via email and password. For OAuth simply sign in and the user account will be created if it does not exist. -- Note: You must enable and configure the OAuth provider appropriately. To enable and configure a provider, please navigate to Users -> Login settings, from your app's dashboard. +You will need to instantiate your authentication client and pass it to the ``. See individual auth docs in the menu for your specific provider. -For the docs on Authentication, see: +## Authentication on the Web Side -If you are also **using Nhost as your GraphQL API server**, you will need to pass `skipFetchCurrentUser` as a prop to `AuthProvider` , as follows: +Once your auth provider is set up you'll get access to the various authentication variables and functions by destructuring them from the `useAuth` hook: ```jsx - -``` - -This avoids having an additional request to fetch the current user which is meant to work with Apollo Server and Prisma. - -Important: The `skipFetchCurrentUser` attribute is **only** needed if you are **not** using the standard RedwoodJS api side GraphQL Server. -+++ - -### Custom - -+++ View Installation and Setup - -#### Installation - -The following CLI command (not implemented, see https://github.com/redwoodjs/redwood/issues/1585) will install required packages and generate boilerplate code and files for Redwood Projects: +import { useAuth } from '@redwoodjs/auth' -```bash -yarn rw setup auth custom +export const MyComponent = () => { + const { currentUser, isAuthenticated, logIn, logOut } = useAuth() + + return ( +
    +
  • The current user is: {currentUser}
  • +
  • Is the user logged in? {isAuthenticated}
  • +
  • Click to
  • +
  • Click to
  • +
+ ) +} ``` -#### Setup - -It is possible to implement a custom provider for Redwood Auth. In which case you might also consider adding the provider to Redwood itself. - -If you are trying to implement your own auth, support is very early and limited at this time. Additionally, there are many considerations and responsibilities when it comes to managing custom auth. For most cases we recommend using an existing provider. - -However, there are examples contributed by developers in the Redwood forums and Discord server. - -The most complete example (although now a bit outdated) is found in [this forum thread](https://community.redwoodjs.com/t/custom-github-jwt-auth-with-redwood-auth/610). Here's another [helpful message in the thread](https://community.redwoodjs.com/t/custom-github-jwt-auth-with-redwood-auth/610/25). -+++ - -## API - -The following values are available from the `useAuth` hook: +The following variables and functions are available from the `useAuth` hook: - async `logIn(options?)`: Differs based on the client library, with Netlify Identity a pop-up is shown, and with Auth0 the user is redirected. Options are passed to the client. - async `logOut(options?)`: Log the current user out. Options are passed to the client. @@ -992,447 +66,29 @@ The following values are available from the `useAuth` hook: - `hasRole(['admin'])`: Determines if the current user is assigned a role like `"admin"` or assigned to any of the roles in a list such as `['editor', 'author']`. - `loading`: The auth state is restored asynchronously when the user visits the site for the first time, use this to determine if you have the correct state. -## Usage in Redwood - -Redwood provides a zeroconf experience when using our Auth package! - -### GraphQL Query and Mutations - -GraphQL requests automatically receive an `Authorization` JWT header when a user is authenticated. - -### Auth Provider API - -If a user is signed in, the `Authorization` token is verified, decoded and available in `context.currentUser` - -```jsx -import { context } from '@redwoodjs/api' - -console.log(context.currentUser) -// { -// sub: ' -// email: 'user@example.com', -// [...] -// } -``` - -You can map the "raw decoded JWT" into a real user object by passing a `getCurrentUser` function to `createGraphQLHandler` - -Our recommendation is to create a `src/lib/auth.js|ts` file that exports a `getCurrentUser`. (Note: You may already have stub functions.) - -```jsx -import { getCurrentUser } from 'src/lib/auth' -// Example: -// export const getCurrentUser = async (decoded) => { -// return await db.user.findUnique({ where: { decoded.email } }) -// } -// - -export const handler = createGraphQLHandler({ - schema: makeMergedSchema({ - schemas, - services: makeServices({ services }), - }), - getCurrentUser, -}) -``` - -The value returned by `getCurrentUser` is available in `context.currentUser` - -Use `requireAuth` in your services to check that a user is logged in, -whether or not they are assigned a role, and optionally raise an -error if they're not. - -```jsx -export const requireAuth = ({ roles }) => { - if (!isAuthenticated()) { - throw new AuthenticationError("You don't have permission to do that.") - } - - if (roles && !hasRole(roles)) { - throw new ForbiddenError("You don't have access to do that.") - } -}} -} -``` - -### Auth Provider Specific Integration - -#### Auth0 - -If you're using Auth0 you must also [create an API](https://auth0.com/docs/quickstart/spa/react/02-calling-an-api#create-an-api) and set the audience parameter, or you'll receive an opaque token instead of a JWT token, and Redwood expects to receive a JWT token. - -+++ View Auth0 Options - -#### Role-based access control (RBAC) in Auth0 - -[Role-based access control (RBAC)](https://auth0.com/docs/authorization/concepts/rbac) refers to the idea of assigning permissions to users based on their role within an organization. It provides fine-grained control and offers a simple, manageable approach to access management that is less prone to error than assigning permissions to users individually. - -Essentially, a role is a collection of permissions that you can apply to users. A role might be "admin", "editor" or "publisher". This differs from permissions an example of which might be "publish:blog". - -#### App metadata in Auth0 - -Auth0 stores information (such as, support plan subscriptions, security roles, or access control groups) in `app_metadata`. Data stored in `app_metadata` cannot be edited by users. - -Create and manage roles for your application in Auth0's "User & Role" management views. You can then assign these roles to users. - -However, that info is not immediately available on the user's `app_metadata` or to RedwoodJS when authenticating. - -If you assign your user the "admin" role in Auth0, you will want your user's `app_metadata` to look like: - -``` -{ - "roles": [ - "admin" - ] -} -``` - -To set this information and make it available to RedwoodJS, you can use [Auth0 Rules](https://auth0.com/docs/rules). - -#### Auth0 Rules for App Metadata - -RedwoodJS needs `app_metadata` to 1) contain the role information and 2) be present in the JWT that is decoded. - -To accomplish these tasks, you can use [Auth0 Rules](https://auth0.com/docs/rules) to add them as custom claims on your JWT. - -#### Add Authorization Roles to App Metadata Rule - -Your first rule will `Add Authorization Roles to App Metadata`. - -```jsx -/// Add Authorization Roles to App Metadata -function (user, context, callback) { - auth0.users.updateAppMetadata(user.user_id, context.authorization) - .then(function(){ - callback(null, user, context); - }) - .catch(function(err){ - callback(err); - }); - } -``` - -Auth0 exposes the user's roles in `context.authorization`. This rule simply copies that information into the user's `app_metadata`, such as: - -``` -{ - "roles": [ - "admin" - ] -} -``` - -However, now you must include the `app_metadata` on the user's JWT that RedwoodJS will decode. - -#### Add App Metadata to JWT Rule in Auth0 - -Therefore, your second rule will `Add App Metadata to JWT`. - -You can add `app_metadata` to the `idToken` or `accessToken`. - -Adding to `idToken` will make the make app metadata accessible to RedwoodJS `getUserMetadata` which for Auth0 calls the auth client's `getUser`. - -Adding to `accessToken` will make the make app metadata accessible to RedwoodJS when decoding the JWT via `getToken`. - -While adding to `idToken` is optional, you _must_ add to `accessToken`. - -To keep your custom claims from colliding with any reserved claims or claims from other resources, you must give them a [globally unique name using a namespaced format](https://auth0.com/docs/tokens/guides/create-namespaced-custom-claims). Otherwise, Auth0 will _not_ add the information to the token(s). - -Therefore, with a namespace of "https://example.com", the `app_metadata` on your token should look like: - -```jsx -"https://example.com/app_metadata": { - "authorization": { - "roles": [ - "admin" - ] - } -}, -``` - -To set this namespace information, use the following function in your rule: - -```jsx -function (user, context, callback) { - var namespace = 'https://example.com/'; - - // adds to idToken, i.e. userMetadata in RedwoodJS - context.idToken[namespace + 'app_metadata'] = {}; - context.idToken[namespace + 'app_metadata'].authorization = { - groups: user.app_metadata.groups, - roles: user.app_metadata.roles, - permissions: user.app_metadata.permissions - }; - - context.idToken[namespace + 'user_metadata'] = {}; - - // accessToken, i.e. the decoded JWT in RedwoodJS - context.accessToken[namespace + 'app_metadata'] = {}; - context.accessToken[namespace + 'app_metadata'].authorization = { - groups: user.app_metadata.groups, - roles: user.app_metadata.roles, - permissions: user.app_metadata.permissions - }; - - context.accessToken[namespace + 'user_metadata'] = {}; - - return callback(null, user, context); -} -``` - -Now, your `app_metadata` with `authorization` and `role` information will be on the user's JWT after logging in. - -#### Add Application hasRole Support in Auth0 - -If you intend to support, RBAC then in your `api/src/lib/auth.js` you need to extract `roles` using the `parseJWT` utility and set these roles on `currentUser`. - -If your roles are on a namespaced `app_metadata` claim, then `parseJWT` provides an option to provide this value. - -```jsx title="api/src/lib/auth.js" -const NAMESPACE = 'https://example.com' - -const currentUserWithRoles = async (decoded) => { - const currentUser = await userByUserId(decoded.sub) - return { - ...currentUser, - roles: parseJWT({ decoded: decoded, namespace: NAMESPACE }).roles, - } -} - -export const getCurrentUser = async (decoded, { type, token }) => { - try { - requireAccessToken(decoded, { type, token }) - return currentUserWithRoles(decoded) - } catch (error) { - return decoded - } -} -``` - -+++ +### Role Protection -#### Magic.Link - -The Redwood API does not include the functionality to decode Magic.link authentication tokens, so the client is initiated and decodes the tokens inside of `getCurrentUser`. - -+++ View Magic.link Options - -##### Installation - -First, you must manually install the **Magic Admin SDK** in your project's `api/package.json`. - -```bash -yarn workspace api add @magic-sdk/admin -``` - -##### Setup - -To get your application running _without setting up_ `Prisma`, get your `SECRET KEY` from [dashboard.magic.link](https://dashboard.magic.link/). Then add `MAGICLINK_SECRET` to your `.env`. - -```jsx title="redwood/api/src/lib/auth.js|ts" -import { Magic } from '@magic-sdk/admin' - -export const getCurrentUser = async (_decoded, { token }) => { - const mAdmin = new Magic(process.env.MAGICLINK_SECRET) - - return await mAdmin.users.getMetadataByToken(token) -} -``` - -Magic.link recommends using the issuer as the userID to retrieve user metadata via `Prisma` - -```jsx title="redwood/api/src/lib/auth.ts" -import { Magic } from '@magic-sdk/admin' - -export const getCurrentUser = async (_decoded, { token }) => { - const mAdmin = new Magic(process.env.MAGICLINK_SECRET) - const { email, publicAddress, issuer } = await mAdmin.users.getMetadataByToken(token) - - return await db.user.findUnique({ where: { issuer } }) -} -``` - -+++ - -#### Firebase - -You must follow the ["Before you begin"](https://firebase.google.com/docs/auth/web/google-signin) part of the "Authenticate Using Google Sign-In with JavaScript" guide. - -+++ View Firebase Options - -#### Role-based access control (RBAC) in Firebase - -Requires a custom implementation. - -#### App metadata in Firebase - -None. - -#### Add Application hasRole Support in Firebase - -Requires a custom implementation. - -#### Auth Providers - -Providers can be configured by specifying `logIn(provider)` and `signUp(provider)`, where `provider` is a **string** of one of the supported providers. - -Supported providers: - -- google.com (Default) -- facebook.com -- github.com -- twitter.com -- microsoft.com -- apple.com - -#### Email & Password Auth in Firebase - -Email/password authentication is supported by calling `login({ email, password })` and `signUp({ email, password })`. - -#### Email link (passwordless sign-in) in Firebase - -In Firebase Console, you must enable "Email link (passwordless sign-in)" with the configuration toggle for the email provider. The authentication sequence for passwordless email links has two steps: - -1. First, an email with the link must be generated and sent to the user. Either using using firebase client sdk (web side) [sendSignInLinkToEmail()](https://firebase.google.com/docs/reference/js/auth.emailauthprovider#example_2_2), which generates the link and sends the email to the user on behalf of your application or alternatively, generate the link using backend admin sdk (api side), see ["Generate email link for sign-in](https://firebase.google.com/docs/auth/admin/email-action-links#generate_email_link_for_sign-in) but it is then your responsibility to send an email to the user containing the link. -2. Second, authentication is completed when the user is redirected back to the application and the AuthProvider's logIn({emailLink, email, providerId: 'emailLink'}) method is called. - -For example, users could be redirected to a dedicated route/page to complete authentication: - -```jsx -import { useEffect } from 'react' -import { Redirect, routes } from '@redwoodjs/router' -import { useAuth } from '@redwoodjs/auth' - -const EmailSigninPage = () => { - const { loading, hasError, error, logIn } = useAuth() - - const email = window.localStorage.getItem('emailForSignIn') - // TODO: Prompt the user for email if not found in local storage, for example - // if the user opened the email link on a different device. - - const emailLink = window.location.href - - useEffect(() => { - logIn({ - providerId: 'emailLink', - email, - emailLink, - }) - }, []) - - if (loading) { - return
Auth Loading...
- } - - if (hasError) { - console.error(error) - return
Auth Error... check console
- } - - return -} - -export default EmailSigninPage -``` - -#### Custom Token in Firebase - -If you want to [integrate firebase auth with another authentication system](https://firebase.google.com/docs/auth/web/custom-auth), you can use a custom token provider: +The `hasRole()` function can be used to implement basic role-based authorization control (RBAC). This assumes that your `getCurrentUser()` function adds a `roles` property to the returned object. ```jsx -logIn({ - providerId: 'customToken', - customToken, -}) -``` - -Some caveats about using custom tokens: - -- make sure it's actually what you want to use -- remember that the client's firebase authentication state has an independent lifetime than the custom token - -If you want to read more, check out [Demystifying Firebase Auth Tokens](https://medium.com/@jwngr/demystifying-firebase-auth-tokens-e0c533ed330c). - -#### Custom Parameters & Scopes for Google OAuth Provider - -Both `logIn()` and `signUp()` can accept a single argument of either a **string** or **object**. If a string is provided, it should be any of the supported providers (see above), which will configure the defaults for that provider. - -`logIn()` and `signUp()` also accept a single a configuration object. This object accepts `providerId`, `email`, `password`, and `scope` and `customParameters`. (In fact, passing in any arguments ultimately results in this object). You can use this configuration object to pass in values for the optional Google OAuth Provider methods _setCustomParameters_ and _addScope_. - -Below are the parameters that `logIn()` and `signUp()` accept: - -- `providerId`: Accepts one of the supported auth providers as a **string**. If no arguments are passed to `login() / signUp()` this will default to 'google.com'. Provider strings passed as a single argument to `login() / signUp()` will be cast to this value in the object. -- `email`: Accepts a **string** of a users email address. Used in conjunction with `password` and requires that Firebase has email authentication enabled as an option. -- `password`: Accepts a **string** of a users password. Used in conjunction with `email` and requires that Firebase has email authentication enabled as an option. -- `scope`: Accepts an **array** of strings ([Google OAuth Scopes](https://developers.google.com/identity/protocols/oauth2/scopes)), which can be added to the requested Google OAuth Provider. These will be added using the Google OAuth _addScope_ method. -- `customParameters`: accepts an **object** with the [optional parameters](https://firebase.google.com/docs/reference/js/firebase.auth.GoogleAuthProvider#setcustomparameters) for the Google OAuth Provider _setCustomParameters_ method. [Valid parameters](https://developers.google.com/identity/protocols/oauth2/openid-connect#authenticationuriparameters) include 'hd', 'include_granted_scopes', 'login_hint' and 'prompt'. +export const MyComponent = () => { + const { isAuthenticated, hasRole } = useAuth() -#### Firebase Auth Examples - -- `logIn()/signUp()`: Defaults to Google provider. -- `logIn({providerId: 'github.com'})`: Log in using GitHub as auth provider. -- `signUp({email: "someone@email.com", password: 'some_good_password'})`: Creates a firebase user with email/password. -- `logIn({email: "someone@email.com", password: 'some_good_password'})`: Logs in existing firebase user with email/password. -- `logIn({scopes: ['https://www.googleapis.com/auth/calendar']})`: Adds a scope using the [addScope](https://firebase.google.com/docs/reference/js/firebase.auth.GoogleAuthProvider#addscope) method. -- `logIn({ customParameters: { prompt: "consent" } })`: Sets the OAuth custom parameters using [setCustomParameters](https://firebase.google.com/docs/reference/js/firebase.auth.GoogleAuthProvider#addscope) method. - -+++ - -#### Netlify Identity - -[Netlify Identity](https://docs.netlify.com/visitor-access/identity) offers [Role-based access control (RBAC)](https://docs.netlify.com/visitor-access/identity/manage-existing-users/#user-account-metadata). - -+++ View Netlify Identity Options - -#### Role-based access control (RBAC) in Netlify Identity - -Role-based access control (RBAC) refers to the idea of assigning permissions to users based on their role within an organization. It provides fine-grained control and offers a simple, manageable approach to access management that is less prone to error than assigning permissions to users individually. - -Essentially, a role is a collection of permissions that you can apply to users. A role might be "admin", "editor" or "publisher". This differs from permissions an example of which might be "publish:blog". - -#### App metadata in Netlify Identity - -Netlify Identity stores information (such as, support plan subscriptions, security roles, or access control groups) in `app_metadata`. Data stored in `app_metadata` cannot be edited by users. - -Create and manage roles for your application in Netlify's "Identity" management views. You can then assign these roles to users. - -#### Add Application hasRole Support in Netlify Identity - -If you intend to support, RBAC then in your `api/src/lib/auth.js` you need to extract `roles` using the `parseJWT` utility and set these roles on `currentUser`. - -Netlify will store the user's roles on the `app_metadata` claim and the `parseJWT` function provides an option to extract the roles so they can be assigned to the `currentUser`. - -For example: - -```jsx title="api/src/lib/auth.js" -export const getCurrentUser = async (decoded) => { - return context.currentUser || { ...decoded, roles: parseJWT({ decoded }).roles } + return ( + <> + {hasRole('admin') && ( + Admin + )} + + {hasRole(['author', 'editor']) && ( + Admin + )} + + ) } ``` -Now your `currentUser.roles` info will be available to both `requireAuth()` on the api side and `hasRole()` on the web side. - -+++ - -### Role Protection on Web - -You can protect content by role in pages or components via the `useAuth()` hook: - -```jsx -const { isAuthenticated, hasRole } = useAuth() - -... - -{hasRole('admin') && ( - Admin -)} - -{hasRole(['author', 'editor']) && ( - Admin -)} -``` - -### Routes +### Route Protection Routes can require authentication by wrapping them in a `` component. An unauthenticated user will be redirected to the page specified in `unauthenticated`. @@ -1482,6 +138,46 @@ const Routes = () => { } ``` +## Authentication on the API Side + +GraphQL requests automatically receive an `Authorization` header when a user is authenticated and Redwood will decode and verify the header, making the user available (if they are logged in) in `context.currentUser`. + +```jsx +import { context } from '@redwoodjs/api' + +console.log(context.currentUser) +// { +// sub: ' +// email: 'user@example.com', +// [...] +// } +``` + +You can map the "raw decoded JWT" into a real user object by passing a `getCurrentUser` function to `createGraphQLHandler` + +Our recommendation is to create a `src/lib/auth.js|ts` file that exports a `getCurrentUser`. (Note: You may already have stub functions.) + +```jsx +import { getCurrentUser } from 'src/lib/auth' +// Example: +// export const getCurrentUser = async (decoded) => { +// return await db.user.findUnique({ where: { decoded.email } }) +// } +// + +export const handler = createGraphQLHandler({ + schema: makeMergedSchema({ + schemas, + services: makeServices({ services }), + }), + getCurrentUser, +}) +``` + +The value returned by `getCurrentUser()` is available in `context.currentUser` + +Use the `requireAuth` and `skipAuth` [GraphQL directives](directives#secure-by-default-with-built-in-directives) to provide protection to individual GraphQL calls. + ## Contributing If you are interested in contributing to the Redwood Auth Package, please [start here](https://github.com/redwoodjs/redwood/blob/main/packages/auth/README.md). diff --git a/docs/versioned_sidebars/version-2.0-sidebars.json b/docs/versioned_sidebars/version-2.0-sidebars.json index 4c65274ccc85..dfcc519e50f1 100644 --- a/docs/versioned_sidebars/version-2.0-sidebars.json +++ b/docs/versioned_sidebars/version-2.0-sidebars.json @@ -59,9 +59,7 @@ ] }, { - "Chapter 7": [ - "tutorial/chapter7/rbac" - ] + "Chapter 7": ["tutorial/chapter7/rbac"] }, "tutorial/afterword" ] @@ -78,7 +76,28 @@ "a11y", "app-configuration-redwood-toml", "assets-and-files", - "authentication", + { + "type": "category", + "label": "Authentication", + "link": { + "type": "doc", + "id": "authentication" + }, + "items": [ + { "type": "doc", "id": "auth/dbauth" }, + { "type": "doc", "id": "auth/auth0" }, + { "type": "doc", "id": "auth/azure" }, + { "type": "doc", "id": "auth/clerk" }, + { "type": "doc", "id": "auth/custom" }, + { "type": "doc", "id": "auth/firebase" }, + { "type": "doc", "id": "auth/gotrue" }, + { "type": "doc", "id": "auth/magic-link" }, + { "type": "doc", "id": "auth/netlify" }, + { "type": "doc", "id": "auth/nhost" }, + { "type": "doc", "id": "auth/supabase" }, + { "type": "doc", "id": "auth/wallet-connect" } + ] + }, "builds", "cells", "cli-commands", From f127efcf802b77ef99e2364e2264745b8f26e16c Mon Sep 17 00:00:00 2001 From: Rushabh Javeri Date: Tue, 21 Jun 2022 16:36:50 +0530 Subject: [PATCH 02/17] docs: Replacing Prisma.xxx types with types from 'types/graphql' (#5740) * docs: Replacing Prisma.xxx types with types from 'types/graphql' * docs: Using types from 'types/graphql' in chapter3/saving-data --- docs/docs/tutorial/chapter2/side-quest.md | 21 ++++++--------------- docs/docs/tutorial/chapter3/saving-data.md | 21 ++++++--------------- 2 files changed, 12 insertions(+), 30 deletions(-) diff --git a/docs/docs/tutorial/chapter2/side-quest.md b/docs/docs/tutorial/chapter2/side-quest.md index ed1c279f47b6..e6e403429a2a 100644 --- a/docs/docs/tutorial/chapter2/side-quest.md +++ b/docs/docs/tutorial/chapter2/side-quest.md @@ -137,42 +137,33 @@ export const deletePost = ({ id }) => { ```javascript title="api/src/services/posts/posts.ts" -import type { Prisma } from '@prisma/client' - import { db } from 'src/lib/db' +import type { QueryResolvers, MutationResolvers } from 'types/graphql' -export const posts = () => { +export const posts: QueryResolvers['posts'] = () => { return db.post.findMany() } -export const post = ({ id }: Prisma.PostWhereUniqueInput) => { +export const post: QueryResolvers['post'] = ({ id }) => { return db.post.findUnique({ where: { id }, }) } -interface CreatePostArgs { - input: Prisma.PostCreateInput -} - -export const createPost = ({ input }: CreatePostArgs) => { +export const createPost: MutationResolvers['createPost'] = ({ input }) => { return db.post.create({ data: input, }) } -interface UpdatePostArgs extends Prisma.PostWhereUniqueInput { - input: Prisma.PostUpdateInput -} - -export const updatePost = ({ id, input }: UpdatePostArgs) => { +export const updatePost: MutationResolvers['updatePost'] = ({ id, input }) => { return db.post.update({ data: input, where: { id }, }) } -export const deletePost = ({ id }: Prisma.PostWhereUniqueInput) => { +export const deletePost: MutationResolvers['deletePost'] = ({ id }) => { return db.post.delete({ where: { id }, }) diff --git a/docs/docs/tutorial/chapter3/saving-data.md b/docs/docs/tutorial/chapter3/saving-data.md index 9a73500c06c0..cacbd020f82a 100644 --- a/docs/docs/tutorial/chapter3/saving-data.md +++ b/docs/docs/tutorial/chapter3/saving-data.md @@ -298,42 +298,33 @@ export const deleteContact = ({ id }) => { ```js title="api/src/services/contacts/contacts.ts" -import type { Prisma } from '@prisma/client' - import { db } from 'src/lib/db' +import type { QueryResolvers, MutationResolvers } from 'types/graphql' -export const contacts = () => { +export const contacts: QueryResolvers['contacts'] = () => { return db.contact.findMany() } -export const contact = ({ id }: Prisma.ContactWhereUniqueInput) => { +export const contact: QueryResolvers['contact'] = ({ id }) => { return db.contact.findUnique({ where: { id }, }) } -interface CreateContactArgs { - input: Prisma.ContactCreateInput -} - -export const createContact = ({ input }: CreateContactArgs) => { +export const createContact: MutationResolvers['createContact'] = ({ input }) => { return db.contact.create({ data: input, }) } -interface UpdateContactArgs extends Prisma.ContactWhereUniqueInput { - input: Prisma.ContactUpdateInput -} - -export const updateContact = ({ id, input }: UpdateContactArgs) => { +export const updateContact: MutationResolvers['updateContact'] = ({ id, input }) => { return db.contact.update({ data: input, where: { id }, }) } -export const deleteContact = ({ id }: Prisma.ContactWhereUniqueInput) => { +export const deleteContact: MutationResolvers['deleteContact'] = ({ id }) => { return db.contact.delete({ where: { id }, }) From a0615130fb9997b763b52e83272871930831299f Mon Sep 17 00:00:00 2001 From: Bouzid Badreddine Date: Tue, 21 Jun 2022 12:12:53 +0100 Subject: [PATCH 03/17] fix typo (#5777) --- docs/docs/tutorial/chapter7/rbac.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/tutorial/chapter7/rbac.md b/docs/docs/tutorial/chapter7/rbac.md index ca81e5e92bc8..3d90bd0634c2 100644 --- a/docs/docs/tutorial/chapter7/rbac.md +++ b/docs/docs/tutorial/chapter7/rbac.md @@ -1184,7 +1184,7 @@ describe('comments', () => { // highlight-start scenario( 'allows a moderator to delete a comment', - async (scenario, StandardScenario) => { + async (scenario: StandardScenario) => { mockCurrentUser({ roles: 'moderator', id: 1, From 86fc1a66210facf2b5d91b827bf48993de46a6a2 Mon Sep 17 00:00:00 2001 From: Tobias Date: Tue, 21 Jun 2022 13:20:50 +0200 Subject: [PATCH 04/17] Fix typo in testing docs (#5782) --- docs/docs/testing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/testing.md b/docs/docs/testing.md index 9632734a7c6e..fef11aa1fe37 100644 --- a/docs/docs/testing.md +++ b/docs/docs/testing.md @@ -1639,7 +1639,7 @@ Only the scenarios named for your test are included at the time the test is run. Only the posts scenarios will be present in the database when running the `posts.test.js` and only comments scenarios will be present when running `comments.test.js`. And within those scenarios, only the `standard` scenario will be loaded for each test unless you specify a differently named scenario to use instead. -During the run of any single test, there is only every one scenario's worth of data present in the database: users.standard *or* users.incomplete. +During the run of any single test, there is only ever one scenario's worth of data present in the database: users.standard *or* users.incomplete. ### mockCurrentUser() on the API-side From e460fd303721a34175c7119a824f9c06fd7f6a89 Mon Sep 17 00:00:00 2001 From: Erica Pisani Date: Tue, 21 Jun 2022 08:25:16 -0400 Subject: [PATCH 05/17] docs: update disable api layer/database to include disabling prisma (#5528) not disabling prisma in the 'deploy' command leads to build errors when trying to deploying to Netlify --- docs/docs/how-to/disable-api-database.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/docs/how-to/disable-api-database.md b/docs/docs/how-to/disable-api-database.md index fa2a9c6d7c8f..39e380dbb914 100644 --- a/docs/docs/how-to/disable-api-database.md +++ b/docs/docs/how-to/disable-api-database.md @@ -18,6 +18,16 @@ rm -rf api You can also run `yarn install` to cleanup those packages that aren't used any more. +## Disable Prisma functionality +The `--prisma` and `--dm` flags are set to `true` by default and need to be set to `false` in the build command. + +```toml {4} +[build] + command = "yarn rw deploy netlify --prisma=false --dm=false" +``` + +While omitting these flags won't prevent you from developing the site in a local environment, not setting them to `false` will lead to a `'No Prisma Schema found'` error when you attempt to deploy your site to a production environment, at least when Netlify is the deployment target. + ## Turn off the API build process When it comes time to deploy, we need to let Netlify know that it shouldn't bother trying to look for any code to turn into AWS Lambda functions. @@ -26,7 +36,7 @@ Open up `netlify.toml`. We're going to comment out one line: ```toml {4} [build] - command = "yarn rw build" + command = "yarn rw deploy netlify --prisma=false --dm=false" publish = "web/dist" # functions = "api/dist/functions" From e40829621ad069f599fba1133dc8bd46ce860c65 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 21 Jun 2022 13:04:25 +0000 Subject: [PATCH 06/17] fix(deps): update dependency webpack to v5.73.0 (#5755) Co-authored-by: Renovate Bot --- packages/core/package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/core/package.json b/packages/core/package.json index 9335a892310d..2eefd903ed11 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -80,7 +80,7 @@ "svg-react-loader": "0.4.6", "typescript": "4.7.3", "url-loader": "4.1.1", - "webpack": "5.72.1", + "webpack": "5.73.0", "webpack-bundle-analyzer": "4.5.0", "webpack-cli": "4.10.0", "webpack-dev-server": "4.9.2", diff --git a/yarn.lock b/yarn.lock index 3cb33d90125a..f2dfad388e05 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6127,7 +6127,7 @@ __metadata: svg-react-loader: 0.4.6 typescript: 4.7.3 url-loader: 4.1.1 - webpack: 5.72.1 + webpack: 5.73.0 webpack-bundle-analyzer: 4.5.0 webpack-cli: 4.10.0 webpack-dev-server: 4.9.2 @@ -31209,9 +31209,9 @@ __metadata: languageName: node linkType: hard -"webpack@npm:5.72.1, webpack@npm:>=4.43.0 <6.0.0, webpack@npm:^5, webpack@npm:^5.9.0": - version: 5.72.1 - resolution: "webpack@npm:5.72.1" +"webpack@npm:5.73.0, webpack@npm:>=4.43.0 <6.0.0, webpack@npm:^5, webpack@npm:^5.9.0": + version: 5.73.0 + resolution: "webpack@npm:5.73.0" dependencies: "@types/eslint-scope": ^3.7.3 "@types/estree": ^0.0.51 @@ -31242,7 +31242,7 @@ __metadata: optional: true bin: webpack: bin/webpack.js - checksum: bdba33268930448eb84347c2da383de8d71f46b2a199c5abf56f6619c86a081de802a4c297c8389c9a888eec0613d5069427c12af0a878058e4be3790875d536 + checksum: 761add2395e37c7bd0b0727bea327496b8a2f5a702b29bd4907f9e80f6e7ea17ecdeefabf0683fdefe1148f725cd51eb81c4a73b4646eda9eb1a8d1a74ac8cae languageName: node linkType: hard From 8fec7e8fb274ad5ab76a16dca254a47f2883af45 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 22 Jun 2022 00:22:23 +0900 Subject: [PATCH 07/17] fix(deps): update dependency core-js to v3.23.2 (#5790) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- packages/api-server/package.json | 2 +- packages/api/package.json | 2 +- packages/auth/package.json | 2 +- packages/cli/package.json | 2 +- packages/codemods/package.json | 2 +- packages/core/package.json | 2 +- packages/create-redwood-app/package.json | 2 +- packages/forms/package.json | 2 +- packages/graphql-server/package.json | 2 +- packages/internal/package.json | 2 +- packages/prerender/package.json | 2 +- packages/record/package.json | 2 +- packages/router/package.json | 2 +- packages/structure/package.json | 2 +- packages/telemetry/package.json | 2 +- packages/testing/package.json | 2 +- packages/web/package.json | 2 +- yarn.lock | 44 ++++++++++++------------ 19 files changed, 40 insertions(+), 40 deletions(-) diff --git a/package.json b/package.json index ec783e8ccbde..dfa4e379ec39 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "babel-plugin-auto-import": "1.1.0", "babel-plugin-remove-code": "0.0.6", "boxen": "5.1.2", - "core-js": "3.23.1", + "core-js": "3.23.2", "cypress": "9.7.0", "cypress-wait-until": "1.7.2", "eslint": "8.16.0", diff --git a/packages/api-server/package.json b/packages/api-server/package.json index 4441e6131deb..7f76aa3b49d9 100644 --- a/packages/api-server/package.json +++ b/packages/api-server/package.json @@ -35,7 +35,7 @@ "ansi-colors": "4.1.3", "chalk": "4.1.2", "chokidar": "3.5.3", - "core-js": "3.23.1", + "core-js": "3.23.2", "fast-json-parse": "1.0.3", "fastify": "3.29.0", "fastify-raw-body": "3.2.0", diff --git a/packages/api/package.json b/packages/api/package.json index e1d34f55f9f4..3cb9fe33522f 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -32,7 +32,7 @@ "dependencies": { "@babel/runtime-corejs3": "7.16.7", "@prisma/client": "3.15.1", - "core-js": "3.23.1", + "core-js": "3.23.2", "cross-undici-fetch": "0.1.27", "crypto-js": "4.1.1", "humanize-string": "2.1.0", diff --git a/packages/auth/package.json b/packages/auth/package.json index f052c15a5812..bed5b265518d 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -23,7 +23,7 @@ }, "dependencies": { "@babel/runtime-corejs3": "7.16.7", - "core-js": "3.23.1" + "core-js": "3.23.2" }, "devDependencies": { "@auth0/auth0-spa-js": "1.22.0", diff --git a/packages/cli/package.json b/packages/cli/package.json index 50db5a1a7c19..19990f3480d3 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -40,7 +40,7 @@ "chalk": "4.1.2", "concurrently": "7.2.1", "configstore": "3.1.5", - "core-js": "3.23.1", + "core-js": "3.23.2", "cross-env": "7.0.3", "decamelize": "5.0.0", "dotenv-defaults": "5.0.0", diff --git a/packages/codemods/package.json b/packages/codemods/package.json index c13d43dc8300..88459005ae4a 100644 --- a/packages/codemods/package.json +++ b/packages/codemods/package.json @@ -27,7 +27,7 @@ "@babel/plugin-transform-typescript": "7.16.7", "@babel/runtime-corejs3": "7.16.7", "@vscode/ripgrep": "1.14.2", - "core-js": "3.23.1", + "core-js": "3.23.2", "cross-undici-fetch": "0.1.27", "deepmerge": "4.2.2", "execa": "5.1.1", diff --git a/packages/core/package.json b/packages/core/package.json index 2eefd903ed11..b617e63def40 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -59,7 +59,7 @@ "babel-plugin-module-resolver": "4.1.0", "babel-timing": "0.9.1", "copy-webpack-plugin": "11.0.0", - "core-js": "3.23.1", + "core-js": "3.23.2", "css-loader": "6.7.1", "css-minimizer-webpack-plugin": "4.0.0", "dotenv-webpack": "7.1.0", diff --git a/packages/create-redwood-app/package.json b/packages/create-redwood-app/package.json index fe405e427270..baca19b186ca 100644 --- a/packages/create-redwood-app/package.json +++ b/packages/create-redwood-app/package.json @@ -28,7 +28,7 @@ "@redwoodjs/telemetry": "2.0.0", "chalk": "4.1.2", "check-node-version": "4.2.1", - "core-js": "3.23.1", + "core-js": "3.23.2", "execa": "5.1.1", "fs-extra": "10.1.0", "listr": "0.14.3", diff --git a/packages/forms/package.json b/packages/forms/package.json index 7978a66aa980..6fce911bc281 100644 --- a/packages/forms/package.json +++ b/packages/forms/package.json @@ -22,7 +22,7 @@ }, "dependencies": { "@babel/runtime-corejs3": "7.16.7", - "core-js": "3.23.1", + "core-js": "3.23.2", "pascalcase": "1.0.0", "react-hook-form": "7.31.3" }, diff --git a/packages/graphql-server/package.json b/packages/graphql-server/package.json index f04b5b7cadc1..53c2f32be744 100644 --- a/packages/graphql-server/package.json +++ b/packages/graphql-server/package.json @@ -34,7 +34,7 @@ "@graphql-yoga/common": "2.7.0", "@prisma/client": "3.15.1", "@redwoodjs/api": "2.0.0", - "core-js": "3.23.1", + "core-js": "3.23.2", "cross-undici-fetch": "0.1.27", "graphql": "16.5.0", "graphql-scalars": "1.17.0", diff --git a/packages/internal/package.json b/packages/internal/package.json index cf9c028831b7..ff999dfd94cb 100644 --- a/packages/internal/package.json +++ b/packages/internal/package.json @@ -44,7 +44,7 @@ "babel-plugin-graphql-tag": "3.3.0", "babel-plugin-polyfill-corejs3": "0.5.0", "chalk": "4.1.2", - "core-js": "3.23.1", + "core-js": "3.23.2", "deepmerge": "4.2.2", "esbuild": "0.14.43", "fast-glob": "3.2.11", diff --git a/packages/prerender/package.json b/packages/prerender/package.json index 53e6d5a68870..80fd1d6f277d 100644 --- a/packages/prerender/package.json +++ b/packages/prerender/package.json @@ -32,7 +32,7 @@ "@redwoodjs/web": "2.0.0", "babel-plugin-ignore-html-and-css-imports": "0.1.0", "cheerio": "1.0.0-rc.11", - "core-js": "3.23.1", + "core-js": "3.23.2", "cross-undici-fetch": "0.1.27", "mime-types": "2.1.35" }, diff --git a/packages/record/package.json b/packages/record/package.json index 6221ef8eb3a0..be290029192d 100644 --- a/packages/record/package.json +++ b/packages/record/package.json @@ -29,7 +29,7 @@ "dependencies": { "@babel/runtime-corejs3": "7.16.7", "@prisma/client": "3.15.1", - "core-js": "3.23.1" + "core-js": "3.23.2" }, "devDependencies": { "@babel/cli": "7.16.7", diff --git a/packages/router/package.json b/packages/router/package.json index 01bd1ea4ba64..830ce4e07160 100644 --- a/packages/router/package.json +++ b/packages/router/package.json @@ -25,7 +25,7 @@ "@babel/runtime-corejs3": "7.16.7", "@reach/skip-nav": "0.16.0", "@redwoodjs/auth": "2.0.0", - "core-js": "3.23.1", + "core-js": "3.23.2", "lodash.isequal": "4.5.0" }, "devDependencies": { diff --git a/packages/structure/package.json b/packages/structure/package.json index aaa080614deb..671649038867 100644 --- a/packages/structure/package.json +++ b/packages/structure/package.json @@ -35,7 +35,7 @@ "@redwoodjs/internal": "2.0.0", "@types/line-column": "1.0.0", "camelcase": "6.3.0", - "core-js": "3.23.1", + "core-js": "3.23.2", "deepmerge": "4.2.2", "dotenv-defaults": "5.0.0", "enquirer": "2.3.6", diff --git a/packages/telemetry/package.json b/packages/telemetry/package.json index e73eb2be1346..2eb3965edba6 100644 --- a/packages/telemetry/package.json +++ b/packages/telemetry/package.json @@ -30,7 +30,7 @@ "@redwoodjs/internal": "2.0.0", "@redwoodjs/structure": "2.0.0", "ci-info": "3.3.1", - "core-js": "3.23.1", + "core-js": "3.23.2", "cross-undici-fetch": "0.1.27", "envinfo": "7.8.1", "systeminformation": "5.11.16", diff --git a/packages/testing/package.json b/packages/testing/package.json index 845e0734ae31..782ce9f7aa3f 100644 --- a/packages/testing/package.json +++ b/packages/testing/package.json @@ -51,7 +51,7 @@ "@types/webpack": "5.28.0", "babel-jest": "27.5.1", "babel-plugin-inline-react-svg": "2.0.1", - "core-js": "3.23.1", + "core-js": "3.23.2", "jest": "27.5.1", "jest-watch-typeahead": "1.1.0", "msw": "0.40.2", diff --git a/packages/web/package.json b/packages/web/package.json index 414247631495..06cf65fb10db 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -39,7 +39,7 @@ "@apollo/client": "3.6.8", "@babel/runtime-corejs3": "7.16.7", "@redwoodjs/auth": "2.0.0", - "core-js": "3.23.1", + "core-js": "3.23.2", "graphql": "16.5.0", "graphql-tag": "2.12.6", "react-helmet-async": "1.3.0", diff --git a/yarn.lock b/yarn.lock index f2dfad388e05..521665df5c41 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5888,7 +5888,7 @@ __metadata: aws-lambda: 1.0.7 chalk: 4.1.2 chokidar: 3.5.3 - core-js: 3.23.1 + core-js: 3.23.2 fast-json-parse: 1.0.3 fastify: 3.29.0 fastify-raw-body: 3.2.0 @@ -5925,7 +5925,7 @@ __metadata: "@types/split2": 3.2.1 "@types/uuid": 8.3.4 aws-lambda: 1.0.7 - core-js: 3.23.1 + core-js: 3.23.2 cross-undici-fetch: 0.1.27 crypto-js: 4.1.1 humanize-string: 2.1.0 @@ -5975,7 +5975,7 @@ __metadata: "@supabase/supabase-js": 1.35.3 "@types/netlify-identity-widget": 1.9.3 "@types/react": 17.0.45 - core-js: 3.23.1 + core-js: 3.23.2 firebase: 9.8.2 firebase-admin: 10.2.0 gotrue-js: 0.9.29 @@ -6007,7 +6007,7 @@ __metadata: chalk: 4.1.2 concurrently: 7.2.1 configstore: 3.1.5 - core-js: 3.23.1 + core-js: 3.23.2 cross-env: 7.0.3 decamelize: 5.0.0 dotenv-defaults: 5.0.0 @@ -6056,7 +6056,7 @@ __metadata: "@types/prettier": 2.6.3 "@types/yargs": 17.0.10 "@vscode/ripgrep": 1.14.2 - core-js: 3.23.1 + core-js: 3.23.2 cross-undici-fetch: 0.1.27 deepmerge: 4.2.2 execa: 5.1.1 @@ -6105,7 +6105,7 @@ __metadata: babel-plugin-module-resolver: 4.1.0 babel-timing: 0.9.1 copy-webpack-plugin: 11.0.0 - core-js: 3.23.1 + core-js: 3.23.2 css-loader: 6.7.1 css-minimizer-webpack-plugin: 4.0.0 dotenv-webpack: 7.1.0 @@ -6192,7 +6192,7 @@ __metadata: "@types/react": 17.0.45 "@types/react-dom": 17.0.17 "@types/testing-library__jest-dom": 5.14.3 - core-js: 3.23.1 + core-js: 3.23.2 graphql: 16.5.0 jest: 27.5.1 nodemon: 2.0.16 @@ -6232,7 +6232,7 @@ __metadata: "@types/lodash.omitby": 4.6.7 "@types/uuid": 8.3.4 aws-lambda: 1.0.7 - core-js: 3.23.1 + core-js: 3.23.2 cross-undici-fetch: 0.1.27 graphql: 16.5.0 graphql-scalars: 1.17.0 @@ -6273,7 +6273,7 @@ __metadata: babel-plugin-polyfill-corejs3: 0.5.0 babel-plugin-tester: 10.1.0 chalk: 4.1.2 - core-js: 3.23.1 + core-js: 3.23.2 deepmerge: 4.2.2 esbuild: 0.14.43 fast-glob: 3.2.11 @@ -6312,7 +6312,7 @@ __metadata: babel-plugin-ignore-html-and-css-imports: 0.1.0 babel-plugin-tester: 10.1.0 cheerio: 1.0.0-rc.11 - core-js: 3.23.1 + core-js: 3.23.2 cross-undici-fetch: 0.1.27 jest: 27.5.1 mime-types: 2.1.35 @@ -6332,7 +6332,7 @@ __metadata: "@babel/runtime-corejs3": 7.16.7 "@prisma/client": 3.15.1 "@prisma/sdk": 3.15.1 - core-js: 3.23.1 + core-js: 3.23.2 esbuild: 0.14.43 jest: 27.5.1 languageName: unknown @@ -6350,7 +6350,7 @@ __metadata: "@types/lodash.isequal": 4.5.6 "@types/react": 17.0.45 "@types/react-dom": 17.0.17 - core-js: 3.23.1 + core-js: 3.23.2 jest: 27.5.1 lodash.isequal: 4.5.0 prop-types: 15.8.1 @@ -6376,7 +6376,7 @@ __metadata: "@types/node": 16.11.38 "@types/vscode": 1.67.0 camelcase: 6.3.0 - core-js: 3.23.1 + core-js: 3.23.2 deepmerge: 4.2.2 dotenv-defaults: 5.0.0 enquirer: 2.3.6 @@ -6412,7 +6412,7 @@ __metadata: "@types/uuid": 8.3.4 "@types/yargs": 17.0.10 ci-info: 3.3.1 - core-js: 3.23.1 + core-js: 3.23.2 cross-undici-fetch: 0.1.27 envinfo: 7.8.1 jest: 27.5.1 @@ -6453,7 +6453,7 @@ __metadata: "@types/webpack": 5.28.0 babel-jest: 27.5.1 babel-plugin-inline-react-svg: 2.0.1 - core-js: 3.23.1 + core-js: 3.23.2 jest: 27.5.1 jest-watch-typeahead: 1.1.0 msw: 0.40.2 @@ -6478,7 +6478,7 @@ __metadata: "@types/react": 17.0.45 "@types/react-dom": 17.0.17 "@types/testing-library__jest-dom": 5.14.3 - core-js: 3.23.1 + core-js: 3.23.2 graphql: 16.5.0 graphql-tag: 2.12.6 jest: 27.5.1 @@ -13064,10 +13064,10 @@ __metadata: languageName: node linkType: hard -"core-js@npm:3.23.1, core-js@npm:^3.0.4, core-js@npm:^3.18.3, core-js@npm:^3.19.0, core-js@npm:^3.22.4, core-js@npm:^3.6.5, core-js@npm:^3.8.2": - version: 3.23.1 - resolution: "core-js@npm:3.23.1" - checksum: dd295fd0407575e2a70ef3755526aa67397b927618faaf03e0c112e196967199e66e7f6e4007e0b2cb05d52c41d73658877a49c3a268ae01d1339fb9bb622efb +"core-js@npm:3.23.2, core-js@npm:^3.0.4, core-js@npm:^3.18.3, core-js@npm:^3.19.0, core-js@npm:^3.22.4, core-js@npm:^3.6.5, core-js@npm:^3.8.2": + version: 3.23.2 + resolution: "core-js@npm:3.23.2" + checksum: f6d1039dd5b7d8d7a886bafad6c34233c8fa62f0df61d110ccde5f37dde3e29ff57354c3ff87c1546c417fac0038265637166dce5588a05c48ac91ec6590e4bb languageName: node linkType: hard @@ -13234,7 +13234,7 @@ __metadata: "@redwoodjs/telemetry": 2.0.0 chalk: 4.1.2 check-node-version: 4.2.1 - core-js: 3.23.1 + core-js: 3.23.2 execa: 5.1.1 fs-extra: 10.1.0 jest: 27.5.1 @@ -27034,7 +27034,7 @@ __metadata: babel-plugin-auto-import: 1.1.0 babel-plugin-remove-code: 0.0.6 boxen: 5.1.2 - core-js: 3.23.1 + core-js: 3.23.2 cypress: 9.7.0 cypress-wait-until: 1.7.2 eslint: 8.16.0 From 23d94f818ff9385b2a04cce4d88896a39b39c6a0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 21 Jun 2022 19:21:47 +0000 Subject: [PATCH 08/17] fix(deps): update prisma monorepo to v3.15.2 (#5789) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- packages/api/package.json | 2 +- packages/cli/package.json | 4 +- packages/graphql-server/package.json | 2 +- packages/record/package.json | 4 +- packages/structure/package.json | 2 +- yarn.lock | 74 ++++++++++++++-------------- 6 files changed, 44 insertions(+), 44 deletions(-) diff --git a/packages/api/package.json b/packages/api/package.json index 3cb9fe33522f..44079cbb4713 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -31,7 +31,7 @@ }, "dependencies": { "@babel/runtime-corejs3": "7.16.7", - "@prisma/client": "3.15.1", + "@prisma/client": "3.15.2", "core-js": "3.23.2", "cross-undici-fetch": "0.1.27", "crypto-js": "4.1.1", diff --git a/packages/cli/package.json b/packages/cli/package.json index 19990f3480d3..b93433750bd1 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -29,7 +29,7 @@ }, "dependencies": { "@babel/runtime-corejs3": "7.16.7", - "@prisma/sdk": "3.15.1", + "@prisma/sdk": "3.15.2", "@redwoodjs/api-server": "2.0.0", "@redwoodjs/internal": "2.0.0", "@redwoodjs/prerender": "2.0.0", @@ -57,7 +57,7 @@ "pascalcase": "1.0.0", "pluralize": "8.0.0", "prettier": "2.6.2", - "prisma": "3.15.1", + "prisma": "3.15.2", "prompts": "2.4.2", "rimraf": "3.0.2", "secure-random-password": "0.2.3", diff --git a/packages/graphql-server/package.json b/packages/graphql-server/package.json index 53c2f32be744..fdbff388300e 100644 --- a/packages/graphql-server/package.json +++ b/packages/graphql-server/package.json @@ -32,7 +32,7 @@ "@graphql-tools/schema": "8.3.13", "@graphql-tools/utils": "8.6.12", "@graphql-yoga/common": "2.7.0", - "@prisma/client": "3.15.1", + "@prisma/client": "3.15.2", "@redwoodjs/api": "2.0.0", "core-js": "3.23.2", "cross-undici-fetch": "0.1.27", diff --git a/packages/record/package.json b/packages/record/package.json index be290029192d..2f05d1316fcb 100644 --- a/packages/record/package.json +++ b/packages/record/package.json @@ -28,13 +28,13 @@ }, "dependencies": { "@babel/runtime-corejs3": "7.16.7", - "@prisma/client": "3.15.1", + "@prisma/client": "3.15.2", "core-js": "3.23.2" }, "devDependencies": { "@babel/cli": "7.16.7", "@babel/core": "7.16.7", - "@prisma/sdk": "3.15.1", + "@prisma/sdk": "3.15.2", "esbuild": "0.14.43", "jest": "27.5.1" }, diff --git a/packages/structure/package.json b/packages/structure/package.json index 671649038867..dc65e33a10bd 100644 --- a/packages/structure/package.json +++ b/packages/structure/package.json @@ -31,7 +31,7 @@ }, "dependencies": { "@babel/runtime-corejs3": "7.16.7", - "@prisma/sdk": "3.15.1", + "@prisma/sdk": "3.15.2", "@redwoodjs/internal": "2.0.0", "@types/line-column": "1.0.0", "camelcase": "6.3.0", diff --git a/yarn.lock b/yarn.lock index 521665df5c41..f51f9431773e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5599,9 +5599,9 @@ __metadata: languageName: node linkType: hard -"@prisma/client@npm:3.15.1": - version: 3.15.1 - resolution: "@prisma/client@npm:3.15.1" +"@prisma/client@npm:3.15.2": + version: 3.15.2 + resolution: "@prisma/client@npm:3.15.2" dependencies: "@prisma/engines-version": 3.15.1-1.461d6a05159055555eb7dfb337c9fb271cbd4d7e peerDependencies: @@ -5609,7 +5609,7 @@ __metadata: peerDependenciesMeta: prisma: optional: true - checksum: cf3f974a81dbb0fa79425667bbaa77b080cc4ced71be518568ed0d377573a747f353d7e0cc3d6e427243d2d61966546914f7a344d7f03bd8ed937142f505e900 + checksum: ef9067c167aa0c4d57587724272f12074152e67b3c037287acc381955534c26e0f65b0b9eea900fd24492edb0f86dba7bcbdb74100eccbf1a82e3d3fc2c33f79 languageName: node linkType: hard @@ -5624,24 +5624,24 @@ __metadata: languageName: node linkType: hard -"@prisma/debug@npm:3.15.1": - version: 3.15.1 - resolution: "@prisma/debug@npm:3.15.1" +"@prisma/debug@npm:3.15.2": + version: 3.15.2 + resolution: "@prisma/debug@npm:3.15.2" dependencies: "@types/debug": 4.1.7 debug: 4.3.4 strip-ansi: 6.0.1 - checksum: b8562f40bd08d4fe802f175807e0489584e032a2bf10a32366dd8f8d1aa87734b24cd35c803cb1b3643bc0144a9b6182d3dfce3b3d4dc1fca3f3f2e421c24efb + checksum: 34417ce289622b29c552cbeb4c2b8a11df1bbb37ed3e2c7a061dd5a469c0eaf1cd869cedfc4de9557edce2b9e4d02dc3656d46267c57839a393fdfe1400aeed4 languageName: node linkType: hard -"@prisma/engine-core@npm:3.15.1": - version: 3.15.1 - resolution: "@prisma/engine-core@npm:3.15.1" +"@prisma/engine-core@npm:3.15.2": + version: 3.15.2 + resolution: "@prisma/engine-core@npm:3.15.2" dependencies: - "@prisma/debug": 3.15.1 + "@prisma/debug": 3.15.2 "@prisma/engines": 3.15.1-1.461d6a05159055555eb7dfb337c9fb271cbd4d7e - "@prisma/generator-helper": 3.15.1 + "@prisma/generator-helper": 3.15.2 "@prisma/get-platform": 3.15.1-1.461d6a05159055555eb7dfb337c9fb271cbd4d7e chalk: 4.1.2 execa: 5.1.1 @@ -5651,7 +5651,7 @@ __metadata: p-retry: 4.6.2 strip-ansi: 6.0.1 undici: 5.1.1 - checksum: c96284df78c75d027f1a714e85dee630beefc22a4d5a11f00dc31f4d64e1927f1ade1e5e6ebcd182bf6e775738954a0461ace8be19f6e4b56207363c733fa83f + checksum: 644a85f3b3eb724adc64344f959c76cc577b2fb5696418314622d4bb8b5e368491ec1a8c8493eb2a4b85cc866f35d720aea803b4d34a3985b18f0384508bd7dc languageName: node linkType: hard @@ -5694,15 +5694,15 @@ __metadata: languageName: node linkType: hard -"@prisma/generator-helper@npm:3.15.1": - version: 3.15.1 - resolution: "@prisma/generator-helper@npm:3.15.1" +"@prisma/generator-helper@npm:3.15.2": + version: 3.15.2 + resolution: "@prisma/generator-helper@npm:3.15.2" dependencies: - "@prisma/debug": 3.15.1 + "@prisma/debug": 3.15.2 "@types/cross-spawn": 6.0.2 chalk: 4.1.2 cross-spawn: 7.0.3 - checksum: b1a4a53d5722ed20e67da675df7766afd740acf3a8a3246e4d20121b8ab5f69b7aaaa6232390c46652bb9d5fd06a2fd4af1ae8a3dfc15fb9680beb946f0884ff + checksum: 362b903eb1b32f610e0eec0fb6c2c9368072a5aad9bf2c8071c15b2469aa9168b52a71ed1a2c7b95647137a3b5ef2ffa1aaa1d8c9cdfffe1ca50506737ef14a8 languageName: node linkType: hard @@ -5715,15 +5715,15 @@ __metadata: languageName: node linkType: hard -"@prisma/sdk@npm:3.15.1": - version: 3.15.1 - resolution: "@prisma/sdk@npm:3.15.1" +"@prisma/sdk@npm:3.15.2": + version: 3.15.2 + resolution: "@prisma/sdk@npm:3.15.2" dependencies: - "@prisma/debug": 3.15.1 - "@prisma/engine-core": 3.15.1 + "@prisma/debug": 3.15.2 + "@prisma/engine-core": 3.15.2 "@prisma/engines": 3.15.1-1.461d6a05159055555eb7dfb337c9fb271cbd4d7e "@prisma/fetch-engine": 3.15.1-1.461d6a05159055555eb7dfb337c9fb271cbd4d7e - "@prisma/generator-helper": 3.15.1 + "@prisma/generator-helper": 3.15.2 "@prisma/get-platform": 3.15.1-1.461d6a05159055555eb7dfb337c9fb271cbd4d7e "@timsuchanek/copy": 1.4.5 archiver: 5.3.1 @@ -5765,7 +5765,7 @@ __metadata: terminal-link: 2.1.1 tmp: 0.2.1 ts-pattern: ^4.0.1 - checksum: 8848d7bec569cf906578834441db8f533f1a0be184c1e6ad6898817e1170701a02ba67bab97c4cd681bb451f381785b1e946fddd0eda0367a101dd339b0fac1c + checksum: c6a15bba8daca145fa44215088549606909fa626d51cca4e796645829a0d6692d65b2a0cb00ee04bab1e61e95997751382ee1942b6abb3b254a7eb09c4821cf5 languageName: node linkType: hard @@ -5915,7 +5915,7 @@ __metadata: "@babel/core": 7.16.7 "@babel/runtime-corejs3": 7.16.7 "@clerk/clerk-sdk-node": 3.6.1 - "@prisma/client": 3.15.1 + "@prisma/client": 3.15.2 "@redwoodjs/auth": 2.0.0 "@types/aws-lambda": 8.10.97 "@types/crypto-js": 4.1.1 @@ -5995,7 +5995,7 @@ __metadata: "@babel/cli": 7.16.7 "@babel/core": 7.16.7 "@babel/runtime-corejs3": 7.16.7 - "@prisma/sdk": 3.15.1 + "@prisma/sdk": 3.15.2 "@redwoodjs/api-server": 2.0.0 "@redwoodjs/internal": 2.0.0 "@redwoodjs/prerender": 2.0.0 @@ -6025,7 +6025,7 @@ __metadata: pascalcase: 1.0.0 pluralize: 8.0.0 prettier: 2.6.2 - prisma: 3.15.1 + prisma: 3.15.2 prompts: 2.4.2 rimraf: 3.0.2 secure-random-password: 0.2.3 @@ -6225,7 +6225,7 @@ __metadata: "@graphql-tools/schema": 8.3.13 "@graphql-tools/utils": 8.6.12 "@graphql-yoga/common": 2.7.0 - "@prisma/client": 3.15.1 + "@prisma/client": 3.15.2 "@redwoodjs/api": 2.0.0 "@redwoodjs/auth": 2.0.0 "@types/lodash.merge": 4.6.7 @@ -6330,8 +6330,8 @@ __metadata: "@babel/cli": 7.16.7 "@babel/core": 7.16.7 "@babel/runtime-corejs3": 7.16.7 - "@prisma/client": 3.15.1 - "@prisma/sdk": 3.15.1 + "@prisma/client": 3.15.2 + "@prisma/sdk": 3.15.2 core-js: 3.23.2 esbuild: 0.14.43 jest: 27.5.1 @@ -6367,7 +6367,7 @@ __metadata: "@babel/cli": 7.16.7 "@babel/core": 7.16.7 "@babel/runtime-corejs3": 7.16.7 - "@prisma/sdk": 3.15.1 + "@prisma/sdk": 3.15.2 "@redwoodjs/internal": 2.0.0 "@types/fs-extra": 9.0.13 "@types/line-column": 1.0.0 @@ -25206,15 +25206,15 @@ __metadata: languageName: node linkType: hard -"prisma@npm:3.15.1": - version: 3.15.1 - resolution: "prisma@npm:3.15.1" +"prisma@npm:3.15.2": + version: 3.15.2 + resolution: "prisma@npm:3.15.2" dependencies: "@prisma/engines": 3.15.1-1.461d6a05159055555eb7dfb337c9fb271cbd4d7e bin: prisma: build/index.js prisma2: build/index.js - checksum: 42b2658a2b204bbbe4a22bde24a63cbb4eb899093177e56fdf0a65e2280ec0bc56d86c1e0a31d1a0ed6a77794790b4a68fb736b73e2f4d48010dbdfb269a26ff + checksum: d308e67be18dc9fc908f0b06af9bdb3e551698d585c24ab778ea53ebda5cfffdb58a693fda252f7dc78eb980c389040a54417ca0e2ad1493c72a82a5e995a62b languageName: node linkType: hard From 4342d6957a777a959d81fc41bb2c3ed82000f15e Mon Sep 17 00:00:00 2001 From: David Thyresson Date: Tue, 21 Jun 2022 16:50:06 -0400 Subject: [PATCH 09/17] Maps JSON GraphQL Scalars to Prisma Json field types for compatibility (#5796) Co-authored-by: Daniel Choudhury --- .../__snapshots__/graphqlCodeGen.test.ts.snap | 8 ++++---- .../internal/src/__tests__/graphqlCodeGen.test.ts | 4 ++++ packages/internal/src/generate/graphqlCodeGen.ts | 11 +++++++---- packages/internal/src/generate/graphqlSchema.ts | 14 +------------- 4 files changed, 16 insertions(+), 21 deletions(-) diff --git a/packages/internal/src/__tests__/__snapshots__/graphqlCodeGen.test.ts.snap b/packages/internal/src/__tests__/__snapshots__/graphqlCodeGen.test.ts.snap index b9ae584a55ce..68d5bd903bd8 100644 --- a/packages/internal/src/__tests__/__snapshots__/graphqlCodeGen.test.ts.snap +++ b/packages/internal/src/__tests__/__snapshots__/graphqlCodeGen.test.ts.snap @@ -23,8 +23,8 @@ export type Scalars = { BigInt: number; Date: string; DateTime: string; - JSON: Record; - JSONObject: Record; + JSON: Prisma.JsonValue; + JSONObject: Prisma.JsonObject; Time: string; }; @@ -261,8 +261,8 @@ export type Scalars = { BigInt: number; Date: string; DateTime: string; - JSON: Record; - JSONObject: Record; + JSON: Prisma.JsonValue; + JSONObject: Prisma.JsonObject; Time: string; }; diff --git a/packages/internal/src/__tests__/graphqlCodeGen.test.ts b/packages/internal/src/__tests__/graphqlCodeGen.test.ts index 503487817cd1..88a88670392a 100644 --- a/packages/internal/src/__tests__/graphqlCodeGen.test.ts +++ b/packages/internal/src/__tests__/graphqlCodeGen.test.ts @@ -51,6 +51,10 @@ test('Generate gql typedefs api', async () => { (file: fs.PathOrFileDescriptor, data: string | ArrayBufferView) => { expect(file).toMatch(path.join('api', 'types', 'graphql.d.ts')) expect(data).toMatchSnapshot() + + // Check that JSON types are imported from prisma + expect(data).toContain('JSON: Prisma.JsonValue;') + expect(data).toContain('JSONObject: Prisma.JsonObject;') } ) diff --git a/packages/internal/src/generate/graphqlCodeGen.ts b/packages/internal/src/generate/graphqlCodeGen.ts index d69f7ce8f830..d08faab0a8dc 100644 --- a/packages/internal/src/generate/graphqlCodeGen.ts +++ b/packages/internal/src/generate/graphqlCodeGen.ts @@ -131,6 +131,11 @@ function getPluginConfig() { if (prismaModels.RW_DataMigration) { delete prismaModels.RW_DataMigration } + + // Include Prisma's JSON field types as these types exist to match the types supported by JSON.parse() + // see: https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields/working-with-json-fields + // We're doing this to avoid adding an extra import statement just for the Prisma namespace + prismaModels['JSON'] = `.prisma/client#Prisma` } catch (error) { // This means they've not set up prisma types yet } @@ -141,13 +146,11 @@ function getPluginConfig() { namingConvention: 'keep', // to allow camelCased query names scalars: { // We need these, otherwise these scalars are mapped to any - // @TODO is there a way we can use scalars defined in - // packages/graphql-server/src/rootSchema.ts BigInt: 'number', DateTime: 'string', Date: 'string', - JSON: 'Record', - JSONObject: 'Record', + JSON: 'Prisma.JsonValue', + JSONObject: 'Prisma.JsonObject', Time: 'string', }, // prevent type names being PetQueryQuery, RW generators already append diff --git a/packages/internal/src/generate/graphqlSchema.ts b/packages/internal/src/generate/graphqlSchema.ts index db6f64e1526e..5dd42ad1469e 100644 --- a/packages/internal/src/generate/graphqlSchema.ts +++ b/packages/internal/src/generate/graphqlSchema.ts @@ -20,17 +20,6 @@ export const generateGraphQLSchema = async () => { 'directives/**/*.{js,ts}': {}, } - const config = { - scalars: { - BigInt: 'number', - DateTime: 'string', - Date: 'string', - JSON: 'Record', - JSONObject: 'Record', - Time: 'string', - }, - } - const loadSchemaConfig: LoadSchemaOptions = { assumeValidSDL: true, sort: true, @@ -38,7 +27,6 @@ export const generateGraphQLSchema = async () => { includeSources: true, cwd: getPaths().api.src, schema: Object.keys(schemaPointerMap), - config, generates: { [getPaths().generated.schema]: { plugins: ['schema-ast'], @@ -101,7 +89,7 @@ export const generateGraphQLSchema = async () => { } const options: CodegenTypes.GenerateOptions = { - config, + config: {}, // no extra config needed for merged schema file generation plugins: [{ 'schema-ast': {} }], pluginMap: { 'schema-ast': schemaAstPlugin }, schema: {} as unknown as DocumentNode, From 350642e1c6f32f52d062d60ff346ba31cd41339d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 23 Jun 2022 00:12:09 +0900 Subject: [PATCH 10/17] chore(deps): update dependency esbuild to v0.14.47 (#5798) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- packages/core/package.json | 2 +- packages/internal/package.json | 2 +- packages/record/package.json | 2 +- yarn.lock | 176 ++++++++++++++++----------------- 4 files changed, 91 insertions(+), 91 deletions(-) diff --git a/packages/core/package.json b/packages/core/package.json index b617e63def40..0ee000d0479e 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -63,7 +63,7 @@ "css-loader": "6.7.1", "css-minimizer-webpack-plugin": "4.0.0", "dotenv-webpack": "7.1.0", - "esbuild": "0.14.43", + "esbuild": "0.14.47", "esbuild-loader": "2.19.0", "fast-glob": "3.2.11", "file-loader": "6.2.0", diff --git a/packages/internal/package.json b/packages/internal/package.json index ff999dfd94cb..20863d9745da 100644 --- a/packages/internal/package.json +++ b/packages/internal/package.json @@ -46,7 +46,7 @@ "chalk": "4.1.2", "core-js": "3.23.2", "deepmerge": "4.2.2", - "esbuild": "0.14.43", + "esbuild": "0.14.47", "fast-glob": "3.2.11", "findup-sync": "5.0.0", "fs-extra": "10.1.0", diff --git a/packages/record/package.json b/packages/record/package.json index 2f05d1316fcb..9b8ffc6dc535 100644 --- a/packages/record/package.json +++ b/packages/record/package.json @@ -35,7 +35,7 @@ "@babel/cli": "7.16.7", "@babel/core": "7.16.7", "@prisma/sdk": "3.15.2", - "esbuild": "0.14.43", + "esbuild": "0.14.47", "jest": "27.5.1" }, "gitHead": "3905ed045508b861b495f8d5630d76c7a157d8f1" diff --git a/yarn.lock b/yarn.lock index f51f9431773e..f8afa4cd99ad 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6109,7 +6109,7 @@ __metadata: css-loader: 6.7.1 css-minimizer-webpack-plugin: 4.0.0 dotenv-webpack: 7.1.0 - esbuild: 0.14.43 + esbuild: 0.14.47 esbuild-loader: 2.19.0 fast-glob: 3.2.11 file-loader: 6.2.0 @@ -6275,7 +6275,7 @@ __metadata: chalk: 4.1.2 core-js: 3.23.2 deepmerge: 4.2.2 - esbuild: 0.14.43 + esbuild: 0.14.47 fast-glob: 3.2.11 findup-sync: 5.0.0 fs-extra: 10.1.0 @@ -6333,7 +6333,7 @@ __metadata: "@prisma/client": 3.15.2 "@prisma/sdk": 3.15.2 core-js: 3.23.2 - esbuild: 0.14.43 + esbuild: 0.14.47 jest: 27.5.1 languageName: unknown linkType: soft @@ -14997,100 +14997,100 @@ __metadata: languageName: node linkType: hard -"esbuild-android-64@npm:0.14.43": - version: 0.14.43 - resolution: "esbuild-android-64@npm:0.14.43" +"esbuild-android-64@npm:0.14.47": + version: 0.14.47 + resolution: "esbuild-android-64@npm:0.14.47" conditions: os=android & cpu=x64 languageName: node linkType: hard -"esbuild-android-arm64@npm:0.14.43": - version: 0.14.43 - resolution: "esbuild-android-arm64@npm:0.14.43" +"esbuild-android-arm64@npm:0.14.47": + version: 0.14.47 + resolution: "esbuild-android-arm64@npm:0.14.47" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"esbuild-darwin-64@npm:0.14.43": - version: 0.14.43 - resolution: "esbuild-darwin-64@npm:0.14.43" +"esbuild-darwin-64@npm:0.14.47": + version: 0.14.47 + resolution: "esbuild-darwin-64@npm:0.14.47" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"esbuild-darwin-arm64@npm:0.14.43": - version: 0.14.43 - resolution: "esbuild-darwin-arm64@npm:0.14.43" +"esbuild-darwin-arm64@npm:0.14.47": + version: 0.14.47 + resolution: "esbuild-darwin-arm64@npm:0.14.47" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"esbuild-freebsd-64@npm:0.14.43": - version: 0.14.43 - resolution: "esbuild-freebsd-64@npm:0.14.43" +"esbuild-freebsd-64@npm:0.14.47": + version: 0.14.47 + resolution: "esbuild-freebsd-64@npm:0.14.47" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"esbuild-freebsd-arm64@npm:0.14.43": - version: 0.14.43 - resolution: "esbuild-freebsd-arm64@npm:0.14.43" +"esbuild-freebsd-arm64@npm:0.14.47": + version: 0.14.47 + resolution: "esbuild-freebsd-arm64@npm:0.14.47" conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard -"esbuild-linux-32@npm:0.14.43": - version: 0.14.43 - resolution: "esbuild-linux-32@npm:0.14.43" +"esbuild-linux-32@npm:0.14.47": + version: 0.14.47 + resolution: "esbuild-linux-32@npm:0.14.47" conditions: os=linux & cpu=ia32 languageName: node linkType: hard -"esbuild-linux-64@npm:0.14.43": - version: 0.14.43 - resolution: "esbuild-linux-64@npm:0.14.43" +"esbuild-linux-64@npm:0.14.47": + version: 0.14.47 + resolution: "esbuild-linux-64@npm:0.14.47" conditions: os=linux & cpu=x64 languageName: node linkType: hard -"esbuild-linux-arm64@npm:0.14.43": - version: 0.14.43 - resolution: "esbuild-linux-arm64@npm:0.14.43" +"esbuild-linux-arm64@npm:0.14.47": + version: 0.14.47 + resolution: "esbuild-linux-arm64@npm:0.14.47" conditions: os=linux & cpu=arm64 languageName: node linkType: hard -"esbuild-linux-arm@npm:0.14.43": - version: 0.14.43 - resolution: "esbuild-linux-arm@npm:0.14.43" +"esbuild-linux-arm@npm:0.14.47": + version: 0.14.47 + resolution: "esbuild-linux-arm@npm:0.14.47" conditions: os=linux & cpu=arm languageName: node linkType: hard -"esbuild-linux-mips64le@npm:0.14.43": - version: 0.14.43 - resolution: "esbuild-linux-mips64le@npm:0.14.43" +"esbuild-linux-mips64le@npm:0.14.47": + version: 0.14.47 + resolution: "esbuild-linux-mips64le@npm:0.14.47" conditions: os=linux & cpu=mips64el languageName: node linkType: hard -"esbuild-linux-ppc64le@npm:0.14.43": - version: 0.14.43 - resolution: "esbuild-linux-ppc64le@npm:0.14.43" +"esbuild-linux-ppc64le@npm:0.14.47": + version: 0.14.47 + resolution: "esbuild-linux-ppc64le@npm:0.14.47" conditions: os=linux & cpu=ppc64 languageName: node linkType: hard -"esbuild-linux-riscv64@npm:0.14.43": - version: 0.14.43 - resolution: "esbuild-linux-riscv64@npm:0.14.43" +"esbuild-linux-riscv64@npm:0.14.47": + version: 0.14.47 + resolution: "esbuild-linux-riscv64@npm:0.14.47" conditions: os=linux & cpu=riscv64 languageName: node linkType: hard -"esbuild-linux-s390x@npm:0.14.43": - version: 0.14.43 - resolution: "esbuild-linux-s390x@npm:0.14.43" +"esbuild-linux-s390x@npm:0.14.47": + version: 0.14.47 + resolution: "esbuild-linux-s390x@npm:0.14.47" conditions: os=linux & cpu=s390x languageName: node linkType: hard @@ -15111,72 +15111,72 @@ __metadata: languageName: node linkType: hard -"esbuild-netbsd-64@npm:0.14.43": - version: 0.14.43 - resolution: "esbuild-netbsd-64@npm:0.14.43" +"esbuild-netbsd-64@npm:0.14.47": + version: 0.14.47 + resolution: "esbuild-netbsd-64@npm:0.14.47" conditions: os=netbsd & cpu=x64 languageName: node linkType: hard -"esbuild-openbsd-64@npm:0.14.43": - version: 0.14.43 - resolution: "esbuild-openbsd-64@npm:0.14.43" +"esbuild-openbsd-64@npm:0.14.47": + version: 0.14.47 + resolution: "esbuild-openbsd-64@npm:0.14.47" conditions: os=openbsd & cpu=x64 languageName: node linkType: hard -"esbuild-sunos-64@npm:0.14.43": - version: 0.14.43 - resolution: "esbuild-sunos-64@npm:0.14.43" +"esbuild-sunos-64@npm:0.14.47": + version: 0.14.47 + resolution: "esbuild-sunos-64@npm:0.14.47" conditions: os=sunos & cpu=x64 languageName: node linkType: hard -"esbuild-windows-32@npm:0.14.43": - version: 0.14.43 - resolution: "esbuild-windows-32@npm:0.14.43" +"esbuild-windows-32@npm:0.14.47": + version: 0.14.47 + resolution: "esbuild-windows-32@npm:0.14.47" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"esbuild-windows-64@npm:0.14.43": - version: 0.14.43 - resolution: "esbuild-windows-64@npm:0.14.43" +"esbuild-windows-64@npm:0.14.47": + version: 0.14.47 + resolution: "esbuild-windows-64@npm:0.14.47" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"esbuild-windows-arm64@npm:0.14.43": - version: 0.14.43 - resolution: "esbuild-windows-arm64@npm:0.14.43" +"esbuild-windows-arm64@npm:0.14.47": + version: 0.14.47 + resolution: "esbuild-windows-arm64@npm:0.14.47" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"esbuild@npm:0.14.43, esbuild@npm:^0.14.39": - version: 0.14.43 - resolution: "esbuild@npm:0.14.43" - dependencies: - esbuild-android-64: 0.14.43 - esbuild-android-arm64: 0.14.43 - esbuild-darwin-64: 0.14.43 - esbuild-darwin-arm64: 0.14.43 - esbuild-freebsd-64: 0.14.43 - esbuild-freebsd-arm64: 0.14.43 - esbuild-linux-32: 0.14.43 - esbuild-linux-64: 0.14.43 - esbuild-linux-arm: 0.14.43 - esbuild-linux-arm64: 0.14.43 - esbuild-linux-mips64le: 0.14.43 - esbuild-linux-ppc64le: 0.14.43 - esbuild-linux-riscv64: 0.14.43 - esbuild-linux-s390x: 0.14.43 - esbuild-netbsd-64: 0.14.43 - esbuild-openbsd-64: 0.14.43 - esbuild-sunos-64: 0.14.43 - esbuild-windows-32: 0.14.43 - esbuild-windows-64: 0.14.43 - esbuild-windows-arm64: 0.14.43 +"esbuild@npm:0.14.47, esbuild@npm:^0.14.39": + version: 0.14.47 + resolution: "esbuild@npm:0.14.47" + dependencies: + esbuild-android-64: 0.14.47 + esbuild-android-arm64: 0.14.47 + esbuild-darwin-64: 0.14.47 + esbuild-darwin-arm64: 0.14.47 + esbuild-freebsd-64: 0.14.47 + esbuild-freebsd-arm64: 0.14.47 + esbuild-linux-32: 0.14.47 + esbuild-linux-64: 0.14.47 + esbuild-linux-arm: 0.14.47 + esbuild-linux-arm64: 0.14.47 + esbuild-linux-mips64le: 0.14.47 + esbuild-linux-ppc64le: 0.14.47 + esbuild-linux-riscv64: 0.14.47 + esbuild-linux-s390x: 0.14.47 + esbuild-netbsd-64: 0.14.47 + esbuild-openbsd-64: 0.14.47 + esbuild-sunos-64: 0.14.47 + esbuild-windows-32: 0.14.47 + esbuild-windows-64: 0.14.47 + esbuild-windows-arm64: 0.14.47 dependenciesMeta: esbuild-android-64: optional: true @@ -15220,7 +15220,7 @@ __metadata: optional: true bin: esbuild: bin/esbuild - checksum: 7e6e4948e83d3feb798619846f8288b79ec8d9d903c1664f55d0cd604321ca3b7d1ee61fe7c7eaf436a1733317a3cda30d04782a90d79b791f6f50da513c72c3 + checksum: 7f3e23ff1eaebe23ea8fa3caec4c7e020794db41f00d54985c25408734fc691723f96a2db9fe7cdf4cdc3dec07cf37e42ba7ff93ab98179e03554ee3c0c4a393 languageName: node linkType: hard From 053b22dc90e0b81f42f68d8ec06046169184e070 Mon Sep 17 00:00:00 2001 From: Leon <18523441+Leon-Sam@users.noreply.github.com> Date: Wed, 22 Jun 2022 14:17:00 -0400 Subject: [PATCH 11/17] Add Azure AD B2C auth provider compatibility (#5781) * Fix "interaction_in_progress" MSAL error on page load * Add logic to support Azure AD B2C auth * Update formatting to pass linter Co-authored-by: David Thyresson --- docs/docs/auth/azure.md | 45 +++++++++++++++++++ docs/docs/authentication.md | 4 +- .../src/auth/decoders/azureActiveDirectory.ts | 10 ++++- .../src/authClients/azureActiveDirectory.ts | 37 ++++++++------- 4 files changed, 73 insertions(+), 23 deletions(-) diff --git a/docs/docs/auth/azure.md b/docs/docs/auth/azure.md index 69adf5041986..9a1bf4155c23 100644 --- a/docs/docs/auth/azure.md +++ b/docs/docs/auth/azure.md @@ -4,6 +4,12 @@ sidebar_label: Azure # Azure Active Directory Authentication ++++ View Installation and Setup + +> **Azure AD B2C Compatibility Note** +> +> Microsoft [Azure AD B2C](https://docs.microsoft.com/en-us/azure/active-directory-b2c/overview) auth product *is* compatible with this auth provider. See below for additional configuration details. + ## Installation The following CLI command will install required packages and generate boilerplate code and files for Redwood Projects: @@ -95,3 +101,42 @@ await getToken({ ``` See [acquireTokenSilent](https://azuread.github.io/microsoft-authentication-library-for-js/ref/classes/_azure_msal_browser.publicclientapplication.html#acquiretokensilent), [Resources and Scopes](https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/resources-and-scopes.md#resources-and-scopes) or [full class documentation](https://pub.dev/documentation/msal_js/latest/msal_js/PublicClientApplication-class.html#constructors) for more documentation. + +## Azure AD B2C specific configuration + +Using Azure AD B2C with [hosted user flows](https://docs.microsoft.com/en-us/azure/active-directory-b2c/add-sign-up-and-sign-in-policy?pivots=b2c-user-flow) requires 2 extra settings + +#### Update the .env file: + +- [MS Documentation about B2C JWT Issuer](https://docs.microsoft.com/en-us/azure/active-directory-b2c/tokens-overview) + +- [MS Documentation about MSAL, Azure B2C (authority|known authorities) parameters](https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/working-with-b2c.md +) + +``` bash title="./.env" + +AZURE_ACTIVE_DIRECTORY_AUTHORITY=https://{your-microsoft-tenant-name}.b2clogin.com/{{your-microsoft-tenant-name}}.onmicrosoft.com/{{your-microsoft-user-flow-id}} + +AZURE_ACTIVE_DIRECTORY_JWT_ISSUER=https://{{your_microsoft_tenant_name}}.b2clogin.com/{{your_microsoft_tenant_id}}/v2.0/ + +AZURE_ACTIVE_DIRECTORY_KNOWN_AUTHORITY=https://{{ms_tenant_name}}.b2clogin.com + +``` + +#### Update const activeDirectoryClient instance + This lets the MSAL (Microsoft Authenication Library) web side client know about our new B2C allowed authority that we defined in the .env file +``` jsx title="./web/App.jsx|.tsx + +const azureActiveDirectoryClient = new PublicClientApplication({ + auth: { + clientId: process.env.AZURE_ACTIVE_DIRECTORY_CLIENT_ID, + authority: process.env.AZURE_ACTIVE_DIRECTORY_AUTHORITY, + redirectUri: process.env.AZURE_ACTIVE_DIRECTORY_REDIRECT_URI, + postLogoutRedirectUri: process.env.AZURE_ACTIVE_DIRECTORY_LOGOUT_REDIRECT_URI, + // highlight-next-line + knownAuthorities:[process.env.AZURE_ACTIVE_DIRECTORY_KNOWN_AUTHORITY] + }, + }) +``` + +Now you can call the login and logout functions from useAuth(), and everything should just work® diff --git a/docs/docs/authentication.md b/docs/docs/authentication.md index 12062f77f72c..7abdae8770b6 100644 --- a/docs/docs/authentication.md +++ b/docs/docs/authentication.md @@ -40,7 +40,7 @@ import { useAuth } from '@redwoodjs/auth' export const MyComponent = () => { const { currentUser, isAuthenticated, logIn, logOut } = useAuth() - + return (
  • The current user is: {currentUser}
  • @@ -79,7 +79,7 @@ export const MyComponent = () => { {hasRole('admin') && ( Admin )} - + {hasRole(['author', 'editor']) && ( Admin )} diff --git a/packages/api/src/auth/decoders/azureActiveDirectory.ts b/packages/api/src/auth/decoders/azureActiveDirectory.ts index e94fa6c25079..4f537993648f 100644 --- a/packages/api/src/auth/decoders/azureActiveDirectory.ts +++ b/packages/api/src/auth/decoders/azureActiveDirectory.ts @@ -5,7 +5,10 @@ export const azureActiveDirectory = async ( token: string ): Promise> => { return new Promise((resolve, reject) => { - const { AZURE_ACTIVE_DIRECTORY_AUTHORITY } = process.env + const { + AZURE_ACTIVE_DIRECTORY_AUTHORITY, + AZURE_ACTIVE_DIRECTORY_JWT_ISSUER, + } = process.env // Make sure we have required environment variables if (!AZURE_ACTIVE_DIRECTORY_AUTHORITY) { @@ -43,7 +46,10 @@ export const azureActiveDirectory = async ( }) }, { - issuer: `${AZURE_ACTIVE_DIRECTORY_AUTHORITY}/v2.0`, + //Set via .env variable (Azure AD B2C use case) or assumes using normal AZURE AD issuer + issuer: AZURE_ACTIVE_DIRECTORY_JWT_ISSUER + ? AZURE_ACTIVE_DIRECTORY_JWT_ISSUER + : `${AZURE_ACTIVE_DIRECTORY_AUTHORITY}/v2.0`, algorithms: ['RS256'], }, (verifyError, decoded) => { diff --git a/packages/auth/src/authClients/azureActiveDirectory.ts b/packages/auth/src/authClients/azureActiveDirectory.ts index 72932b69ff61..bffd38b811a4 100644 --- a/packages/auth/src/authClients/azureActiveDirectory.ts +++ b/packages/auth/src/authClients/azureActiveDirectory.ts @@ -48,29 +48,28 @@ export const azureActiveDirectory = ( // As we are using the redirect flow, we need to call and wait for handleRedirectPromise to complete. // This should only happen on a valid redirect, and having it in the restoreAuthState makes sense for now. // https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/v1-migration.md#3-update-your-code - if (window.location.href.includes('#code=')) { - // Wait for promise - await client.handleRedirectPromise() + await client.handleRedirectPromise().then((token) => { + if (token) { + // Get accounts + const accounts = client.getAllAccounts() - // Get accounts - const accounts = client.getAllAccounts() + switch (accounts.length) { + case 0: + // No accounts so we need to login + client.loginRedirect() + break - switch (accounts.length) { - case 0: - // No accounts so we need to login - client.loginRedirect() - break + case 1: + // We have one account so we can set it as active + client.setActiveAccount(accounts[0]) + break - case 1: - // We have one account so we can set it as active - client.setActiveAccount(accounts[0]) - break - - default: - // We most likely have multiple accounts so we need to ask the user which one to use - client.loginRedirect() + default: + // We most likely have multiple accounts so we need to ask the user which one to use + client.loginRedirect() + } } - } + }) }, } } From 0ee65efe961cf14967e5908190e2aa2023347264 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 23 Jun 2022 14:15:22 +0900 Subject: [PATCH 12/17] chore(deps): update dependency firebase to v9.8.3 (#5799) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- packages/auth/package.json | 2 +- yarn.lock | 441 +++++++++++++++++++------------------ 2 files changed, 225 insertions(+), 218 deletions(-) diff --git a/packages/auth/package.json b/packages/auth/package.json index bed5b265518d..28179415ee62 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -38,7 +38,7 @@ "@supabase/supabase-js": "1.35.3", "@types/netlify-identity-widget": "1.9.3", "@types/react": "17.0.45", - "firebase": "9.8.2", + "firebase": "9.8.3", "firebase-admin": "10.2.0", "gotrue-js": "0.9.29", "jest": "27.5.1", diff --git a/yarn.lock b/yarn.lock index f8afa4cd99ad..abd7447ae30f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2502,18 +2502,18 @@ __metadata: languageName: node linkType: hard -"@firebase/analytics-compat@npm:0.1.10": - version: 0.1.10 - resolution: "@firebase/analytics-compat@npm:0.1.10" +"@firebase/analytics-compat@npm:0.1.11": + version: 0.1.11 + resolution: "@firebase/analytics-compat@npm:0.1.11" dependencies: - "@firebase/analytics": 0.7.9 + "@firebase/analytics": 0.7.10 "@firebase/analytics-types": 0.7.0 - "@firebase/component": 0.5.14 - "@firebase/util": 1.6.0 + "@firebase/component": 0.5.15 + "@firebase/util": 1.6.1 tslib: ^2.1.0 peerDependencies: "@firebase/app-compat": 0.x - checksum: b3450e9320a8bf5f1e72fd54321cf942d22fb494e0b58386301fabdd73bafbba458b4901b32fd02b95518284696b89c6ebd0f7efb8a8f229f55c7dd592599fac + checksum: 0a965ac4acddfd65fd66fd192b0c6525722fa6a5920adaf09b9f6eb4e8809834c99f2a078afba81e76d99ede251efbb731553aee841dfb01a2f9779d288527f8 languageName: node linkType: hard @@ -2524,34 +2524,34 @@ __metadata: languageName: node linkType: hard -"@firebase/analytics@npm:0.7.9": - version: 0.7.9 - resolution: "@firebase/analytics@npm:0.7.9" +"@firebase/analytics@npm:0.7.10": + version: 0.7.10 + resolution: "@firebase/analytics@npm:0.7.10" dependencies: - "@firebase/component": 0.5.14 - "@firebase/installations": 0.5.9 - "@firebase/logger": 0.3.2 - "@firebase/util": 1.6.0 + "@firebase/component": 0.5.15 + "@firebase/installations": 0.5.10 + "@firebase/logger": 0.3.3 + "@firebase/util": 1.6.1 tslib: ^2.1.0 peerDependencies: "@firebase/app": 0.x - checksum: db12043ba9845bc06ef9778194e2a3eb163a520271002cf86ffe1f40f27de2b41ec1bab4bee7c4d90a8cc86302c18e7888349409ea1ccb00fe6455a598158037 + checksum: 94e175156594d9c7c3affb52ea3c3ddaf2b10f46eb213cc5de4bdf1875fc7df6a041e373d6b2ec4ab13019391527f89d295c03bc48c5ce4a42154a0b31bfbc5e languageName: node linkType: hard -"@firebase/app-check-compat@npm:0.2.8": - version: 0.2.8 - resolution: "@firebase/app-check-compat@npm:0.2.8" +"@firebase/app-check-compat@npm:0.2.9": + version: 0.2.9 + resolution: "@firebase/app-check-compat@npm:0.2.9" dependencies: - "@firebase/app-check": 0.5.8 + "@firebase/app-check": 0.5.9 "@firebase/app-check-types": 0.4.0 - "@firebase/component": 0.5.14 - "@firebase/logger": 0.3.2 - "@firebase/util": 1.6.0 + "@firebase/component": 0.5.15 + "@firebase/logger": 0.3.3 + "@firebase/util": 1.6.1 tslib: ^2.1.0 peerDependencies: "@firebase/app-compat": 0.x - checksum: 1afba7f31cc3325ca4e364484e3110a7f91b2c7c1efeb42d1ce89e962a8fb7da11309f1807fcd3063aa930aa8b600cc58914ff4362f3bd5f8064552d19e1982d + checksum: a96f44ab1727e14c33cd0d059239cab245eed4b3071224ae0815945f4c8f90577682d06b51432b0b4ff5cf12ce050294147ae3fa8d9e59d70cdb5086b90a9a92 languageName: node linkType: hard @@ -2569,30 +2569,30 @@ __metadata: languageName: node linkType: hard -"@firebase/app-check@npm:0.5.8": - version: 0.5.8 - resolution: "@firebase/app-check@npm:0.5.8" +"@firebase/app-check@npm:0.5.9": + version: 0.5.9 + resolution: "@firebase/app-check@npm:0.5.9" dependencies: - "@firebase/component": 0.5.14 - "@firebase/logger": 0.3.2 - "@firebase/util": 1.6.0 + "@firebase/component": 0.5.15 + "@firebase/logger": 0.3.3 + "@firebase/util": 1.6.1 tslib: ^2.1.0 peerDependencies: "@firebase/app": 0.x - checksum: 420287a1e4b98f2e7364aaec20877c58a366fc4a5b47c9f7e9514a4de154c29d7ddbf243171f163724e8cda3c0b5488c71cc1533c25977db45da459bc82a0ecc + checksum: 6cad0acc5e8fb04dae52675d92a358884bf3cc3e02efef3b3745cfb892fa9241b90ba6a60d69bbc50b50b01336b8661c9b053e51078c4349a6542a2d3650b90f languageName: node linkType: hard -"@firebase/app-compat@npm:0.1.26": - version: 0.1.26 - resolution: "@firebase/app-compat@npm:0.1.26" +"@firebase/app-compat@npm:0.1.27": + version: 0.1.27 + resolution: "@firebase/app-compat@npm:0.1.27" dependencies: - "@firebase/app": 0.7.25 - "@firebase/component": 0.5.14 - "@firebase/logger": 0.3.2 - "@firebase/util": 1.6.0 + "@firebase/app": 0.7.26 + "@firebase/component": 0.5.15 + "@firebase/logger": 0.3.3 + "@firebase/util": 1.6.1 tslib: ^2.1.0 - checksum: 7f7e1019978d832f9612700b4c5e25c243b1c09e2da254556a314b0277dc1fc77b45d32e3a3376fa8c833d8005275fb5be2a43150d03bce1704081af3dcf24e9 + checksum: de50ca1a13a2cd6f0d82afe568ccef2b68a9896573be41f38420923ea1110704ea4cd49736f354383e1378f5709c02accd850893dd44e50da1dc3a27943f515e languageName: node linkType: hard @@ -2603,33 +2603,33 @@ __metadata: languageName: node linkType: hard -"@firebase/app@npm:0.7.25": - version: 0.7.25 - resolution: "@firebase/app@npm:0.7.25" +"@firebase/app@npm:0.7.26": + version: 0.7.26 + resolution: "@firebase/app@npm:0.7.26" dependencies: - "@firebase/component": 0.5.14 - "@firebase/logger": 0.3.2 - "@firebase/util": 1.6.0 + "@firebase/component": 0.5.15 + "@firebase/logger": 0.3.3 + "@firebase/util": 1.6.1 idb: 7.0.1 tslib: ^2.1.0 - checksum: b78b7e9a808d96f0878a1d3ef9b6621c479f3c9c3ae9f1b02b647e1245b21afbf03ded967cd2d2ebd812d9b51a2aff165cd75aca72d81a0b1a8ff479ab488f41 + checksum: 393e7ef06018b1c728dfee3516e608d24a5cb0779d5f70d91a1a05e36e043e9b15d9dc99125fcb427d09a8308f6f06b70dc1e99220885555dcd400ee38e7f364 languageName: node linkType: hard -"@firebase/auth-compat@npm:0.2.15": - version: 0.2.15 - resolution: "@firebase/auth-compat@npm:0.2.15" +"@firebase/auth-compat@npm:0.2.16": + version: 0.2.16 + resolution: "@firebase/auth-compat@npm:0.2.16" dependencies: - "@firebase/auth": 0.20.2 + "@firebase/auth": 0.20.3 "@firebase/auth-types": 0.11.0 - "@firebase/component": 0.5.14 - "@firebase/util": 1.6.0 + "@firebase/component": 0.5.15 + "@firebase/util": 1.6.1 node-fetch: 2.6.7 selenium-webdriver: 4.1.2 tslib: ^2.1.0 peerDependencies: "@firebase/app-compat": 0.x - checksum: 4a88759db77cc6ae7dd9cc0f396942b35209de2f7a9a76b37dac887d0b098c7d96988aca6a07a9cd3b2ebdaf39234757c6f147187448a5a8546eb97fe5f6b42e + checksum: bca9775b3768681cd089c0066d05884124f7c4c9f1def505c6e46d1457e64295c20850cdcf3a5d2820ab8086002dd8a56852e296c886a6e44533c355a323912a languageName: node linkType: hard @@ -2653,19 +2653,19 @@ __metadata: languageName: node linkType: hard -"@firebase/auth@npm:0.20.2": - version: 0.20.2 - resolution: "@firebase/auth@npm:0.20.2" +"@firebase/auth@npm:0.20.3": + version: 0.20.3 + resolution: "@firebase/auth@npm:0.20.3" dependencies: - "@firebase/component": 0.5.14 - "@firebase/logger": 0.3.2 - "@firebase/util": 1.6.0 + "@firebase/component": 0.5.15 + "@firebase/logger": 0.3.3 + "@firebase/util": 1.6.1 node-fetch: 2.6.7 selenium-webdriver: 4.1.2 tslib: ^2.1.0 peerDependencies: "@firebase/app": 0.x - checksum: ade5243b1e8dde83591b16473c0a4184c13ff3d7d9a87023e1d0d7be0bc242c79a4600382458237528ecb8247d9932bb6a633a4213f5b0d9f501c3810a0093da + checksum: 7f65d0b4bd7557044d5ac823c18c1a6e67021148c1472883fc7c2d73f2b9d76a4544c9a0ca4f25e03f8d7d4af0a7261139c2f1639353cf246329985bb2b9b304 languageName: node linkType: hard @@ -2679,29 +2679,27 @@ __metadata: languageName: node linkType: hard -"@firebase/component@npm:0.5.14": - version: 0.5.14 - resolution: "@firebase/component@npm:0.5.14" +"@firebase/component@npm:0.5.15": + version: 0.5.15 + resolution: "@firebase/component@npm:0.5.15" dependencies: - "@firebase/util": 1.6.0 + "@firebase/util": 1.6.1 tslib: ^2.1.0 - checksum: 7ab535d59d894c52c5d03d33ee06d63cc0a43ce04ced8279bbec47c3b974fcc8e26386d4ac4902a51f1ed8369ca87654971367576d6580488132bd9844c89fb0 + checksum: 5bf23560cb3b7071024512dabea14c3dd95e2085d5d99c1f1eccc67ff1dfd8f561c2c9dfdbff30883c21a6c92e822bf4cadce4613b25911a9cac0c83a5d869e6 languageName: node linkType: hard -"@firebase/database-compat@npm:0.2.0": - version: 0.2.0 - resolution: "@firebase/database-compat@npm:0.2.0" +"@firebase/database-compat@npm:0.2.1": + version: 0.2.1 + resolution: "@firebase/database-compat@npm:0.2.1" dependencies: - "@firebase/component": 0.5.14 - "@firebase/database": 0.13.0 - "@firebase/database-types": 0.9.8 - "@firebase/logger": 0.3.2 - "@firebase/util": 1.6.0 + "@firebase/component": 0.5.15 + "@firebase/database": 0.13.1 + "@firebase/database-types": 0.9.9 + "@firebase/logger": 0.3.3 + "@firebase/util": 1.6.1 tslib: ^2.1.0 - peerDependencies: - "@firebase/app-compat": 0.x - checksum: 0f43051659eadcd828047620f1df11fa0d46ce98c22ca2003f81be644c2eb6f4d5f4343ff7552b25b419e777011784f6dcd6ac638c9446f97c7fc6b7b8a0a44b + checksum: 309c8115d53991cb073682f47c67ab6bd8d83b7a8608354c26f3d664efdd46f16b38193b875eeb93cedf8a004749ec18c76f5075cc6f4d7c326010ee9d55caba languageName: node linkType: hard @@ -2731,13 +2729,13 @@ __metadata: languageName: node linkType: hard -"@firebase/database-types@npm:0.9.8, @firebase/database-types@npm:^0.9.7": - version: 0.9.8 - resolution: "@firebase/database-types@npm:0.9.8" +"@firebase/database-types@npm:0.9.9, @firebase/database-types@npm:^0.9.7": + version: 0.9.9 + resolution: "@firebase/database-types@npm:0.9.9" dependencies: "@firebase/app-types": 0.7.0 - "@firebase/util": 1.6.0 - checksum: 598f659fb606be52b421949c3f53ed6e0c8eeaf441573f129536342b4abbc952dffbc2c5b8c9490b9e68843e6172cb9e43e4b463b229d54d8f282055a1983a2d + "@firebase/util": 1.6.1 + checksum: 152f342d99e146fe23cf1f695fa658c0359b7df14c5f7e12569390b8ec5e2b2eb6f042565fc06d198944ec616275c812f6d327e87dda41b820e43575dbbd5116 languageName: node linkType: hard @@ -2755,32 +2753,32 @@ __metadata: languageName: node linkType: hard -"@firebase/database@npm:0.13.0": - version: 0.13.0 - resolution: "@firebase/database@npm:0.13.0" +"@firebase/database@npm:0.13.1": + version: 0.13.1 + resolution: "@firebase/database@npm:0.13.1" dependencies: "@firebase/auth-interop-types": 0.1.6 - "@firebase/component": 0.5.14 - "@firebase/logger": 0.3.2 - "@firebase/util": 1.6.0 + "@firebase/component": 0.5.15 + "@firebase/logger": 0.3.3 + "@firebase/util": 1.6.1 faye-websocket: 0.11.4 tslib: ^2.1.0 - checksum: 3972f87655356896cbe8cfa110495476d9b7dbdc58cbde34352e7d07ff5e55f16fc0c4bba26f16b1f4016724091809812d4e11462a480b0435a3b3bea0153c36 + checksum: 642c558828d244b58fc5b6edebda47e1dcce3b65e00fc1416b7687197e5294f66ffa58364b9386b4ff987a7faf0c454ef2266af9129e67ec0223e79ee5a21a87 languageName: node linkType: hard -"@firebase/firestore-compat@npm:0.1.18": - version: 0.1.18 - resolution: "@firebase/firestore-compat@npm:0.1.18" +"@firebase/firestore-compat@npm:0.1.19": + version: 0.1.19 + resolution: "@firebase/firestore-compat@npm:0.1.19" dependencies: - "@firebase/component": 0.5.14 - "@firebase/firestore": 3.4.9 + "@firebase/component": 0.5.15 + "@firebase/firestore": 3.4.10 "@firebase/firestore-types": 2.5.0 - "@firebase/util": 1.6.0 + "@firebase/util": 1.6.1 tslib: ^2.1.0 peerDependencies: "@firebase/app-compat": 0.x - checksum: 425b9704e45ecd92253d60dcb0d56d05f46da33ebf87472ef3f046601c63253d3f2f4e35c25a58779e0c83b5c76f965163a9fdd4e5ac3c9e9b8f7958351ce38d + checksum: 369ed8a44b3bade5efdd90bc6121aca91cc1507f29bc503ad841768db7ac7871c962ac268b38c66e6478e73a798c3b839535bdac216dbb6ca9897c3fe2cc0e0c languageName: node linkType: hard @@ -2794,36 +2792,36 @@ __metadata: languageName: node linkType: hard -"@firebase/firestore@npm:3.4.9": - version: 3.4.9 - resolution: "@firebase/firestore@npm:3.4.9" +"@firebase/firestore@npm:3.4.10": + version: 3.4.10 + resolution: "@firebase/firestore@npm:3.4.10" dependencies: - "@firebase/component": 0.5.14 - "@firebase/logger": 0.3.2 - "@firebase/util": 1.6.0 - "@firebase/webchannel-wrapper": 0.6.1 + "@firebase/component": 0.5.15 + "@firebase/logger": 0.3.3 + "@firebase/util": 1.6.1 + "@firebase/webchannel-wrapper": 0.6.2 "@grpc/grpc-js": ^1.3.2 "@grpc/proto-loader": ^0.6.0 node-fetch: 2.6.7 tslib: ^2.1.0 peerDependencies: "@firebase/app": 0.x - checksum: b2b4a1d31e2d45cc38e076680664cf034029b0c97d4510a23bfde11da0465af978ccbbfa85a5ca69e0301e950e7084de70a228708cf799893da2a02c5ea946e4 + checksum: 6af7f905774fd41c34a27a45aacbd8f2991c0642b2df9675c35034d43c96e797a9e039172df0f56a7cc5d74e6cf0f6b3b880c80557b09c7ae363235dfa83bef2 languageName: node linkType: hard -"@firebase/functions-compat@npm:0.2.1": - version: 0.2.1 - resolution: "@firebase/functions-compat@npm:0.2.1" +"@firebase/functions-compat@npm:0.2.2": + version: 0.2.2 + resolution: "@firebase/functions-compat@npm:0.2.2" dependencies: - "@firebase/component": 0.5.14 - "@firebase/functions": 0.8.1 + "@firebase/component": 0.5.15 + "@firebase/functions": 0.8.2 "@firebase/functions-types": 0.5.0 - "@firebase/util": 1.6.0 + "@firebase/util": 1.6.1 tslib: ^2.1.0 peerDependencies: "@firebase/app-compat": 0.x - checksum: 07d447bba55f8e161e5f297cb3d3929294701e1c64c3070c9eb4f3e16afc80d46d821677f9b8f3925c1ea04088ec422f09b1483b31ad274ab4838efdcf9be2ec + checksum: a3d73ebb036861560ab9d68c79c857843ab8a95ff0b62530ae79820cde53dd78f6395087a4b52f092e41b7a603050e28c5b4a4554d1ca33cd221369c97e2f632 languageName: node linkType: hard @@ -2834,34 +2832,34 @@ __metadata: languageName: node linkType: hard -"@firebase/functions@npm:0.8.1": - version: 0.8.1 - resolution: "@firebase/functions@npm:0.8.1" +"@firebase/functions@npm:0.8.2": + version: 0.8.2 + resolution: "@firebase/functions@npm:0.8.2" dependencies: "@firebase/app-check-interop-types": 0.1.0 "@firebase/auth-interop-types": 0.1.6 - "@firebase/component": 0.5.14 + "@firebase/component": 0.5.15 "@firebase/messaging-interop-types": 0.1.0 - "@firebase/util": 1.6.0 + "@firebase/util": 1.6.1 node-fetch: 2.6.7 tslib: ^2.1.0 peerDependencies: "@firebase/app": 0.x - checksum: 8a775522fafa239cbefea499a320e8b31b0d86a6519370347e3a948b85381c62f08b0e86b8da940a71e7b7e1787bc5bdb5d3be366c360dc5c3279e64709ec059 + checksum: ef1ce8688a7defe3a11c05a835a26fb031291eb4ded29557d84522164c4ab298cd983b0b092210a953053817fe4474fce81a988eaf89875e84b8cf15f6d97c11 languageName: node linkType: hard -"@firebase/installations@npm:0.5.9": - version: 0.5.9 - resolution: "@firebase/installations@npm:0.5.9" +"@firebase/installations@npm:0.5.10": + version: 0.5.10 + resolution: "@firebase/installations@npm:0.5.10" dependencies: - "@firebase/component": 0.5.14 - "@firebase/util": 1.6.0 + "@firebase/component": 0.5.15 + "@firebase/util": 1.6.1 idb: 7.0.1 tslib: ^2.1.0 peerDependencies: "@firebase/app": 0.x - checksum: 30e8865594363f2a0b36d742f11b7c1f08446e947c4271a587e7a3b0f61e37d22a5efd684032406fc87b86e61b4322e8cbfb8db3783855276e51e9e8ae1b4950 + checksum: 10071aad0123e738279aa062bbbc794b7b120aad3a25e6926d99a04d9bac9591b5bdf2cbf27da5d2b7d17efa30c7c58d1cc3d1b8bc3e07beeae97b6e88f74c72 languageName: node linkType: hard @@ -2874,17 +2872,26 @@ __metadata: languageName: node linkType: hard -"@firebase/messaging-compat@npm:0.1.13": - version: 0.1.13 - resolution: "@firebase/messaging-compat@npm:0.1.13" +"@firebase/logger@npm:0.3.3": + version: 0.3.3 + resolution: "@firebase/logger@npm:0.3.3" + dependencies: + tslib: ^2.1.0 + checksum: 049662ba252f749517358468e70bf353a98d487793d3e3b1f0ad5e7e38064b7109b6251652d54875855a6b74b8cbe45c6fec23cb853c02216e08f8796fa4d060 + languageName: node + linkType: hard + +"@firebase/messaging-compat@npm:0.1.14": + version: 0.1.14 + resolution: "@firebase/messaging-compat@npm:0.1.14" dependencies: - "@firebase/component": 0.5.14 - "@firebase/messaging": 0.9.13 - "@firebase/util": 1.6.0 + "@firebase/component": 0.5.15 + "@firebase/messaging": 0.9.14 + "@firebase/util": 1.6.1 tslib: ^2.1.0 peerDependencies: "@firebase/app-compat": 0.x - checksum: ae6f15f8c9d3090d5f19c2737b5c4f36eb9629da25fde43fd46db6c226043d09b2988012e613c4b164de5d09cbcb96a99aebf7dbd1e6b424f7e00a4a53c65159 + checksum: d25be39c4875585115901eac3d33953d342a87971e494608a1b1f8f23b1173c98cab5d686b69b3453381b7f0232feb99345cd525e5b02e44924f35d4cfb413b0 languageName: node linkType: hard @@ -2895,35 +2902,35 @@ __metadata: languageName: node linkType: hard -"@firebase/messaging@npm:0.9.13": - version: 0.9.13 - resolution: "@firebase/messaging@npm:0.9.13" +"@firebase/messaging@npm:0.9.14": + version: 0.9.14 + resolution: "@firebase/messaging@npm:0.9.14" dependencies: - "@firebase/component": 0.5.14 - "@firebase/installations": 0.5.9 + "@firebase/component": 0.5.15 + "@firebase/installations": 0.5.10 "@firebase/messaging-interop-types": 0.1.0 - "@firebase/util": 1.6.0 + "@firebase/util": 1.6.1 idb: 7.0.1 tslib: ^2.1.0 peerDependencies: "@firebase/app": 0.x - checksum: d68e09f9981e7426b8b746455fdf08550625e0049c47c009586ad72527f06af660d087e2f931c12e913f62fc79a089b312c5b63e08d0e5a6ff62dd2b89bb51f7 + checksum: 2d7dbbab9f7ce71115bcd4db7369b9bf74c08a8aedb65ebfb05c8fa2290a079bb9826323e2b0053e71655a5a7be067e250aaeea1ce048ca0095dc48663c285a7 languageName: node linkType: hard -"@firebase/performance-compat@npm:0.1.9": - version: 0.1.9 - resolution: "@firebase/performance-compat@npm:0.1.9" +"@firebase/performance-compat@npm:0.1.10": + version: 0.1.10 + resolution: "@firebase/performance-compat@npm:0.1.10" dependencies: - "@firebase/component": 0.5.14 - "@firebase/logger": 0.3.2 - "@firebase/performance": 0.5.9 + "@firebase/component": 0.5.15 + "@firebase/logger": 0.3.3 + "@firebase/performance": 0.5.10 "@firebase/performance-types": 0.1.0 - "@firebase/util": 1.6.0 + "@firebase/util": 1.6.1 tslib: ^2.1.0 peerDependencies: "@firebase/app-compat": 0.x - checksum: 37c8b2c6655f4046a39319a0c1695d5c2114178084a397c883ca2ddec83138da74dc371bf175cbb9c8045ba9c780924ba1451873dc9843a6dc7aab8122c268e1 + checksum: 65153a1863513f4ac4f4512a708eb349ae96f4b9b69710a9f169369f742414362f06b5309a93158cae756b5a9c53f31ed36f4b1427b5f4c567c34a18f7841281 languageName: node linkType: hard @@ -2934,18 +2941,18 @@ __metadata: languageName: node linkType: hard -"@firebase/performance@npm:0.5.9": - version: 0.5.9 - resolution: "@firebase/performance@npm:0.5.9" +"@firebase/performance@npm:0.5.10": + version: 0.5.10 + resolution: "@firebase/performance@npm:0.5.10" dependencies: - "@firebase/component": 0.5.14 - "@firebase/installations": 0.5.9 - "@firebase/logger": 0.3.2 - "@firebase/util": 1.6.0 + "@firebase/component": 0.5.15 + "@firebase/installations": 0.5.10 + "@firebase/logger": 0.3.3 + "@firebase/util": 1.6.1 tslib: ^2.1.0 peerDependencies: "@firebase/app": 0.x - checksum: 817134752c9761a00b67cda6bd7b2c3da5c78347527847bdf8a480c443a37bffa0a25f3393680b6bba7b7065cc167e1744a0888121efe8f373508098ef47fd61 + checksum: d418cc88a7a98e9ae8fcae8fb4f117dc87b7799e8682bc5e40c6595a203ef21de0d25f29b1b5f5f9a3d8cf8c295839b32f9e9056560037ad6fa5302ce99efe32 languageName: node linkType: hard @@ -2960,19 +2967,19 @@ __metadata: languageName: node linkType: hard -"@firebase/remote-config-compat@npm:0.1.9": - version: 0.1.9 - resolution: "@firebase/remote-config-compat@npm:0.1.9" +"@firebase/remote-config-compat@npm:0.1.10": + version: 0.1.10 + resolution: "@firebase/remote-config-compat@npm:0.1.10" dependencies: - "@firebase/component": 0.5.14 - "@firebase/logger": 0.3.2 - "@firebase/remote-config": 0.3.8 + "@firebase/component": 0.5.15 + "@firebase/logger": 0.3.3 + "@firebase/remote-config": 0.3.9 "@firebase/remote-config-types": 0.2.0 - "@firebase/util": 1.6.0 + "@firebase/util": 1.6.1 tslib: ^2.1.0 peerDependencies: "@firebase/app-compat": 0.x - checksum: 0ea9c01f1a956e38b603337ddab9f67194001cda4bec3276b55c45068ffbb75dcd3d99ee773c1552b33c1ef06f64d537d5e7adfdfe3493ecb3b37f9cf2eb68fa + checksum: 63f4792915179eb09186ae314ae6e6259319f4b92e6d8c85478a65a5e8bc0eed2fbca779dfb41950bddd3fe25a39f653019d473dd355f19e679b4bb5978489b1 languageName: node linkType: hard @@ -2983,33 +2990,33 @@ __metadata: languageName: node linkType: hard -"@firebase/remote-config@npm:0.3.8": - version: 0.3.8 - resolution: "@firebase/remote-config@npm:0.3.8" +"@firebase/remote-config@npm:0.3.9": + version: 0.3.9 + resolution: "@firebase/remote-config@npm:0.3.9" dependencies: - "@firebase/component": 0.5.14 - "@firebase/installations": 0.5.9 - "@firebase/logger": 0.3.2 - "@firebase/util": 1.6.0 + "@firebase/component": 0.5.15 + "@firebase/installations": 0.5.10 + "@firebase/logger": 0.3.3 + "@firebase/util": 1.6.1 tslib: ^2.1.0 peerDependencies: "@firebase/app": 0.x - checksum: b44f8fd941f2035ed5a1811dbfdf7113482bef27badf99135cbe654e3e81e09cfec0ae2607cc02bb9d0dc8a21688794ae4ba7076c580e14c5b9a89f8b4c36dd0 + checksum: 538cf7ab64a343655f1c49d0e6720a0cfa4d76bbf9c7bb70c3d958603b39d591f48fb4ace43e740ae416b28c1073b5fef042e78fb2898e87360488634126c9a3 languageName: node linkType: hard -"@firebase/storage-compat@npm:0.1.14": - version: 0.1.14 - resolution: "@firebase/storage-compat@npm:0.1.14" +"@firebase/storage-compat@npm:0.1.15": + version: 0.1.15 + resolution: "@firebase/storage-compat@npm:0.1.15" dependencies: - "@firebase/component": 0.5.14 - "@firebase/storage": 0.9.6 + "@firebase/component": 0.5.15 + "@firebase/storage": 0.9.7 "@firebase/storage-types": 0.6.0 - "@firebase/util": 1.6.0 + "@firebase/util": 1.6.1 tslib: ^2.1.0 peerDependencies: "@firebase/app-compat": 0.x - checksum: d99be8356ccc8371951af1c818bf8930aef7167dc938a3ce5f1dc08ef7df9008c078ba3e2d15dda818a55384e0cc1e8ab893e1d6fc7a1dd1d6186b413bc1125e + checksum: 5f951a5f4df7c31e79d0502188d7de7240a011d563b21ba9172686169752eee936b75a31fa1099c9f86fbd083e1e23c13aab865079588d7279bac91358bcfc9b languageName: node linkType: hard @@ -3023,17 +3030,17 @@ __metadata: languageName: node linkType: hard -"@firebase/storage@npm:0.9.6": - version: 0.9.6 - resolution: "@firebase/storage@npm:0.9.6" +"@firebase/storage@npm:0.9.7": + version: 0.9.7 + resolution: "@firebase/storage@npm:0.9.7" dependencies: - "@firebase/component": 0.5.14 - "@firebase/util": 1.6.0 + "@firebase/component": 0.5.15 + "@firebase/util": 1.6.1 node-fetch: 2.6.7 tslib: ^2.1.0 peerDependencies: "@firebase/app": 0.x - checksum: a9610c6c7b5d899444f7c750fa04193c577d415a94c7244277e3fc912dc1078f0054e6eb76de64028610a59198110b295dab822b5bacb4604402a1eea376353a + checksum: eff3c588b9efa58d78c85ccd7579c67ba242ba24c734a527debe7997f92f88fc0892a0447b81ee9c03fc3c4dacb185b2c75a5fb689e300893346570ef47181c0 languageName: node linkType: hard @@ -3046,19 +3053,19 @@ __metadata: languageName: node linkType: hard -"@firebase/util@npm:1.6.0": - version: 1.6.0 - resolution: "@firebase/util@npm:1.6.0" +"@firebase/util@npm:1.6.1": + version: 1.6.1 + resolution: "@firebase/util@npm:1.6.1" dependencies: tslib: ^2.1.0 - checksum: c98d968be4fd1a11e46796f95d2e3c77a794ca834f5bcf86d08fc1c4d085457982bc406d7a3a019124e8179c919ebee1efec4a50dc821a13c43e679efc694a1c + checksum: 646d69a465fc46830f93bad6dd55d18c7bd0d17080230738a21cb2f561ec85767ab01013d9c4d688773998a9f79bfb058e266ccd21f628c42c9c01600a6c7970 languageName: node linkType: hard -"@firebase/webchannel-wrapper@npm:0.6.1": - version: 0.6.1 - resolution: "@firebase/webchannel-wrapper@npm:0.6.1" - checksum: ecdd59320c400cd807688672f4695383cc0994c7292bfd81058ab6eafd9788ff47073b327df5f9410fa65de62625caf2ea9585a42281adb253bd47ee9e18654f +"@firebase/webchannel-wrapper@npm:0.6.2": + version: 0.6.2 + resolution: "@firebase/webchannel-wrapper@npm:0.6.2" + checksum: 7a574acccae4e459f9beabc63a643ede990150cd9d09f45e0d2d5d22b96f42f4baabfa0446cbdd986f0ba7104a0be1ecccc70081b41b4d4eb845107629ee7ed7 languageName: node linkType: hard @@ -5976,7 +5983,7 @@ __metadata: "@types/netlify-identity-widget": 1.9.3 "@types/react": 17.0.45 core-js: 3.23.2 - firebase: 9.8.2 + firebase: 9.8.3 firebase-admin: 10.2.0 gotrue-js: 0.9.29 jest: 27.5.1 @@ -16557,37 +16564,37 @@ __metadata: languageName: node linkType: hard -"firebase@npm:9.8.2": - version: 9.8.2 - resolution: "firebase@npm:9.8.2" +"firebase@npm:9.8.3": + version: 9.8.3 + resolution: "firebase@npm:9.8.3" dependencies: - "@firebase/analytics": 0.7.9 - "@firebase/analytics-compat": 0.1.10 - "@firebase/app": 0.7.25 - "@firebase/app-check": 0.5.8 - "@firebase/app-check-compat": 0.2.8 - "@firebase/app-compat": 0.1.26 + "@firebase/analytics": 0.7.10 + "@firebase/analytics-compat": 0.1.11 + "@firebase/app": 0.7.26 + "@firebase/app-check": 0.5.9 + "@firebase/app-check-compat": 0.2.9 + "@firebase/app-compat": 0.1.27 "@firebase/app-types": 0.7.0 - "@firebase/auth": 0.20.2 - "@firebase/auth-compat": 0.2.15 - "@firebase/database": 0.13.0 - "@firebase/database-compat": 0.2.0 - "@firebase/firestore": 3.4.9 - "@firebase/firestore-compat": 0.1.18 - "@firebase/functions": 0.8.1 - "@firebase/functions-compat": 0.2.1 - "@firebase/installations": 0.5.9 - "@firebase/messaging": 0.9.13 - "@firebase/messaging-compat": 0.1.13 - "@firebase/performance": 0.5.9 - "@firebase/performance-compat": 0.1.9 + "@firebase/auth": 0.20.3 + "@firebase/auth-compat": 0.2.16 + "@firebase/database": 0.13.1 + "@firebase/database-compat": 0.2.1 + "@firebase/firestore": 3.4.10 + "@firebase/firestore-compat": 0.1.19 + "@firebase/functions": 0.8.2 + "@firebase/functions-compat": 0.2.2 + "@firebase/installations": 0.5.10 + "@firebase/messaging": 0.9.14 + "@firebase/messaging-compat": 0.1.14 + "@firebase/performance": 0.5.10 + "@firebase/performance-compat": 0.1.10 "@firebase/polyfill": 0.3.36 - "@firebase/remote-config": 0.3.8 - "@firebase/remote-config-compat": 0.1.9 - "@firebase/storage": 0.9.6 - "@firebase/storage-compat": 0.1.14 - "@firebase/util": 1.6.0 - checksum: 233162a03b0c896a8b8992262d6094fbee4ec0442fa35d4967d5722c552fbb46ddf48ee95bcf269fd9f4476b2526738777e21a6b0017ece9f4c8ad6f654c3055 + "@firebase/remote-config": 0.3.9 + "@firebase/remote-config-compat": 0.1.10 + "@firebase/storage": 0.9.7 + "@firebase/storage-compat": 0.1.15 + "@firebase/util": 1.6.1 + checksum: 560a3100874e8470a6e2a4a76331856a5064695fd645bc41f9616b209b090f49e5498ddd98439985739d7c125408e78926a6aab0fb75d4209b6cbaa8227eaec7 languageName: node linkType: hard From ff37e9ebd390c30fa8ddbceae4881cfc20b8ee37 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 23 Jun 2022 15:08:17 +0900 Subject: [PATCH 13/17] fix(deps): update dependency @apollo/client to v3.6.9 (#5804) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- packages/web/package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/web/package.json b/packages/web/package.json index 06cf65fb10db..13b599e5c451 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -36,7 +36,7 @@ "test:watch": "yarn test --watch" }, "dependencies": { - "@apollo/client": "3.6.8", + "@apollo/client": "3.6.9", "@babel/runtime-corejs3": "7.16.7", "@redwoodjs/auth": "2.0.0", "core-js": "3.23.2", diff --git a/yarn.lock b/yarn.lock index abd7447ae30f..2f02e5bbeea9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -48,9 +48,9 @@ __metadata: languageName: node linkType: hard -"@apollo/client@npm:3.6.8": - version: 3.6.8 - resolution: "@apollo/client@npm:3.6.8" +"@apollo/client@npm:3.6.9": + version: 3.6.9 + resolution: "@apollo/client@npm:3.6.9" dependencies: "@graphql-typed-document-node/core": ^3.1.1 "@wry/context": ^0.6.0 @@ -76,7 +76,7 @@ __metadata: optional: true subscriptions-transport-ws: optional: true - checksum: 600342755e32ce6a729206c4668158d21535df93317fe947abc115a217d877be39bcbc071acd1d8e5294d1682c4337c6ce3c0dca609f920039c25088f8cdc927 + checksum: af61040828167023b2206ccc2bb24021bae400eabe5fdc38a2590e554d3b80deace1d324c2f1a4943dbcc468a11051ece3e927eec1a459a87e0ba59ed74853b0 languageName: node linkType: hard @@ -6474,7 +6474,7 @@ __metadata: version: 0.0.0-use.local resolution: "@redwoodjs/web@workspace:packages/web" dependencies: - "@apollo/client": 3.6.8 + "@apollo/client": 3.6.9 "@babel/cli": 7.16.7 "@babel/core": 7.16.7 "@babel/runtime-corejs3": 7.16.7 From 76491cc3b40fba393d5e97d144298a98fd2866b6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 23 Jun 2022 16:34:16 +0900 Subject: [PATCH 14/17] fix(deps): update dependency systeminformation to v5.11.21 (#5805) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- packages/internal/package.json | 2 +- packages/telemetry/package.json | 2 +- yarn.lock | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/internal/package.json b/packages/internal/package.json index 20863d9745da..30d847a2ec6f 100644 --- a/packages/internal/package.json +++ b/packages/internal/package.json @@ -55,7 +55,7 @@ "prettier": "2.6.2", "rimraf": "3.0.2", "string-env-interpolation": "1.0.1", - "systeminformation": "5.11.16", + "systeminformation": "5.11.21", "terminal-link": "2.1.1", "toml": "3.0.0" }, diff --git a/packages/telemetry/package.json b/packages/telemetry/package.json index 2eb3965edba6..5661564141a4 100644 --- a/packages/telemetry/package.json +++ b/packages/telemetry/package.json @@ -33,7 +33,7 @@ "core-js": "3.23.2", "cross-undici-fetch": "0.1.27", "envinfo": "7.8.1", - "systeminformation": "5.11.16", + "systeminformation": "5.11.21", "uuid": "8.3.2", "yargs": "17.5.1" }, diff --git a/yarn.lock b/yarn.lock index 2f02e5bbeea9..8fa4e9050599 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6293,7 +6293,7 @@ __metadata: prettier: 2.6.2 rimraf: 3.0.2 string-env-interpolation: 1.0.1 - systeminformation: 5.11.16 + systeminformation: 5.11.21 terminal-link: 2.1.1 toml: 3.0.0 typescript: 4.7.3 @@ -6423,7 +6423,7 @@ __metadata: cross-undici-fetch: 0.1.27 envinfo: 7.8.1 jest: 27.5.1 - systeminformation: 5.11.16 + systeminformation: 5.11.21 uuid: 8.3.2 yargs: 17.5.1 languageName: unknown @@ -28954,12 +28954,12 @@ __metadata: languageName: node linkType: hard -"systeminformation@npm:5.11.16": - version: 5.11.16 - resolution: "systeminformation@npm:5.11.16" +"systeminformation@npm:5.11.21": + version: 5.11.21 + resolution: "systeminformation@npm:5.11.21" bin: systeminformation: lib/cli.js - checksum: edb5c3e66ed3531042e2f491e172d4fff31d575b21073ecfd5f3fece865e93f3f58694b54a8f94f744cd5d5f2a1467ac9a39e1b47515e42e6bd02913357c6b33 + checksum: 5308f4808b46dad37392ce3f66dce4f7fa2469f99e461e8e86885e76f1c06e45627a431432494410e3de728d31ba5c6f91a8d60abefb4e6442ab3d99a46e1f21 conditions: (os=darwin | os=linux | os=win32 | os=freebsd | os=openbsd | os=netbsd | os=sunos | os=android) languageName: node linkType: hard From 057af6c8103fed85878ecbc7181bba51b7262ec5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 23 Jun 2022 17:28:52 +0900 Subject: [PATCH 15/17] fix(deps): update dependency eslint to v8.18.0 (#5806) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- packages/eslint-config/package.json | 2 +- yarn.lock | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index dfa4e379ec39..f7de2496e71b 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,7 @@ "core-js": "3.23.2", "cypress": "9.7.0", "cypress-wait-until": "1.7.2", - "eslint": "8.16.0", + "eslint": "8.18.0", "fast-glob": "3.2.11", "fs-extra": "10.1.0", "is-port-reachable": "3.1.0", diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json index f86b4a450c80..5e650a395d5c 100644 --- a/packages/eslint-config/package.json +++ b/packages/eslint-config/package.json @@ -18,7 +18,7 @@ "@redwoodjs/internal": "2.0.0", "@typescript-eslint/eslint-plugin": "5.26.0", "@typescript-eslint/parser": "5.26.0", - "eslint": "8.16.0", + "eslint": "8.18.0", "eslint-config-prettier": "8.5.0", "eslint-import-resolver-babel-module": "5.3.1", "eslint-plugin-babel": "5.3.1", diff --git a/yarn.lock b/yarn.lock index 8fa4e9050599..9a710ce71275 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6168,7 +6168,7 @@ __metadata: "@redwoodjs/internal": 2.0.0 "@typescript-eslint/eslint-plugin": 5.26.0 "@typescript-eslint/parser": 5.26.0 - eslint: 8.16.0 + eslint: 8.18.0 eslint-config-prettier: 8.5.0 eslint-import-resolver-babel-module: 5.3.1 eslint-plugin-babel: 5.3.1 @@ -15515,9 +15515,9 @@ __metadata: languageName: node linkType: hard -"eslint@npm:8.16.0": - version: 8.16.0 - resolution: "eslint@npm:8.16.0" +"eslint@npm:8.18.0": + version: 8.18.0 + resolution: "eslint@npm:8.18.0" dependencies: "@eslint/eslintrc": ^1.3.0 "@humanwhocodes/config-array": ^0.9.2 @@ -15556,7 +15556,7 @@ __metadata: v8-compile-cache: ^2.0.3 bin: eslint: bin/eslint.js - checksum: 175ed470440835a9b5fc01f3458b6fbcc58119647019a21672bab4907a407c0bf38b9401828fceeea0f724be94ac04e81cf454b1e5cab7565ffcfcb9fc9f148c + checksum: e35639453e5aeffaba8dcf5c304563f360fc599a12ebd2c01e9f33994a846bfa5263921455aecbcfbc2c2d01dba35fd0174d3cca3b3cb1a258aec2657c769bca languageName: node linkType: hard @@ -27044,7 +27044,7 @@ __metadata: core-js: 3.23.2 cypress: 9.7.0 cypress-wait-until: 1.7.2 - eslint: 8.16.0 + eslint: 8.18.0 fast-glob: 3.2.11 fs-extra: 10.1.0 is-port-reachable: 3.1.0 From 19204b2df8abe272a5448170a1ad4a3e65aeddf6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 23 Jun 2022 18:16:05 +0900 Subject: [PATCH 16/17] fix(deps): update dependency prettier to v2.7.1 (#5808) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- packages/cli/package.json | 2 +- packages/codemods/package.json | 2 +- packages/eslint-config/package.json | 2 +- packages/internal/package.json | 2 +- yarn.lock | 16 ++++++++-------- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index b93433750bd1..7372d76d69fa 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -56,7 +56,7 @@ "param-case": "3.0.4", "pascalcase": "1.0.0", "pluralize": "8.0.0", - "prettier": "2.6.2", + "prettier": "2.7.1", "prisma": "3.15.2", "prompts": "2.4.2", "rimraf": "3.0.2", diff --git a/packages/codemods/package.json b/packages/codemods/package.json index 88459005ae4a..bb8e9b0851bd 100644 --- a/packages/codemods/package.json +++ b/packages/codemods/package.json @@ -35,7 +35,7 @@ "findup-sync": "5.0.0", "jest": "27.5.1", "jscodeshift": "0.13.1", - "prettier": "2.6.2", + "prettier": "2.7.1", "tasuku": "2.0.0", "toml": "3.0.0", "yargs": "17.5.1" diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json index 5e650a395d5c..dd66c0d0f703 100644 --- a/packages/eslint-config/package.json +++ b/packages/eslint-config/package.json @@ -28,7 +28,7 @@ "eslint-plugin-prettier": "4.0.0", "eslint-plugin-react": "7.30.0", "eslint-plugin-react-hooks": "4.5.0", - "prettier": "2.6.2" + "prettier": "2.7.1" }, "devDependencies": { "@babel/cli": "7.16.7", diff --git a/packages/internal/package.json b/packages/internal/package.json index 30d847a2ec6f..4ef87a3ce5fe 100644 --- a/packages/internal/package.json +++ b/packages/internal/package.json @@ -52,7 +52,7 @@ "fs-extra": "10.1.0", "graphql": "16.5.0", "kill-port": "1.6.1", - "prettier": "2.6.2", + "prettier": "2.7.1", "rimraf": "3.0.2", "string-env-interpolation": "1.0.1", "systeminformation": "5.11.21", diff --git a/yarn.lock b/yarn.lock index 9a710ce71275..a2ab5389c5ed 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6031,7 +6031,7 @@ __metadata: param-case: 3.0.4 pascalcase: 1.0.0 pluralize: 8.0.0 - prettier: 2.6.2 + prettier: 2.7.1 prisma: 3.15.2 prompts: 2.4.2 rimraf: 3.0.2 @@ -6072,7 +6072,7 @@ __metadata: fs-extra: 10.1.0 jest: 27.5.1 jscodeshift: 0.13.1 - prettier: 2.6.2 + prettier: 2.7.1 tasuku: 2.0.0 tempy: 1.0.1 toml: 3.0.0 @@ -6179,7 +6179,7 @@ __metadata: eslint-plugin-react: 7.30.0 eslint-plugin-react-hooks: 4.5.0 jest: 27.5.1 - prettier: 2.6.2 + prettier: 2.7.1 typescript: 4.7.3 languageName: unknown linkType: soft @@ -6290,7 +6290,7 @@ __metadata: graphql-tag: 2.12.6 jest: 27.5.1 kill-port: 1.6.1 - prettier: 2.6.2 + prettier: 2.7.1 rimraf: 3.0.2 string-env-interpolation: 1.0.1 systeminformation: 5.11.21 @@ -25106,12 +25106,12 @@ __metadata: languageName: node linkType: hard -"prettier@npm:2.6.2, prettier@npm:^2.0.1, prettier@npm:^2.6.2": - version: 2.6.2 - resolution: "prettier@npm:2.6.2" +"prettier@npm:2.7.1, prettier@npm:^2.0.1, prettier@npm:^2.6.2": + version: 2.7.1 + resolution: "prettier@npm:2.7.1" bin: prettier: bin-prettier.js - checksum: 4a2717d0aca6b5b5c24570854fdf119c4184ff7422a1aa283364bdfe5394ecff4f6ac375663840dc2680ea09b1d5370329b83ac06579588db6f8bc71620e1267 + checksum: 359d2b7ecf36bd52924a48331cae506d335f18637fde6c686212f952b9ce678ce9f554a80571049b36ec2897a8a6c40094b776dea371cc5c04c481cf5b78504b languageName: node linkType: hard From 9ce4efa3aadcf1ab3325f6f34aa96e2e4061f3ec Mon Sep 17 00:00:00 2001 From: Alejandro Frias <3598338+AlejandroFrias@users.noreply.github.com> Date: Thu, 23 Jun 2022 09:32:17 -0700 Subject: [PATCH 17/17] validateUniquess optional prismaClient parameter (#5763) * validateUniques optional prismaClient parameter * update docs: new option for validateUniqueness * s/prismaClient/db/ * example usage of db option * tested docs * docs fine tuning * test default message still respected when using custom db option Co-authored-by: Alejandro Frias --- docs/docs/services.md | 21 +++++++++++-- .../validations/__tests__/validations.test.js | 30 +++++++++++++++++++ packages/api/src/validations/validations.ts | 28 +++++++++++++++-- 3 files changed, 73 insertions(+), 6 deletions(-) diff --git a/docs/docs/services.md b/docs/docs/services.md index 2d03ccd309be..3ff4b0505809 100644 --- a/docs/docs/services.md +++ b/docs/docs/services.md @@ -645,14 +645,14 @@ So `validateUniqueness()` first tries to find a record with the given fields, an > **Why use this when the database can verify uniqueness with a UNIQUE INDEX database constraint?** > > You may be in a situation where you can't have a unique index (supporting a legacy schema, perhaps), but still want to make sure the data is unique before proceeding. There is also the belief that you shouldn't have to count on the database to validate your data—that's a core concern of your business logic, and your business logic should live in your Services in a Redwood app. -> -> Another issue is that the error raised by Prisma when a record validates a unique index is swallowed by GraphQL and so you can't report it to the user (there are still ways around this, but it involves catching and re-throwing a different error). The error raised by `validateUniqueness()` is already safe-listed and allowed to be sent to the browser. +> +> Another issue is that the error raised by Prisma when a record validates a unique index is swallowed by GraphQL and so you can't report it to the user (there are still ways around this, but it involves catching and re-throwing a different error). The error raised by `validateUniqueness()` is already safe-listed and allowed to be sent to the browser. #### Arguments 1. The name of the db table accessor that will be checked (what you would call on `db` in a normal Prisma call). If you'd call `db.user` then this value is `"user"`. 2. An object, containing the db fields/values to check for uniqueness, like `{ email: 'rob@redwoodjs.com' }`. Can also include additional options explained below that provide for a narrower scope for uniqueness requirements, and a way for the record to identify itself and not create a false positive for an existing record. -3. [Optional] An object with options. +3. [Optional] An object with options. `message` - custom error message. `db` - custom instance of the PrismaClient to use 4. Callback to be invoked if record is found to be unique. In its most basic usage, say you want to make sure that a user's email address is unique before creating the record. `input` is an object containing all the user fields to save to the database, including `email` which must be unique: @@ -677,6 +677,21 @@ const createUser = (input) => { } ``` +You can provide the PrismaClient to be used for the transaction and callback. +```jsx +import { db } from 'src/lib/db' + +const createUser = (input) => { + return validateUniqueness('user', + { email: input.email }, + { db }, + (db) => db.user.create({ data: input }) + ) +} +``` + +> If you are overwriting the DATABASE_URL in your `src/lib/db` instantiation of the PrismaClient, you need to use this option. If not provided, a vanilla `new PrismaClient()` is used to run the callback that will not respect any custom configurations not represented in your `prisma.schema` + Be sure that both your callback and the surrounding `validateUniqueness()` function are `return`ed or else your service function will have nothing to return to its consumers, like GraphQL. ##### $self diff --git a/packages/api/src/validations/__tests__/validations.test.js b/packages/api/src/validations/__tests__/validations.test.js index 8442724c2c50..1228d089da67 100644 --- a/packages/api/src/validations/__tests__/validations.test.js +++ b/packages/api/src/validations/__tests__/validations.test.js @@ -1144,4 +1144,34 @@ describe('validateUniqueness', () => { } expect.assertions(1) }) + + it('uses the given prisma client', async () => { + const mockFindFirstOther = jest.fn() + mockFindFirstOther.mockImplementation(() => ({ + id: 2, + email: 'rob@redwoodjs.com', + })) + const mockPrismaClient = { + $transaction: async (func) => + func({ + user: { + findFirst: mockFindFirstOther, + }, + }), + } + + expect(mockFindFirstOther).not.toBeCalled() + + await expect( + validateUniqueness( + 'user', + { email: 'rob@redwoodjs.com' }, + { db: mockPrismaClient }, + () => {} + ) + ).rejects.toThrowError('email must be unique') + + expect(mockFindFirstOther).toBeCalled() + expect(mockFindFirst).not.toBeCalled() + }) }) diff --git a/packages/api/src/validations/validations.ts b/packages/api/src/validations/validations.ts index 328e5de862bc..7c5c8685076c 100644 --- a/packages/api/src/validations/validations.ts +++ b/packages/api/src/validations/validations.ts @@ -143,7 +143,9 @@ interface PresenceValidatorOptions extends WithOptionalMessage { allowEmptyString?: boolean } -interface UniquenessValidatorOptions extends WithRequiredMessage {} +interface UniquenessValidatorOptions extends WithOptionalMessage { + db?: PrismaClient +} type UniquenessWhere = Record<'AND' | 'NOT', Array>> interface ValidationRecipe { @@ -612,6 +614,18 @@ export const validateWith = (func: () => void) => { // }, (db) => { // return db.create(data: { email }) // }) +// +// const myCustomDb = new PrismaClient({ +// log: emitLogLevels(['info', 'warn', 'error']), +// datasources: { +// db: { +// url: process.env.DATABASE_URL, +// }, +// }, +// }) +// return validateUniqueness('user', { email: 'rob@redwoodjs.com' }, { prismaClient: myCustomDb}, (db) => { +// return db.create(data: { email }) +// }) export async function validateUniqueness( model: string, fields: Record, @@ -632,10 +646,10 @@ export async function validateUniqueness( | ((tx: PrismaClient) => Promise), callback?: (tx: PrismaClient) => Promise ): Promise { - const db = new PrismaClient() const { $self, $scope, ...rest } = fields - let options = {} + let options: UniquenessValidatorOptions = {} let validCallback: (tx: PrismaClient) => Promise + let db = null if (typeof optionsOrCallback === 'function') { validCallback = optionsOrCallback @@ -644,6 +658,14 @@ export async function validateUniqueness( validCallback = callback as (tx: PrismaClient) => Promise } + if (options.db) { + const { db: customDb, ...restOptions } = options + options = restOptions + db = customDb + } else { + db = new PrismaClient() + } + const where: UniquenessWhere = { AND: [rest], NOT: [],