Skip to content

Commit

Permalink
Merge branch 'main' of github.com:redwoodjs/redwood into try/setupSce…
Browse files Browse the repository at this point in the history
…nario-multiple-tests

* 'main' of github.com:redwoodjs/redwood: (21 commits)
  fix(deps): update dependency @whatwg-node/server to v0.9.18 (#9602)
  fix(deps): update dependency @apollo/client to v3.8.8 (#9600)
  chore: update yarn.lock
  chore(deps): update dependency @playwright/test to v1.40.1 (#9599)
  chore(deps): update dependency @supabase/supabase-js to v2.39.0 (#9603)
  fix(deps): update dependency @clerk/clerk-sdk-node to v4.12.22 (#9601)
  chore(deps): update dependency @clerk/clerk-react to v4.28.1 (#9598)
  fix(deps): update storybook monorepo to v7.6.2 (#9597)
  RSC: Generate a route manifest (#9592)
  chore(private-set): Wrap profile page in <PrivateSet> instead of Private (#9575)
  add documentation on mocking useParams in component test (#9284)
  Update Typescript to 5.3.2 (#9589)
  RSC: Refactor build process (#9588)
  fix(crwa): clarify docs to avoid issues in yarn modern installs (#9579)
  fix: Prevent `rw graphiql setup` from breaking with encryptSession error (#9582)
  fix: Remove "god code" typos from auth READMEs (#9583)
  Experimental Dockerfile: Fix typo in setup command (#9577)
  fix(streaming-ssr): Fixes running the streaming server using rw serve (#9558)
  9316/update mantine setup (#9388)
  chore: clean up `RouteParams` type test structure (#9573)
  ...
  • Loading branch information
dac09 committed Dec 1, 2023
2 parents 41ea47c + 1e1c8d1 commit e0104f6
Show file tree
Hide file tree
Showing 128 changed files with 1,338 additions and 1,419 deletions.
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ root = true
[*]
end_of_line = lf
insert_final_newline = true
charset = utf8
charset = utf-8

[*.{js,jsx,ts,tsx,graphql,sql,md,html,mjml,json,jsonc,json5,yml,yaml,template,sh,Dockerfile}]
indent_style = space
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
*.icloud
.cosine
.idea
.DS_Store
node_modules
Expand Down
13 changes: 6 additions & 7 deletions docs/docs/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,12 +130,11 @@ const Routes = () => {
<Route path="/login" page={LoginPage} name="login" />

// highlight-start
<Private unauthenticated="login">
{/* Or... <Set private unauthenticated="login"> */}
<PrivateSet unauthenticated="login">
// highlight-end
<Route path="/admin" page={AdminPage} name="admin" />
<Route path="/secret-page" page={SecretPage} name="secret" />
</Private>
<PrivateSet>
</Router>
)
}
Expand All @@ -153,19 +152,19 @@ const Routes = () => {
<Route path="/login" page={LoginPage} name="login" />
<Route path="/forbidden" page={ForbiddenPage} name="forbidden" />

<Private unauthenticated="login">
<PrivateSet unauthenticated="login">
<Route path="/secret-page" page={SecretPage} name="secret" />
</Private>
<PrivateSet>

// highlight-next-line
<Set private unauthenticated="forbidden" hasRole="admin">
<Route path="/admin" page={AdminPage} name="admin" />
</Set>

// highlight-next-line
<Private unauthenticated="forbidden" hasRole={['author', 'editor']}>
<PrivateSet unauthenticated="forbidden" hasRole={['author', 'editor']}>
<Route path="/posts" page={PostsPage} name="posts" />
</Private>
<PrivateSet>
</Router>
)
}
Expand Down
24 changes: 12 additions & 12 deletions docs/docs/how-to/role-based-access-control.md
Original file line number Diff line number Diff line change
Expand Up @@ -238,52 +238,52 @@ export const getCurrentUser = async (decoded) => {
#### How to Protect a Route
To protect a `Private` route for access by a single role:
To protect a `PrivateSet` route for access by a single role:
```jsx
import { Router, Route, Private } from '@redwoodjs/router'
import { Router, Route, PrivateSet } from '@redwoodjs/router'

const Routes = () => {
return (
<Router>
<Private unauthenticated="home" roles="admin">
<PrivateSet unauthenticated="home" roles="admin">
<Route path="/admin/users" page={UsersPage} name="users" />
</Private>
</PrivateSet>
</Router>
)
}
```
To protect a `Private` route for access by a multiple roles:
To protect a `PrivateSet` route for access by a multiple roles:
```jsx
import { Router, Route, Private } from '@redwoodjs/router'
import { Router, Route, PrivateSet } from '@redwoodjs/router'

const Routes = () => {
return (
<Router>
<Private unauthenticated="home" roles={['admin', 'editor', 'publisher']}>
<PrivateSet unauthenticated="home" roles={['admin', 'editor', 'publisher']}>
<Route path="/admin/posts/{id:Int}/edit" page={EditPostPage} name="editPost" />
</Private>
</PrivateSet>
</Router>
)
}
```
> Note: If you are using `Set` you can use its `private` attribute instead of the `<Private>` component.
> Note: If you are using `Set` you can use its `private` attribute instead of the `<PrivateSet>` component.
If the currentUser is not assigned the role, they will be redirected to the page specified in the `unauthenticated` property. Therefore, you can define a specific page to be seen when attempting to access the protected route and denied access such as a "forbidden" page:
```jsx
import { Router, Route, Private } from '@redwoodjs/router'
import { Router, Route, PrivateSet } from '@redwoodjs/router'

const Routes = () => {
return (
<Router>
<Private unauthenticated="forbidden" roles="admin">
<PrivateSet unauthenticated="forbidden" roles="admin">
<Route path="/settings" page={SettingsPage} name="settings" />
<Route path="/admin" page={AdminPage} name="sites" />
</Private>
</PrivateSet>

<Route notfound page={NotFoundPage} />
<Route path="/forbidden" page={ForbiddenPage} name="forbidden" />
Expand Down
6 changes: 3 additions & 3 deletions docs/docs/prerender.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,16 +56,16 @@ This will prerender your NotFoundPage to `404.html` in your dist folder. Note th
For Private Routes, Redwood prerenders your Private Routes' `whileLoadingAuth` prop:

```jsx
<Private >
<PrivateSet>
// Loading is shown while we're checking to see if the user's logged in
<Route path="/super-secret-admin-dashboard" page={SuperSecretAdminDashboard} name="ssad" whileLoadingAuth={() => <Loading />} prerender/>
</Private>
</PrivateSet>
```

### Rendering skeletons while authenticating
Sometimes you want to render the shell of the page, while you wait for your authentication checks to happen. This can make the experience feel a lot snappier to the user, since they don't wait on a blank screen while their credentials are checked.

To do this, make use of the `whileLoadingAuth` prop on `<Private>` or a `<Set private>` in your Routes file. For example, if we have a dashboard that you need to be logged in to access:
To do this, make use of the `whileLoadingAuth` prop on `<PrivateSet>` in your Routes file. For example, if we have a dashboard that you need to be logged in to access:

```js ./web/src/Routes.{tsx,js}
// This renders the layout with skeleton loaders in the content area
Expand Down
24 changes: 11 additions & 13 deletions docs/docs/router.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,6 @@ The `path` prop specifies the URL path to match, starting with the beginning sla

Some pages should only be visible to authenticated users.

We support this using private `<Set>`s or the `<Private>` component. Read more [further down](#private-set).

## Sets of Routes

You can group Routes into sets using the `Set` component. `Set` allows you to wrap a set of Routes in another component or array of components—usually a Context, a Layout, or both:
Expand Down Expand Up @@ -145,16 +143,16 @@ Here's an example of how you'd use a private set:
</Router>
```

Private routes are important and should be easy to spot in your Routes file. The larger your Routes file gets, the more difficult it will probably become to find `<Set private /*...*/>` among your other Sets. So we also provide a `<Private>` component that's just an alias for `<Set private /*...*/>`. Most of our documentation uses `<Private>`.
Private routes are important and should be easy to spot in your Routes file. The larger your Routes file gets, the more difficult it will probably become to find `<Set private /*...*/>` among your other Sets. So we also provide a `<PrivateSet>` component that's just an alias for `<Set private /*...*/>`. Most of our documentation uses `<PrivateSet>`.

Here's the same example again, but now using `<Private>`
Here's the same example again, but now using `<PrivateSet>`

```jsx title="Routes.js"
<Router>
<Route path="/" page={HomePage} name="home" />
<Private unauthenticated="home">
<PrivateSet unauthenticated="home">
<Route path="/admin" page={AdminPage} name="admin" />
</Private>
<PrivateSet>
</Router>
```

Expand All @@ -164,9 +162,9 @@ To protect `Private` routes for access by a single role:

```jsx title="Routes.js"
<Router>
<Private unauthenticated="forbidden" roles="admin">
<PrivateSet unauthenticated="forbidden" roles="admin">
<Route path="/admin/users" page={UsersPage} name="users" />
</Private>
<PrivateSet>

<Route path="/forbidden" page={ForbiddenPage} name="forbidden" />
</Router>
Expand All @@ -176,9 +174,9 @@ To protect `Private` routes for access by multiple roles:

```jsx title="Routes.js"
<Router>
<Private unauthenticated="forbidden" roles={['admin', 'editor', 'publisher']}>
<PrivateSet unauthenticated="forbidden" roles={['admin', 'editor', 'publisher']}>
<Route path="/admin/posts/{id:Int}/edit" page={EditPostPage} name="editPost" />
</Private>
<PrivateSet>

<Route path="/forbidden" page={ForbiddenPage} name="forbidden" />
</Router>
Expand Down Expand Up @@ -574,13 +572,13 @@ When the lazy-loaded page is loading, `PageLoadingContext.Consumer` will pass `{

Let's say you have a dashboard area on your Redwood app, which can only be accessed after logging in. When Redwood Router renders your private page, it will first fetch the user's details, and only render the page if it determines the user is indeed logged in.

In order to display a loader while auth details are being retrieved you can add the `whileLoadingAuth` prop to your private `<Route>`, `<Set private>` or the `<Private>` component:
In order to display a loader while auth details are being retrieved you can add the `whileLoadingAuth` prop to your private `<Route>`, `<Set private>` or the `<PrivateSet>` component:

```jsx
//Routes.js

<Router>
<Private
<PrivateSet
wrap={DashboardLayout}
unauthenticated="login"
whileLoadingAuth={SkeletonLoader} //<-- auth loader
Expand All @@ -590,7 +588,7 @@ In order to display a loader while auth details are being retrieved you can add
<Route path="/dashboard" page={DashboardHomePage} name="dashboard" />

{/* other routes */}
</Private>
<PrivateSet>
</Router>
```

Expand Down
17 changes: 17 additions & 0 deletions docs/docs/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,7 @@ render(<Article article={ title: 'Foobar' } />, {
})
```
:::

### Mocking useLocation

To mock `useLocation` in your component tests, wrap the component with `LocationProvider`:
Expand All @@ -288,6 +289,22 @@ render(
)
```

### Mocking useParams

To mock `useParams` in your component tests, wrap the component with `ParamsProvider`:

```jsx
import { ParamsProvider } from '@redwoodjs/router';

render(
<ParamsProvider allParams={{ param1: 'val1', param2: 'val2' }}>
<Component />
</ParamsProvider>
)
```

The `allParams` argument accepts an object that will provide parameters as you expect them from the query parameters of a URL string. In the above example, we are assuming the URL looks like `/?param1=val1&param2=val2`.

### Queries

In most cases you will want to exclude the design elements and structure of your components from your test. Then you're free to redesign the component all you want without also having to make the same changes to your test suite. Let's look at some of the functions that React Testing Library provides (they call them "[queries](https://testing-library.com/docs/queries/about/)") that let you check for *parts* of the rendered component, rather than a full string match.
Expand Down
8 changes: 4 additions & 4 deletions docs/docs/tutorial/chapter0/what-is-redwood.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ const Routes = () => {
<Set wrap={ApplicationLayout}>
<Route path="/login" page={LoginPage} name="login" />
<Route path="/signup" page={SignupPage} name="signup" />
<Private unauthenticated="login">
<PrivateSet unauthenticated="login">
<Route path="/dashboard" page={DashboardPage} name="dashboard" />
<Route path="/products/{sku}" page={ProductsPage} name="products" />
</Private>
</PrivateSet>
</Set>

<Route path="/" page={HomePage} name="home" />
Expand All @@ -54,7 +54,7 @@ const Routes = () => {
}
```

You can probably get a sense of how all of this works without ever having seen a Redwood route before! Some routes can be marked as `<Private>` and will not be accessible without being logged in. Others can be wrapped in a "layout" (again, just a React component) to provide common styling shared between pages in your app.
You can probably get a sense of how all of this works without ever having seen a Redwood route before! Some routes can be marked as `<PrivateSet>` and will not be accessible without being logged in. Others can be wrapped in a "layout" (again, just a React component) to provide common styling shared between pages in your app.

#### Prerender

Expand All @@ -66,7 +66,7 @@ This is Redwood's version of static site generation, aka SSG.

### Authentication

The `<Private>` route limits access to users that are authenticated, but how do they authenticate? Redwood includes integrations to many popular third party authentication hosts (including [Auth0](https://auth0.com/), [Supabase](https://supabase.com/docs/guides/auth) and [Clerk](https://clerk.com/)). You can also [host your own auth](https://redwoodjs.com/docs/auth/dbauth), or write your own [custom authentication](https://redwoodjs.com/docs/auth/custom) option. If going self-hosted, we include login, signup, and reset password pages, as well as the option to include TouchID/FaceID and third party biometric readers!
The `<PrivateSet>` route limits access to users that are authenticated, but how do they authenticate? Redwood includes integrations to many popular third party authentication hosts (including [Auth0](https://auth0.com/), [Supabase](https://supabase.com/docs/guides/auth) and [Clerk](https://clerk.com/)). You can also [host your own auth](https://redwoodjs.com/docs/auth/dbauth), or write your own [custom authentication](https://redwoodjs.com/docs/auth/custom) option. If going self-hosted, we include login, signup, and reset password pages, as well as the option to include TouchID/FaceID and third party biometric readers!

Once authenticated, how do you know what a user is allowed to do or not do? Redwood includes helpers for [role-based access control](https://redwoodjs.com/docs/how-to/role-based-access-control-rbac) that integrates on both the front- and backend.

Expand Down
15 changes: 11 additions & 4 deletions docs/docs/tutorial/chapter1/prerequisites.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,21 @@ Please do upgrade accordingly. Then proceed to the Redwood installation when you

There are many ways to install and manage both Node.js and Yarn. If you're installing for the first time, we recommend the following:

**1. Yarn**
We recommend following the [instructions via Yarnpkg.com](https://yarnpkg.com/getting-started/install).

**2. Node.js**
**1. Node.js**
Using the recommended [LTS version from Nodejs.org](https://nodejs.org/en/) is preferred.

- `nvm` is a great tool for managing multiple versions of Node on one system. It takes a bit more effort to set up and learn, however. Follow the [nvm installation instructions](https://github.com/nvm-sh/nvm#installing-and-updating). (Windows users should go to [nvm-windows](https://github.com/coreybutler/nvm-windows/releases)). For **Mac** users with Homebrew installed, you can alternatively use it to [install `nvm`](https://formulae.brew.sh/formula/nvm). Or, refer to our how to guide [using nvm](../../how-to/using-nvm.md).

**2. Yarn**
As of Node.js v18+, Node.js ships with a CLI tool called [Corepack](https://nodejs.org/docs/latest-v18.x/api/corepack.html) to manage package managers. All you have to do is enable it, then you'll have Yarn:

```
corepack enable
yarn -v
```

The version of Yarn will probably be `1.22.21`, but don't worry—in your Redwood project, Corepack will know to use a modern version of Yarn because of the `packageManager` field in the root `package.json`.

**Windows:** Recommended Development Setup

- JavaScript development on Windows has specific requirements in addition to Yarn and npm. Follow our simple setup guide:
Expand Down
16 changes: 8 additions & 8 deletions docs/docs/tutorial/chapter4/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ Try reloading the Posts admin and we'll see something that's 50% correct:
![image](https://user-images.githubusercontent.com/300/146462761-d21c93f0-289a-4e11-bccf-8e4e68f21438.png)
Going to the admin section now prevents a non-logged in user from seeing posts, great! This is the result of the `@requireAuth` directive in `api/src/graphql/posts.sdl.{js,ts}`: you're not authenticated so GraphQL will not respond to your request for data. But, ideally they wouldn't be able to see the admin pages themselves. Let's fix that with a new component in the Routes file, `<Private>`:
Going to the admin section now prevents a non-logged in user from seeing posts, great! This is the result of the `@requireAuth` directive in `api/src/graphql/posts.sdl.{js,ts}`: you're not authenticated so GraphQL will not respond to your request for data. But, ideally they wouldn't be able to see the admin pages themselves. Let's fix that with a new component in the Routes file, `</PrivateSet>`:
<Tabs groupId="js-ts">
<TabItem value="js" label="JavaScript">
Expand All @@ -213,15 +213,15 @@ const Routes = () => {
return (
<Router useAuth={useAuth}>
// highlight-next-line
<Private unauthenticated="home">
</PrivateSet unauthenticated="home">
<Set wrap={ScaffoldLayout} title="Posts" titleTo="posts" buttonLabel="New Post" buttonTo="newPost">
<Route path="/admin/posts/new" page={PostNewPostPage} name="newPost" />
<Route path="/admin/posts/{id:Int}/edit" page={PostEditPostPage} name="editPost" />
<Route path="/admin/posts/{id:Int}" page={PostPostPage} name="post" />
<Route path="/admin/posts" page={PostPostsPage} name="posts" />
</Set>
// highlight-next-line
</Private>
</PrivateSet>
<Set wrap={BlogLayout}>
<Route path="/article/{id:Int}" page={ArticlePage} name="article" />
<Route path="/contact" page={ContactPage} name="contact" />
Expand Down Expand Up @@ -252,15 +252,15 @@ const Routes = () => {
return (
<Router useAuth={useAuth}>
// highlight-next-line
<Private unauthenticated="home">
</PrivateSet unauthenticated="home">
<Set wrap={ScaffoldLayout} title="Posts" titleTo="posts" buttonLabel="New Post" buttonTo="newPost">
<Route path="/admin/posts/new" page={PostNewPostPage} name="newPost" />
<Route path="/admin/posts/{id:Int}/edit" page={PostEditPostPage} name="editPost" />
<Route path="/admin/posts/{id:Int}" page={PostPostPage} name="post" />
<Route path="/admin/posts" page={PostPostsPage} name="posts" />
</Set>
// highlight-next-line
</Private>
</PrivateSet>
<Set wrap={BlogLayout}>
<Route path="/article/{id:Int}" page={ArticlePage} name="article" />
<Route path="/contact" page={ContactPage} name="contact" />
Expand All @@ -278,7 +278,7 @@ export default Routes
</TabItem>
</Tabs>
We wrap the routes we want to be private (that is, only accessible when logged in) in the `<Private>` component, and tell our app where to send them if they are unauthenticated. In this case they should go to the `home` route.
We wrap the routes we want to be private (that is, only accessible when logged in) in the `</PrivateSet>` component, and tell our app where to send them if they are unauthenticated. In this case they should go to the `home` route.
Try going back to [http://localhost:8910/admin/posts](http://localhost:8910/admin/posts) now and—yikes!
Expand All @@ -288,7 +288,7 @@ Well, we couldn't get to the admin pages, but we also can't see our blog posts a
It's because the `posts` query in `posts.sdl.{js,ts}` is used by both the homepage *and* the posts admin page. Since it has the `@requireAuth` directive, it's locked down and can only be accessed when logged in. But we *do* want people that aren't logged in to be able to view the posts on the homepage!
Now that our admin pages are behind a `<Private>` route, what if we set the `posts` query to be `@skipAuth` instead? Let's try:
Now that our admin pages are behind a `</PrivateSet>` route, what if we set the `posts` query to be `@skipAuth` instead? Let's try:
<Tabs groupId="js-ts">
<TabItem value="js" label="JavaScript">
Expand Down Expand Up @@ -888,7 +888,7 @@ You can generate a new value with the `yarn rw g secret` command. It only output
## Wrapping Up
Believe it or not, that's pretty much it for authentication! You can use the combination of `@requireAuth` and `@skipAuth` directives to lock down access to GraphQL query/mutations, and the `<Private>` component to restrict access to entire pages of your app. If you only want to restrict access to certain components, or certain parts of a component, you can always get `isAuthenticated` from the `useAuth()` hook and then render one thing or another.
Believe it or not, that's pretty much it for authentication! You can use the combination of `@requireAuth` and `@skipAuth` directives to lock down access to GraphQL query/mutations, and the `</PrivateSet>` component to restrict access to entire pages of your app. If you only want to restrict access to certain components, or certain parts of a component, you can always get `isAuthenticated` from the `useAuth()` hook and then render one thing or another.
Head over to the Redwood docs to read more about [self-hosted](../../auth/dbauth.md) and [third-party authentication](../../authentication.md#official-integrations).
Expand Down
2 changes: 1 addition & 1 deletion docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,6 @@
"devDependencies": {
"@docusaurus/module-type-aliases": "3.0.0",
"@docusaurus/tsconfig": "3.0.0",
"typescript": "5.2.2"
"typescript": "5.3.2"
}
}
Loading

0 comments on commit e0104f6

Please sign in to comment.