diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index 9f59a16a..2d5235fa 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -14,6 +14,7 @@ export default defineConfig({ lastUpdated: true, head: headConfig, sitemap: sitemapConfig, + ignoreDeadLinks: 'localhostLinks', themeConfig: { logo: '/lock.png', outline: { level: 'deep' }, diff --git a/docs/.vitepress/routes/navbar.ts b/docs/.vitepress/routes/navbar.ts index 3d88aeb0..fd2f0cef 100644 --- a/docs/.vitepress/routes/navbar.ts +++ b/docs/.vitepress/routes/navbar.ts @@ -18,8 +18,6 @@ export const routes: DefaultTheme.Config['nav'] = [ }, ], }, - // TODO: Add full API docs - // { text: 'API', link: '/api/overview' }, { text: 'Resources', items: [ @@ -27,6 +25,10 @@ export const routes: DefaultTheme.Config['nav'] = [ text: 'Overview', link: '/resources/overview', }, + { + text: 'Recipes', + link: '/recipes/introduction/welcome', + }, { text: 'Security', link: '/resources/security', diff --git a/docs/.vitepress/routes/sidebar.ts b/docs/.vitepress/routes/sidebar.ts deleted file mode 100644 index 02762323..00000000 --- a/docs/.vitepress/routes/sidebar.ts +++ /dev/null @@ -1,103 +0,0 @@ -import type { DefaultTheme } from 'vitepress' - -export const routes: DefaultTheme.Config['sidebar'] = { - '/guide': [ - { - text: 'Getting started', - base: '/guide/getting-started', - items: [ - { - text: 'Introduction', - link: '/introduction', - }, - { - text: 'Installation', - link: '/installation', - }, - { - text: 'Choosing the provider', - link: '/choose-provider', - }, - ], - }, - { - text: 'Application side', - base: '/guide/application-side', - items: [ - { - text: 'Configuration', - link: '/configuration', - }, - { - text: 'Session access', - link: '/session-access', - }, - { - text: 'Protecting pages', - link: '/protecting-pages', - }, - ], - }, - { - text: 'AuthJS Provider', - base: '/guide/authjs', - items: [ - { - text: 'Quick Start', - link: '/quick-start', - }, - { - text: 'NuxtAuthHandler', - link: '/nuxt-auth-handler', - }, - { - text: 'Custom pages', - link: '/custom-pages', - }, - { - text: 'Session data', - link: '/session-data', - }, - { - text: 'Server side', - collapsed: true, - items: [ - { text: 'Session access', link: '/server-side/session-access' }, - { text: 'JWT access', link: '/server-side/jwt-access' }, - { text: 'Rest API', link: '/server-side/rest-api' }, - ], - }, - ], - }, - { - text: 'Local / Refresh Provider', - base: '/guide/local', - items: [ - { - text: 'Quick Start', - link: '/quick-start', - }, - { - text: 'Session data', - link: '/session-data', - } - ], - }, - { - text: 'Advanced', - base: '/guide/advanced', - items: [ - { - text: 'Deployment', - collapsed: true, - items: [ - { text: 'Self-hosted', link: '/deployment/self-hosted' }, - { text: 'Vercel', link: '/deployment/vercel' }, - { text: 'Netlify', link: '/deployment/netlify' }, - ], - }, - { text: 'Caching', link: '/caching' }, - ], - }, - ], -} diff --git a/docs/.vitepress/routes/sidebar/guide.ts b/docs/.vitepress/routes/sidebar/guide.ts new file mode 100644 index 00000000..ce07216b --- /dev/null +++ b/docs/.vitepress/routes/sidebar/guide.ts @@ -0,0 +1,101 @@ +import type { DefaultTheme } from 'vitepress' + +export const routes: DefaultTheme.SidebarItem[] = [ + { + text: 'Getting started', + base: '/guide/getting-started', + items: [ + { + text: 'Introduction', + link: '/introduction', + }, + { + text: 'Installation', + link: '/installation', + }, + { + text: 'Choosing the provider', + link: '/choose-provider', + }, + ], + }, + { + text: 'Application side', + base: '/guide/application-side', + items: [ + { + text: 'Configuration', + link: '/configuration', + }, + { + text: 'Session access', + link: '/session-access', + }, + { + text: 'Protecting pages', + link: '/protecting-pages', + }, + ], + }, + { + text: 'AuthJS Provider', + base: '/guide/authjs', + items: [ + { + text: 'Quick Start', + link: '/quick-start', + }, + { + text: 'NuxtAuthHandler', + link: '/nuxt-auth-handler', + }, + { + text: 'Custom pages', + link: '/custom-pages', + }, + { + text: 'Session data', + link: '/session-data', + }, + { + text: 'Server side', + collapsed: true, + items: [ + { text: 'Session access', link: '/server-side/session-access' }, + { text: 'JWT access', link: '/server-side/jwt-access' }, + { text: 'Rest API', link: '/server-side/rest-api' }, + ], + }, + ], + }, + { + text: 'Local / Refresh Provider', + base: '/guide/local', + items: [ + { + text: 'Quick Start', + link: '/quick-start', + }, + { + text: 'Session data', + link: '/session-data', + } + ], + }, + { + text: 'Advanced', + base: '/guide/advanced', + items: [ + { + text: 'Deployment', + collapsed: true, + items: [ + { text: 'Self-hosted', link: '/deployment/self-hosted' }, + { text: 'Vercel', link: '/deployment/vercel' }, + { text: 'Netlify', link: '/deployment/netlify' }, + ], + }, + { text: 'Caching', link: '/caching' }, + ], + }, +] diff --git a/docs/.vitepress/routes/sidebar/index.ts b/docs/.vitepress/routes/sidebar/index.ts new file mode 100644 index 00000000..91240e73 --- /dev/null +++ b/docs/.vitepress/routes/sidebar/index.ts @@ -0,0 +1,9 @@ +import type { DefaultTheme } from 'vitepress' + +import { routes as guideRoutes } from './guide' +import { routes as recipesRoutes } from './recipes' + +export const routes: DefaultTheme.Config['sidebar'] = { + '/guide': guideRoutes, + '/recipes': recipesRoutes +} diff --git a/docs/.vitepress/routes/sidebar/recipes.ts b/docs/.vitepress/routes/sidebar/recipes.ts new file mode 100644 index 00000000..e812c531 --- /dev/null +++ b/docs/.vitepress/routes/sidebar/recipes.ts @@ -0,0 +1,46 @@ +import type { DefaultTheme } from 'vitepress' + +export const routes: DefaultTheme.SidebarItem[] = [ + { + text: 'Introduction', + base: '/recipes/introduction', + items: [ + { + text: 'Welcome', + link: '/welcome', + }, + { + text: 'Adding your recipe', + link: '/adding-your-recipe', + } + ], + }, + { + text: 'Official', + base: '/recipes/official', + items: [ + { + text: 'Mocking with Vitest', + link: '/mocking-with-vitest', + }, + ], + }, + { + text: 'Community', + base: '/recipes/community', + items: [ + { + text: 'Strapi', + link: '/strapi' + }, + { + text: 'Directus', + link: '/directus', + }, + { + text: 'Laravel Passport', + link: '/laravel-passport' + } + ], + }, +] diff --git a/docs/.vitepress/theme/Layout.vue b/docs/.vitepress/theme/components/Layout.vue similarity index 84% rename from docs/.vitepress/theme/Layout.vue rename to docs/.vitepress/theme/components/Layout.vue index faa7cde6..7837779b 100644 --- a/docs/.vitepress/theme/Layout.vue +++ b/docs/.vitepress/theme/components/Layout.vue @@ -1,7 +1,7 @@ + + + + diff --git a/docs/.vitepress/theme/components/Tag.vue b/docs/.vitepress/theme/components/Tag.vue new file mode 100644 index 00000000..d171d941 --- /dev/null +++ b/docs/.vitepress/theme/components/Tag.vue @@ -0,0 +1,40 @@ + + + + + diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts index 1f2d9350..337afb03 100644 --- a/docs/.vitepress/theme/index.ts +++ b/docs/.vitepress/theme/index.ts @@ -1,9 +1,18 @@ // https://vitepress.dev/guide/custom-theme import type { Theme } from 'vitepress' import DefaultTheme from 'vitepress/theme' -import Layout from './Layout.vue' +import Layout from './components/Layout.vue' // Styles import './style.css' -export default { extends: DefaultTheme, Layout } satisfies Theme +// Components +import RecipeHeader from './components/RecipeHeader.vue' + +export default { + extends: DefaultTheme, + Layout, + enhanceApp({ app }) { + app.component('RecipeHeader', RecipeHeader) + } +} satisfies Theme diff --git a/docs/recipes/community/directus.md b/docs/recipes/community/directus.md new file mode 100644 index 00000000..bfde8f06 --- /dev/null +++ b/docs/recipes/community/directus.md @@ -0,0 +1,203 @@ +# Directus + Provider `authjs` + + + +This section gives an example of how the `NuxtAuthHandler` can be configured to use Directus JWTs for authentication via the `CredentialsProvider` provider and how to implement a token refresh for the Directus JWT. + +The below is a code-example that needs to be adapted to your specific configuration: +```ts +import CredentialsProvider from "next-auth/providers/credentials"; +import { NuxtAuthHandler } from "#auth"; + +/** + * Takes a token, and returns a new token with updated + * `accessToken` and `accessTokenExpires`. If an error occurs, + * returns the old token and an error property + */ +async function refreshAccessToken(refreshToken: { + accessToken: string; + accessTokenExpires: string; + refreshToken: string; +}) { + try { + console.warn("trying to post to refresh token"); + + const refreshedTokens = await $fetch<{ + data: { + access_token: string; + expires: number; + refresh_token: string; + }; + } | null>("https://domain.directus.app/auth/refresh", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: { + refresh_token: refreshToken.refreshToken, + mode: "json", + }, + }); + + if (!refreshedTokens || !refreshedTokens.data) { + console.warn("No refreshed tokens"); + throw refreshedTokens; + } + + console.warn("Refreshed tokens successfully"); + return { + ...refreshToken, + accessToken: refreshedTokens.data.access_token, + accessTokenExpires: Date.now() + refreshedTokens.data.expires, + refreshToken: refreshedTokens.data.refresh_token, + }; + } catch (error) { + console.warn("Error refreshing token", error); + return { + ...refreshToken, + error: "RefreshAccessTokenError", + }; + } +} + +export default NuxtAuthHandler({ + // secret needed to run nuxt-auth in production mode (used to encrypt data) + secret: process.env.NUXT_SECRET, + + providers: [ + // @ts-expect-error You need to use .default here for it to work during SSR. May be fixed via Vite at some point + CredentialsProvider.default({ + // The name to display on the sign in form (e.g. 'Sign in with...') + name: "Credentials", + // The credentials is used to generate a suitable form on the sign in page. + // You can specify whatever fields you are expecting to be submitted. + // e.g. domain, username, password, 2FA token, etc. + // You can pass any HTML attribute to the tag through the object. + credentials: { + email: { label: "Email", type: "text" }, + password: { label: "Password", type: "password" }, + }, + async authorize(credentials: any) { + // You need to provide your own logic here that takes the credentials + // submitted and returns either a object representing a user or value + // that is false/null if the credentials are invalid. + // NOTE: THE BELOW LOGIC IS NOT SAFE OR PROPER FOR AUTHENTICATION! + + try { + const payload = { + email: credentials.email, + password: credentials.password, + }; + + const userTokens = await $fetch<{ + data: { access_token: string; expires: number; refresh_token: string }; + } | null>("https://domain.directus.app/auth/login", { + method: "POST", + body: payload, + headers: { + "Content-Type": "application/json", + "Accept-Language": "en-US", + }, + }); + + const userDetails = await $fetch<{ + data: { + id: string; + email: string; + first_name: string; + last_name: string; + role: string; + phone?: string; + cvr?: string; + company_name?: string; + }; + } | null>("https://domain.directus.app/users/me", { + method: "GET", + headers: { + "Content-Type": "application/json", + "Accept-Language": "en-US", + Authorization: `Bearer ${userTokens?.data?.access_token}`, + }, + }); + + if (!userTokens || !userTokens.data || !userDetails || !userDetails.data) { + throw createError({ + statusCode: 500, + statusMessage: "Next auth failed", + }); + } + + const user = { + id: userDetails.data.id, + email: userDetails.data.email, + firstName: userDetails.data.first_name, + lastName: userDetails.data.last_name, + role: userDetails.data.role, + phone: userDetails.data.phone, + cvr: userDetails.data.cvr, + companyName: userDetails.data.company_name, + accessToken: userTokens.data.access_token, + accessTokenExpires: Date.now() + userTokens.data.expires, + refreshToken: userTokens.data.refresh_token, + }; + + const allowedRoles = [ + "53ed3a6a-b236-49aa-be72-f26e6e4857a0", + "d9b59a92-e85d-43e2-8062-7a1242a8fce6", + ]; + + // Only allow admins and sales + if (!allowedRoles.includes(user.role)) { + throw createError({ + statusCode: 403, + statusMessage: "Not allowed", + }); + } + + return user; + } catch (error) { + console.warn("Error logging in", error); + + return null; + } + }, + }), + ], + + session: { + strategy: "jwt", + }, + + callbacks: { + async jwt({ token, user, account }) { + if (account && user) { + console.warn("JWT callback", { token, user, account }); + return { + ...token, + ...user, + }; + } + + // Handle token refresh before it expires of 15 minutes + if (token.accessTokenExpires && Date.now() > token.accessTokenExpires) { + console.warn("Token is expired. Getting a new"); + return refreshAccessToken(token); + } + + return token; + }, + async session({ session, token }) { + session.user = { + ...session.user, + ...token, + }; + + return session; + }, + }, +}); +``` + +This was contributes by [@madsh93 from Github](https://github.com/madsh93) here: +- Github Comment: https://github.com/sidebase/nuxt-auth/v0.6/issues/64#issuecomment-1330308402 +- Gist: https://gist.github.com/madsh93/b573b3d8f070e62eaebc5c53ae34e2cc diff --git a/docs/recipes/community/laravel-passport.md b/docs/recipes/community/laravel-passport.md new file mode 100644 index 00000000..7f9a4127 --- /dev/null +++ b/docs/recipes/community/laravel-passport.md @@ -0,0 +1,115 @@ +# Laravel Passport + Provider `authjs` + + + +This section gives an example of how the `NuxtAuthHandler` can be configured to use Laravel Passport Oauth2 and SSO. + +You can refer to the official [Laravel documentation](https://laravel.com/docs/10.x/passport#managing-clients) to add new client to Passport. + +By default, you can simply create one using the command: + +```sh +php artisan passport:client +``` + +It will ask you to choose a +- `client ID`, and +- a `redirect URI`. + +Keep the client ID for the next step and set the redirect URI to `http://localhost:3000/api/auth/callback/laravelpassport` (default value for dev environement, modify it according to your environement, you can add several URI comma separated). + +## 2. Add a Laravel API route returning the user data + +Next create a route that is returned to the user. In the example given here, we will use `/api/v1/me`. + +The route will return the field of your user data. You **must** return a field with the key `id`. + +## 3. Setting configuration and the provider + +### 3.1. Storing the config in your .env + +You can add the following variables to your .env: +- `PASSPORT_BASE_URL`: the URL of your passport APP +- `PASSPORT_CLIENT_ID`: the client ID you set in the previous step +- `PASSPORT_CLIENT_SECRET`: the client secret Laravel generated for you at the end of step 1 + +```bash +# .env +PASSPORT_BASE_URL=http://www.my_passport_app.test +PASSPORT_CLIENT_ID=123456789 +PASSPORT_CLIENT_SECRET=123456789 +``` + +### 3.2. Adding your config to the runtimeConfig + +Then add these values to your runtimeConfig: + +```ts +// ~/nuxt.config.ts +export default defineNuxtConfig({ + //... + modules: [ + //... + '@sidebase/nuxt-auth', + ], + runtimeConfig: { + //... + passport: { + baseUrl: process.env.PASSPORT_BASE_URL, + clientId: process.env.PASSPORT_CLIENT_ID, + clientSecret: process.env.PASSPORT_CLIENT_SECRET, + } + + }, +}); +``` + +### 2.3. Create the catch-all `NuxtAuthHandler` and add the this custom provider: + +```ts +// ~/server/api/auth/[...].ts +import { NuxtAuthHandler } from '#auth' +const { passport } = useRuntimeConfig(); //get the values from the runtimeConfig + +export default NuxtAuthHandler({ + //... + providers: [ + { + id: "laravelpassport", //ID is only used for the callback URL + name: "Passport", // name is used for the login button + type: "oauth", // connexion type + version: "2.0",// oauth version + authorization: { + url: `${passport.baseUrl}/oauth/authorize`, // this is the route created by passport by default to get the autorization code + params: { + scope: "*", // this is the wildcard for all scopes in laravel passport, you can specify scopes separated by a space + } + }, + token: { + url: `${passport.baseUrl}/oauth/token`, // this is the default route created by passport to get and renew the tokens + }, + clientId: passport.clientId, // the client Id + clientSecret: passport.clientSecret,// the client secret + userinfo: { + url: `${passport.baseUrl}/api/v1/me`,// this is a custom route that must return the current user that must be created in laravel + }, + profile: (profile) => { + // map the session fields with you laravel fields + // profile is the user coming from the laravel app + // update the return with your own fields names + return { + id: profile.id, + name: profile.username, + email: profile.email, + image: profile.image, + }; + }, + idToken: false, + } + ], +}); +``` + +:::tip Learn more +You can find the full discussion in the issue [#149](https://github.com/sidebase/nuxt-auth/v0.6/issues/149). Solution provided by [@Jericho1060](https://github.com/Jericho1060) +::: diff --git a/docs/recipes/community/strapi.md b/docs/recipes/community/strapi.md new file mode 100644 index 00000000..39d18d4b --- /dev/null +++ b/docs/recipes/community/strapi.md @@ -0,0 +1,80 @@ +# Strapi + Provider `authjs` + + + +This section gives an example of how the `NuxtAuthHandler` can be configured to use Strapi JWTs for authentication via the `CredentialsProvider` provider. + +You have to configure the following places to make `nuxt-auth` work with Strapi: +- `STRAPI_BASE_URL` in `.env`: Add the Strapi environment variable to your .env file +- [`runtimeConfig.STRAPI_BASE_URL`-key in `nuxt.config.ts`](https://nuxt.com/docs/guide/going-further/runtime-config): Add the Strapi base url env variable to the runtime config +- [`auth`-key in `nuxt.config.ts`](/guide/application-side/configuration): Configure the module itself, e.g., where the auth-endpoints are, what origin the app is deployed to, ... +- [NuxtAuthHandler](/guide/authjs/nuxt-auth-handler): Configure the authentication behavior, e.g., what authentication providers to use + +For a production deployment, you will have to at least set the: +- `STRAPI_BASE_URL` Strapi base URL for all API endpoints by default http://localhost:1337 + +1. Create a `.env` file with the following lines: +``` +// Strapi v4 url, out of the box +AUTH_ORIGIN=http://localhost:3000 +NUXT_SECRET=a-not-so-good-secret +STRAPI_BASE_URL=http://localhost:1337/api +``` + +1. Set the following options in your `nuxt.config.ts`: +```ts +export default defineNuxtConfig({ + runtimeConfig: { + // The private keys which are only available server-side + NUXT_SECRET: process.env.NUXT_SECRET, + // Default http://localhost:1337/api + STRAPI_BASE_URL: process.env.STRAPI_BASE_URL, + }, +}); +``` + +1. Create the catch-all `NuxtAuthHandler` and add the this custom Strapi credentials provider: +```ts +// file: ~/server/api/auth/[...].ts +import CredentialsProvider from "next-auth/providers/credentials"; +import { NuxtAuthHandler } from "#auth"; +const config = useRuntimeConfig() + +export default NuxtAuthHandler({ + secret: config.NUXT_SECRET, + providers: [ + // @ts-expect-error You need to use .default here for it to work during SSR. May be fixed via Vite at some point + CredentialsProvider.default({ + name: "Credentials", + credentials: {}, // Object is required but can be left empty. + async authorize(credentials: any) { + const response = await $fetch( + `${config.STRAPI_BASE_URL}/auth/local/`, + { + method: "POST", + body: JSON.stringify({ + identifier: credentials.username, + password: credentials.password, + }), + } + ); + + if (response.user) { + const u = { + id: response.id, + name: response.user.username, + email: response.user.email, + }; + return u; + } else { + return null + } + }, + }), + ] +}); +``` + +:::tip Learn More +Checkout this blog-post for further notes and explanation: https://darweb.nl/foundry/article/nuxt3-sidebase-strapi-user-auth +::: diff --git a/docs/recipes/introduction/adding-your-recipe.md b/docs/recipes/introduction/adding-your-recipe.md new file mode 100644 index 00000000..deba2acb --- /dev/null +++ b/docs/recipes/introduction/adding-your-recipe.md @@ -0,0 +1,16 @@ +# Adding your recipes + +We appreciate anyone willing to share their recipes with the community! Authentication is a complex requirement, individual to every project, therefore the more experiences the community shares with one another the better! + +We are willing to add recipes that come in different media forms. These could be: + +- A blog post +- A step-by-step guide +- A GitHub repository +- _Any other media you prefer!_ + +However, we have noticed that the more detailed a recipe is the more helpful it can be to the community! + +## Submitting your recipe + +If you would like to submit a recipe, please open a Pull request, with your recipe added to `~/docs/recipes/community`. diff --git a/docs/recipes/introduction/welcome.md b/docs/recipes/introduction/welcome.md new file mode 100644 index 00000000..c7debee9 --- /dev/null +++ b/docs/recipes/introduction/welcome.md @@ -0,0 +1,13 @@ +# Recipes + +The following pages contain recipes for commonly asked patterns, questions, and implementations. The recipes are mostly provided by the community and can serve as guidelines to implement something similar in your Nuxt 3 application. + +## What are recipes? + +Recipes are short guides provided by the community and our team that outline how to use `@sidebase/nuxt-auth` in specific use cases required by your application. + +Recipes are not meant to act as official documentation and should be primarily used as inspiration to customize `@sidebase/nuxt-auth` to your needs. + +:::warning +The recipes are not all tested through by the sidebase team. If you have any concerns, questions or improvement proposals or want to contribute a recipe yourself, we'd be very happy if you [open an issue on our repository](https://github.com/sidebase/nuxt-auth/issues/new/choose). +::: diff --git a/docs/recipes/official/mocking-with-vitest.md b/docs/recipes/official/mocking-with-vitest.md new file mode 100644 index 00000000..baad8386 --- /dev/null +++ b/docs/recipes/official/mocking-with-vitest.md @@ -0,0 +1,90 @@ +# Mocking with Vitest + + + +In order to run end-to-end or component tests with Vitest, you will need to create a "mocked" version of the NuxtAuth composables for the test to interact with. In some cases if you are using the `local` or `refresh` provider with a Full-Stack application, you can also directly interact with your authentication API and mock the reponses inside your backend. + +:::tip See the code +You can find the full code for this guide [here](https://github.com/zoey-kaiser/nuxt-auth-recipes/tree/mocking-with-vitest). +::: + +## Add your mocked functions + +Begin by creating a mocked version of the module functions provided by `@sidebase/nuxt-auth` inside `~/tests/mocks/auth.ts`. + +```ts +import { eventHandler } from 'h3' + +export const MOCKED_USER = { + user: { + role: 'admin', + email: 'hi@sidebase.io', + name: 'sidebase' + } +} + +// App-side mocks +export function useAuth() { + return { + data: ref(MOCKED_USER), + status: ref('authenticated'), + getSession: () => MOCKED_USER, + signOut: () => {}, + } +} + +// Server-side mocks +export const getServerSession = () => MOCKED_USER +export const NuxtAuthHandler = () => eventHandler(() => MOCKED_USER) +``` + +Inside this file, you can define any NuxtAuth composable (client-side or server-side) that you need to access inside your tests. Later on when Vitest is running, it will access these functions instead of the built-in ones from NuxtAuth. Therefore you can customize the `MOCKED_USER` to match your session data type. + + +## Setup the mocked module + +Inside of `~/tests/mocks/setup.ts` create a new local Nuxt Module using the mocked functions defined above. + +```ts +import { createResolver, defineNuxtModule } from '@nuxt/kit' + +export default defineNuxtModule({ + setup: (_options, nuxt) => { + const { resolve } = createResolver(import.meta.url) + const pathToMocks = resolve('./auth.ts') + + nuxt.hook('imports:extend', (_imports) => { + _imports.push({ name: 'useAuth', from: pathToMocks }) + }) + + nuxt.hook('nitro:config', (nitroConfig) => { + if (!nitroConfig.alias) { + throw new Error('Alias must exist at this point, otherwise server-side cannot be mocked') + } + nitroConfig.alias['#auth'] = pathToMocks + }) + }, +}) +``` + +## Update your `nuxt.config.ts` + +Inside the `nuxt.config` import either `@sidebase/nuxt-auth` or your mocked version into the `modules` array, depending on the environment + +```ts +// If vitest is running the application, overwrite using the mocked module +const mockAuthModule = process.env.VITEST ? ['./test/mocks/setup.ts'] : [] + +export default defineNuxtConfig({ + modules: [ + '@sidebase/nuxt-auth', + ...mockAuthModule, + ], +} +``` + +That's it! You can now use `@sidebase/nuxt-auth` inside your tests! We decided to not natively include a mocked version of the module, as the configuration of it highly depends on your setup. + +:::tip See the code +You can find the full code for this guide [here](https://github.com/zoey-kaiser/nuxt-auth-recipes/tree/mocking-with-vitest). +::: diff --git a/docs/recipes/overview.md b/docs/recipes/overview.md deleted file mode 100644 index a195a28a..00000000 --- a/docs/recipes/overview.md +++ /dev/null @@ -1,3 +0,0 @@ -# Coming soon - -Oops, this page is not ready yet. You can find our current docs [here](https://sidebase.io). diff --git a/playground-authjs/.env.example b/playground-authjs/.env.example new file mode 100644 index 00000000..84efc8a3 --- /dev/null +++ b/playground-authjs/.env.example @@ -0,0 +1,2 @@ +GITHUB_CLIENT_ID="your-github-client-id" +GITHUB_CLIENT_SECRET="your-github-client-secret" diff --git a/playground-authjs/server/api/auth/[...].ts b/playground-authjs/server/api/auth/[...].ts index e4551f71..cd1c1b36 100644 --- a/playground-authjs/server/api/auth/[...].ts +++ b/playground-authjs/server/api/auth/[...].ts @@ -8,8 +8,8 @@ export default NuxtAuthHandler({ providers: [ // @ts-expect-error You need to use .default here for it to work during SSR. May be fixed via Vite at some point GithubProvider.default({ - clientId: 'your-client-id', - clientSecret: 'your-client-secret' + clientId: process.env.GITHUB_CLIENT_ID ?? 'your-client-id', + clientSecret: process.env.GITHUB_CLIENT_SECRET ?? 'your-client-secret' }), // @ts-expect-error You need to use .default here for it to work during SSR. May be fixed via Vite at some point CredentialsProvider.default({