diff --git a/.github/ISSUE_TEMPLATE/bug-report.yaml b/.github/ISSUE_TEMPLATE/bug-report.yaml new file mode 100644 index 00000000..0236ebf8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.yaml @@ -0,0 +1,40 @@ +name: 🐞 Bug report +description: Create a bug report for this project +labels: [bug] +body: + - type: markdown + attributes: + value: | + Please help us finding and resolving problems. + - type: textarea + id: bug-env + attributes: + label: Environment + description: You can use `npx nuxi info` to fill this section + placeholder: Environment + - type: textarea + id: reproduction + attributes: + label: Reproduction + description: Provide a link to a repo that can reproduce the problem you ran into if you can. + placeholder: Reproduction + - type: textarea + id: bug-description + attributes: + label: Describe the bug + description: A clear and concise description of what the bug is. If you intend to submit a PR for this issue, tell us in the description. Thanks! + placeholder: Bug description + validations: + required: true + - type: textarea + id: additonal + attributes: + label: Additional context + description: If applicable, add any other context about the problem here + - type: textarea + id: logs + attributes: + label: Logs + description: | + Optional if provided reproduction. Please try not to insert an image but copy paste the log text. + render: shell diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..7f137cd5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: true +contact_links: + - name: 📚 nuxt-auth documentation + url: https://sidebase.io/nuxt-auth + about: Check the documentation for usage of nuxt-auth + - name: 💬 Discussions + url: https://github.com/sidebase/nuxt-auth/discussions + about: Use discussions if you have another issue, an idea for improvement or for asking questions. diff --git a/.github/ISSUE_TEMPLATE/feature-request.yaml b/.github/ISSUE_TEMPLATE/feature-request.yaml new file mode 100644 index 00000000..6e196c0e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.yaml @@ -0,0 +1,22 @@ +name: 🚀 Feature request +description: Suggest a feature to improve this project +labels: [enhancement] +body: + - type: markdown + attributes: + value: | + Thank you for taking the time to fill out this feature request of this young, just growing project. It really means a lot to us! ❤️ + - type: textarea + id: feature-description + attributes: + label: Describe the feature + description: A clear and concise description of what you think would be a helpful addition, including the possible use cases and alternatives you have considered. If you have a working prototype or module that implements it, please include a link. + placeholder: Feature description + validations: + required: true + - type: textarea + id: additional-info + attributes: + label: Additional information + description: Additional information that helps us decide how to proceed. + placeholder: Additional information diff --git a/README.md b/README.md index 134f6aaf..2b0e5cc4 100644 --- a/README.md +++ b/README.md @@ -106,7 +106,7 @@ This module also has it's own playground: #### Testing different Providers -We have one playtground per provider: +We have one playground per provider: - [`local`](./playground-local) - [`authjs`](./playground-authjs) diff --git a/docs/.nuxtrc b/docs/.nuxtrc new file mode 100644 index 00000000..109361b8 --- /dev/null +++ b/docs/.nuxtrc @@ -0,0 +1 @@ +imports.autoImport=true diff --git a/docs/README.md b/docs/README.md index 5c60c2c7..d71f46c4 100755 --- a/docs/README.md +++ b/docs/README.md @@ -15,13 +15,13 @@ npx nuxi init docs -t nuxt-themes/docus-starter Install dependencies: ```bash -yarn install +npm install ``` ## Development ```bash -yarn dev +npm dev ``` ## Edge Side Rendering @@ -31,7 +31,7 @@ Can be deployed to Vercel Functions, Netlify Functions, AWS, and most Node-compa Look at all the available presets [here](https://nuxt.com/docs/getting-started/deployment#presets). ```bash -yarn build +npm build ``` ## Static Generation @@ -41,7 +41,7 @@ Use the `generate` command to build your application. The HTML files will be generated in the .output/public directory and ready to be deployed to any static compatible hosting. ```bash -yarn generate +npm generate ``` ## Preview build @@ -49,7 +49,7 @@ yarn generate You might want to preview the result of your build locally, to do so, run the following command: ```bash -yarn preview +npm preview ``` --- diff --git a/docs/app.config.ts b/docs/app.config.ts index 23088f7d..39395b53 100644 --- a/docs/app.config.ts +++ b/docs/app.config.ts @@ -1,5 +1,6 @@ export default defineAppConfig({ + // @ts-ignore docus: { - title: 'My Docs' + title: 'nuxt-auth' } }) diff --git a/docs/content/2.configuration/2.nuxt-config.md b/docs/content/2.configuration/2.nuxt-config.md index 9d1c8afd..e7d6d9fb 100644 --- a/docs/content/2.configuration/2.nuxt-config.md +++ b/docs/content/2.configuration/2.nuxt-config.md @@ -61,7 +61,7 @@ The `AUTH_ORIGIN` environment variable takes precendence over the `origin` confi The origin must be set so that `nuxt-auth` can ensure that callbacks for authentication are correct. The `origin` consists out of (up to) 3 parts: - scheme: `http` or `https` - host: e.g., `localhost`, `example.org`, `www.sidebase.io` -- port: e.g., `:3000`, `:4444`; leave empty to implicitly set `:80` (this is an internet convention, don't ask) +- port: e.g., `:3000`, `:4444`; leave empty to implicitly set `:80` for http, and `:443` for https (this is an internet convention, don't ask) For the demo-app at https://nuxt-auth-example.sidebase.io we set the `origin` to `https://nuxt-auth-example.sidebase.io`. If for some reason required, you can explicitly set the `origin` to `http://localhost:3000` to stop `nuxt-auth` from aborting `npm run build` when the origin is unset. diff --git a/docs/content/2.configuration/3.nuxt-auth-handler.md b/docs/content/2.configuration/3.nuxt-auth-handler.md index 0faf24c1..0cd446c9 100644 --- a/docs/content/2.configuration/3.nuxt-auth-handler.md +++ b/docs/content/2.configuration/3.nuxt-auth-handler.md @@ -92,7 +92,9 @@ export default NuxtAuthHandler({ ``` :: +::alert{type="info"} The `NuxtAuthHandler` accepts [all options that NextAuth.js accepts for its API initialization](https://next-auth.js.org/configuration/options#options). Use this place to configure authentication providers (oauth-Google, credential flow, ...), your `secret`, add callbacks for authentication events, configure a custom logger and more. Read the [`NextAuth.js` docs to see all possible options](https://next-auth.js.org/configuration/options#options). +:: ### secret diff --git a/docs/content/3.application-side/4.protecting-pages.md b/docs/content/3.application-side/4.protecting-pages.md index 0a20faec..fd436567 100644 --- a/docs/content/3.application-side/4.protecting-pages.md +++ b/docs/content/3.application-side/4.protecting-pages.md @@ -1,6 +1,7 @@ # Protecting Pages `nuxt-auth` offers different approaches to protect pages: + 1. Global protection: Protects all pages with manual exceptions 2. Local protection: Protects specific pages 3. Custom middleware: Create your own middleware @@ -48,6 +49,7 @@ That's it! Every page of your application will now need authentication for the u ### Disabling the global middleware locally To disable the global middleware on a specific page only, you can use the [`definePageMeta` macro](https://nuxt.com/docs/api/utils/define-page-meta#definepagemeta) to turn `auth` off: + ```vue diff --git a/playground-local/nuxt.config.ts b/playground-local/nuxt.config.ts index e11fc735..7acb85cd 100644 --- a/playground-local/nuxt.config.ts +++ b/playground-local/nuxt.config.ts @@ -40,7 +40,15 @@ export default defineNuxtConfig({ }, token: { signInResponseTokenPointer: '/token/accessToken' - } + }, + sessionDataType: { id: 'string', email: 'string', name: 'string', role: 'admin | guest | account', subscriptions: "{ id: number, status: 'ACTIVE' | 'INACTIVE' }[]" } + }, + session: { + // Whether to refresh the session every time the browser window is refocused. + enableRefreshOnWindowFocus: true, + + // Whether to refresh the session every `X` milliseconds. Set this to `false` to turn it off. The session will only be refreshed if a session already exists. + enableRefreshPeriodically: 5000 }, globalAppMiddleware: { isEnabled: false diff --git a/playground-local/pages/login.vue b/playground-local/pages/login.vue new file mode 100644 index 00000000..a9786e3b --- /dev/null +++ b/playground-local/pages/login.vue @@ -0,0 +1,33 @@ + + + diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4925dfaa..67248502 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -19,6 +19,12 @@ importers: h3: specifier: ^1.6.4 version: 1.6.4 + knitwork: + specifier: ^1.0.0 + version: 1.0.0 + next-auth: + specifier: ^4.21.1 + version: 4.21.1(next@13.2.4)(react-dom@18.2.0)(react@18.2.0) nitropack: specifier: ^2.3.2 version: 2.3.3 diff --git a/src/module.ts b/src/module.ts index 5769d02b..97d23278 100644 --- a/src/module.ts +++ b/src/module.ts @@ -1,7 +1,7 @@ -import { fileURLToPath } from 'url' -import { defineNuxtModule, useLogger, createResolver, addTemplate, addPlugin, addServerPlugin, addImports } from '@nuxt/kit' +import { defineNuxtModule, useLogger, createResolver, addTemplate, addPlugin, addServerPlugin, addImports, addRouteMiddleware } from '@nuxt/kit' import { defu } from 'defu' import { joinURL } from 'ufo' +import { genInterface } from 'knitwork' import type { DeepRequired } from 'ts-essentials' import { getOriginAndPathnameFromURL, isProduction } from './runtime/helpers' import type { ModuleOptions, SupportedAuthProviders, AuthProviders } from './runtime/types' @@ -36,8 +36,10 @@ const defaultsByBackend: { [key in SupportedAuthProviders]: DeepRequired({ nuxt.options.runtimeConfig = nuxt.options.runtimeConfig || { public: {} } - // @ts-ignore - nuxt.options.runtimeConfig.auth = options - // @ts-ignore nuxt.options.runtimeConfig.public.auth = options @@ -137,6 +136,7 @@ export default defineNuxtModule({ ` const getServerSession: typeof import('${resolve('./runtime/server/services')}').getServerSession`, ` const getToken: typeof import('${resolve('./runtime/server/services')}').getToken`, ` const NuxtAuthHandler: typeof import('${resolve('./runtime/server/services')}').NuxtAuthHandler`, + options.provider.type === 'local' ? genInterface('SessionData', (options.provider as any).sessionDataType) : '', '}' ].join('\n') }) @@ -145,10 +145,16 @@ export default defineNuxtModule({ options.references.push({ path: resolve(nuxt.options.buildDir, 'types/auth.d.ts') }) }) - // 6. Add plugin for initial load + // 6. Register middleware for autocomplete in definePageMeta + addRouteMiddleware({ + name: 'auth', + path: resolve('./runtime/middleware/auth') + }) + + // 7. Add plugin for initial load addPlugin(resolve('./runtime/plugin')) - // 7. Add a server-plugin to check the `origin` on production-startup + // 8. Add a server-plugin to check the `origin` on production-startup if (selectedProvider === 'authjs') { addServerPlugin(resolve('./runtime/server/plugins/assertOrigin')) } diff --git a/src/runtime/composables/commonAuthState.ts b/src/runtime/composables/commonAuthState.ts index 5558d483..f50292f3 100644 --- a/src/runtime/composables/commonAuthState.ts +++ b/src/runtime/composables/commonAuthState.ts @@ -8,11 +8,11 @@ import { useState } from '#imports' export const makeCommonAuthState = () => { const data = useState('auth:data', () => undefined) - const hasInitialSession = data.value !== undefined + const hasInitialSession = computed(() => !!data.value) // If session exists, initialize as already synced const lastRefreshedAt = useState('auth:lastRefreshedAt', () => { - if (hasInitialSession) { + if (hasInitialSession.value) { return new Date() } @@ -20,23 +20,20 @@ export const makeCommonAuthState = () => { }) // If session exists, initialize as not loading - const loading = useState('auth:loading', () => !hasInitialSession) - + const loading = useState('auth:loading', () => false) const status = computed(() => { if (loading.value) { return 'loading' - } - - if (data.value) { + } else if (data.value) { return 'authenticated' + } else { + return 'unauthenticated' } - - return 'unauthenticated' }) // Determine base url of app let baseURL - const { origin, pathname, fullBaseUrl } = useRuntimeConfig().auth.computed + const { origin, pathname, fullBaseUrl } = useRuntimeConfig().public.auth.computed if (origin) { // Case 1: An origin was supplied by the developer in the runtime-config. Use it by returning the already assembled full base url that contains it baseURL = fullBaseUrl diff --git a/src/runtime/composables/local/useAuth.ts b/src/runtime/composables/local/useAuth.ts index e8fa983e..55f79739 100644 --- a/src/runtime/composables/local/useAuth.ts +++ b/src/runtime/composables/local/useAuth.ts @@ -4,14 +4,12 @@ import { CommonUseAuthReturn, SignOutFunc, SignInFunc, GetSessionFunc, Secondary import { _fetch } from '../../utils/fetch' import { jsonPointerGet, useTypedBackendConfig } from '../../helpers' import { getRequestURLWN } from '../../utils/callWithNuxt' -import type { SessionData } from './useAuthState' import { useAuthState } from './useAuthState' +// @ts-expect-error - #auth not defined +import type { SessionData } from '#auth' import { useNuxtApp, useRuntimeConfig, nextTick, navigateTo } from '#imports' -interface Credentials { - username: string - password: string -} +type Credentials = { username?: string, email?: string, password?: string } & Record const signIn: SignInFunc = async (credentials, signInOptions, signInParams) => { const nuxt = useNuxtApp() @@ -74,11 +72,11 @@ const getSession: GetSessionFunc = async (getSessionO const { path, method } = config.endpoints.getSession const { data, loading, lastRefreshedAt, token, rawToken } = useAuthState() - if (!token.value) { + if (!token.value && !getSessionOptions?.force) { return } - const headers = new Headers({ [config.token.headerName]: token.value } as HeadersInit) + const headers = new Headers(token.value ? { [config.token.headerName]: token.value } as HeadersInit : undefined) loading.value = true try { diff --git a/src/runtime/composables/local/useAuthState.ts b/src/runtime/composables/local/useAuthState.ts index f548f534..06ab54ec 100644 --- a/src/runtime/composables/local/useAuthState.ts +++ b/src/runtime/composables/local/useAuthState.ts @@ -4,9 +4,8 @@ import { CommonUseAuthStateReturn } from '../../types' import { makeCommonAuthState } from '../commonAuthState' import { useTypedBackendConfig } from '../../helpers' import { useRuntimeConfig, useCookie, useState } from '#imports' - -// TODO: Improve typing of sessiondata -export type SessionData = Record +// @ts-expect-error - #auth not defined +import type { SessionData } from '#auth' interface UseAuthStateReturn extends CommonUseAuthStateReturn { token: ComputedRef @@ -18,7 +17,7 @@ export const useAuthState = (): UseAuthStateReturn => { const commonAuthState = makeCommonAuthState() // Re-construct state from cookie, also setup a cross-component sync via a useState hack, see https://github.com/nuxt/nuxt/issues/13020#issuecomment-1397282717 - const _rawTokenCookie = useCookie('auth:token', { default: () => null, maxAge: config.token.maxAgeInSeconds, sameSite: 'lax' }) + const _rawTokenCookie = useCookie('auth:token', { default: () => null, maxAge: config.token.maxAgeInSeconds, sameSite: config.token.sameSiteAttribute }) const rawToken = useState('auth:raw-token', () => _rawTokenCookie.value) watch(rawToken, () => { _rawTokenCookie.value = rawToken.value }) diff --git a/src/runtime/helpers.ts b/src/runtime/helpers.ts index f5067f2e..03bf1457 100644 --- a/src/runtime/helpers.ts +++ b/src/runtime/helpers.ts @@ -28,8 +28,8 @@ export const getOriginAndPathnameFromURL = (url: string) => { * @param type Backend type to be enforced (e.g.: `local` or `authjs`) */ export const useTypedBackendConfig = (runtimeConfig: ReturnType, type: T): Extract, { type: T }> => { - if (runtimeConfig.auth.provider.type === type) { - return runtimeConfig.auth.provider as Extract, { type: T }> + if (runtimeConfig.public.auth.provider.type === type) { + return runtimeConfig.public.auth.provider as Extract, { type: T }> } throw new Error('RuntimeError: Type must match at this point') } @@ -42,7 +42,7 @@ export const useTypedBackendConfig = (runtimeC * Implementation adapted from https://github.com/manuelstofer/json-pointer/blob/931b0f9c7178ca09778087b4b0ac7e4f505620c2/index.js#L48-L59 * * @param obj - * @param path + * @param pointer */ export const jsonPointerGet = (obj: Record, pointer: string): string | Record => { const unescape = (str: string) => str.replace(/~1/g, '/').replace(/~0/g, '~') diff --git a/src/runtime/middleware/auth.ts b/src/runtime/middleware/auth.ts index 77811273..9eda419d 100644 --- a/src/runtime/middleware/auth.ts +++ b/src/runtime/middleware/auth.ts @@ -3,18 +3,39 @@ import { navigateToAuthPages, determineCallbackUrl } from '../utils/url' import { useAuth } from '#imports' type MiddlewareMeta = boolean | { - unauthenticatedOnly: true, + /** Whether to only allow unauthenticated users to access this page. + * + * Authenticated users will be redirected to `/` or the route defined in `navigateAuthenticatedTo` + * + * @default undefined + */ + unauthenticatedOnly?: boolean, + /** Where to redirect authenticated users if `unauthenticatedOnly` is set to true + * + * @default undefined + */ navigateAuthenticatedTo?: string, + /** Where to redirect unauthenticated users if this page is protected + * + * @default undefined + */ + navigateUnauthenticatedTo?: string } -declare module '#app' { +declare module '#app/../pages/runtime/composables' { interface PageMeta { auth?: MiddlewareMeta } } export default defineNuxtRouteMiddleware((to) => { - const metaAuth = to.meta.auth as MiddlewareMeta + const metaAuth = typeof to.meta.auth === 'object' + ? { + unauthenticatedOnly: true, + ...to.meta.auth + } + : to.meta.auth + if (metaAuth === false) { return } @@ -22,7 +43,6 @@ export default defineNuxtRouteMiddleware((to) => { const authConfig = useRuntimeConfig().public.auth const { status, signIn } = useAuth() const isGuestMode = typeof metaAuth === 'object' && metaAuth.unauthenticatedOnly - // Guest mode happy path 1: Unauthenticated user is allowed to view page if (isGuestMode && status.value === 'unauthenticated') { return @@ -49,7 +69,7 @@ export default defineNuxtRouteMiddleware((to) => { * - avoid the `Error [ERR_HTTP_HEADERS_SENT]`-error that occurs when we redirect to the sign-in page when the original to-page does not exist. Likely related to https://github.com/nuxt/framework/issues/9438 * */ - if (authConfig.globalAppMiddleware.allow404WithoutAuth) { + if (authConfig.globalAppMiddleware.allow404WithoutAuth || authConfig.globalAppMiddleware === true) { const matchedRoute = to.matched.length > 0 if (!matchedRoute) { // Hands control back to `vue-router`, which will direct to the `404` page @@ -61,6 +81,8 @@ export default defineNuxtRouteMiddleware((to) => { const signInOptions: Parameters[1] = { error: 'SessionRequired', callbackUrl: determineCallbackUrl(authConfig, () => to.path) } // @ts-ignore This is valid for a backend-type of `authjs`, where sign-in accepts a provider as a first argument return signIn(undefined, signInOptions) as ReturnType + } else if (typeof metaAuth === 'object' && metaAuth.navigateUnauthenticatedTo) { + return navigateTo(metaAuth.navigateUnauthenticatedTo) } else { return navigateTo(authConfig.provider.pages.login) } diff --git a/src/runtime/plugin.ts b/src/runtime/plugin.ts index 0b7b4ed1..2acca240 100644 --- a/src/runtime/plugin.ts +++ b/src/runtime/plugin.ts @@ -1,4 +1,5 @@ import { addRouteMiddleware, defineNuxtPlugin, useRuntimeConfig } from '#app' +import { getHeader } from 'h3' import authMiddleware from './middleware/auth' import { useAuth, useAuthState } from '#imports' @@ -7,8 +8,14 @@ export default defineNuxtPlugin(async (nuxtApp) => { const { data, lastRefreshedAt } = useAuthState() const { getSession } = useAuth() + // Skip auth if we're prerendering + let nitroPrerender = false + if (nuxtApp.ssrContext) { + nitroPrerender = getHeader(nuxtApp.ssrContext.event, 'x-nitro-prerender') !== undefined + } + // Only fetch session if it was not yet initialized server-side - if (typeof data.value === 'undefined') { + if (typeof data.value === 'undefined' && !nitroPrerender) { await getSession() } @@ -58,7 +65,9 @@ export default defineNuxtPlugin(async (nuxtApp) => { // 3. Enable the middleware, either globally or as a named `auth` option const { globalAppMiddleware } = useRuntimeConfig().public.auth - addRouteMiddleware('auth', authMiddleware, { - global: globalAppMiddleware.isEnabled - }) + if (globalAppMiddleware === true || globalAppMiddleware.isEnabled) { + addRouteMiddleware('auth', authMiddleware, { + global: true + }) + } }) diff --git a/src/runtime/server/services/authjs/nuxtAuthHandler.ts b/src/runtime/server/services/authjs/nuxtAuthHandler.ts index 03941356..eca14b5b 100644 --- a/src/runtime/server/services/authjs/nuxtAuthHandler.ts +++ b/src/runtime/server/services/authjs/nuxtAuthHandler.ts @@ -183,7 +183,7 @@ export const NuxtAuthHandler = (nuxtAuthOptions?: AuthOptions) => { } export const getServerSession = async (event: H3Event) => { - const authBasePath = useRuntimeConfig().auth.computed.pathname + const authBasePath = useRuntimeConfig().public.auth.computed.pathname // avoid running auth middleware on auth middleware (see #186) if (event.path && event.path.startsWith(authBasePath)) { @@ -218,7 +218,7 @@ export const getServerSession = async (event: H3Event) => { * * @param eventAndOptions Omit & { event: H3Event } The event to get the cookie or authorization header from that contains the JWT Token and options you want to alter token getting behavior. */ -export const getToken = ({ event, secureCookie, secret, ...rest }: Omit & { event: H3Event }) => nextGetToken({ +export const getToken = ({ event, secureCookie, secret, ...rest }: Omit, 'req'> & { event: H3Event }) => nextGetToken({ // @ts-expect-error As our request is not a real next-auth request, we pass down only what's required for the method, as per code from https://github.com/nextauthjs/next-auth/blob/8387c78e3fef13350d8a8c6102caeeb05c70a650/packages/next-auth/src/jwt/index.ts#L68 req: { cookies: parseCookies(event), diff --git a/src/runtime/server/services/utils.ts b/src/runtime/server/services/utils.ts index 0b6d39fe..8a8f3c5b 100644 --- a/src/runtime/server/services/utils.ts +++ b/src/runtime/server/services/utils.ts @@ -16,7 +16,7 @@ export const getServerOrigin = (event?: H3Event): string => { } // Prio 2: Runtime configuration - const runtimeConfigOrigin = useRuntimeConfig().auth.computed.origin + const runtimeConfigOrigin = useRuntimeConfig().public.auth.computed.origin if (runtimeConfigOrigin) { return runtimeConfigOrigin } @@ -44,5 +44,5 @@ export const getRequestURLFromRequest = (event: H3Event, { trustHost }: { trustH } catch (error) { return undefined } - return joinURL(origin, useRuntimeConfig().auth.computed.pathname) + return joinURL(origin, useRuntimeConfig().public.auth.computed.pathname) } diff --git a/src/runtime/types.ts b/src/runtime/types.ts index 7e8763bf..b4e6e1eb 100644 --- a/src/runtime/types.ts +++ b/src/runtime/types.ts @@ -36,6 +36,14 @@ interface GlobalMiddlewareOptions { addDefaultCallbackUrl?: boolean | string } +type DataObjectPrimitives = 'string' | 'number' | 'boolean' | 'any' | 'undefined' | 'function' | 'null' + +type DataObjectArray = `${string}[]` + +export type SessionDataObject = { + [key: string]: Omit | SessionDataObject +}; + /** * Available `nuxt-auth` authentication providers. */ @@ -134,7 +142,22 @@ type ProviderLocal = { * Note: Your backend may reject / expire the token earlier / differently. */ maxAgeInSeconds?: number, - } + /** + * The cookie sameSite policy. See the specification here: https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.2.7 + * + * @default 'lax' + * @example 'strict' + */ + sameSiteAttribute?: boolean | 'lax' | 'strict' | 'none' | undefined, + }, + /** + * Define an interface for the session data object that `nuxt-auth` expects to receive from the `getSession` endpoint. + * + * @default { id: 'string | number' } + * @example { id: 'string', name: 'string', email: 'string' } + * @advanced_array_example { id: 'string', email: 'string', name: 'string', role: 'admin | guest | account', subscriptions: "{ id: number, status: 'ACTIVE' | 'INACTIVE' }[]" } + */ + sessionDataType?: SessionDataObject, } /** @@ -316,6 +339,11 @@ export type GetSessionOptions = Partial<{ required?: boolean callbackUrl?: string onUnauthenticated?: () => void + /** Whether to refetch the session even if the token returned by useAuthState is null. + * + * @default false + */ + force?: boolean }> // TODO: These types could be nicer and more general, or located withing `useAuth` files and more specific diff --git a/src/runtime/utils/url.ts b/src/runtime/utils/url.ts index 857b062c..2d2cae44 100644 --- a/src/runtime/utils/url.ts +++ b/src/runtime/utils/url.ts @@ -1,7 +1,7 @@ import { joinURL } from 'ufo' import getURL from 'requrl' import { sendRedirect } from 'h3' -import { useRequestEvent, useNuxtApp } from '#app' +import { useRequestEvent, useNuxtApp, abortNavigation } from '#app' import { useAuthState, useRuntimeConfig } from '#imports' export const getRequestURL = (includePath = true) => getURL(useRequestEvent()?.node.req, includePath) @@ -12,7 +12,7 @@ export const joinPathToApiURL = (path: string) => joinURL(useAuthState()._intern * * More specifically, we need this function to correctly handle the following cases: * 1. On the client-side, returning `navigateTo(signInUrl)` leads to a `404` error as the next-auth-signin-page was not registered with the vue-router that is used for routing under the hood. For this reason we need to - * manually set `window.location.href` on the client **and then fake return a Promise that does not immeadiatly resolve to block navigation (although it will not actually be fully awaited, but just be awaited long enough for the naviation to complete)**. + * manually set `window.location.href` on the client **and then fake return a Promise that does not immediately resolve to block navigation (although it will not actually be fully awaited, but just be awaited long enough for the naviation to complete)**. * 2. Additionally on the server-side, we cannot use `navigateTo(signInUrl)` as this uses `vue-router` internally which does not know the "external" sign-in page of next-auth and thus will log a warning which we want to avoid. * * Adapted from: https://github.com/nuxt/framework/blob/ab2456c295fc8c7609a7ef7ca1e47def5d087e87/packages/nuxt/src/app/composables/router.ts#L97-L115 @@ -24,7 +24,11 @@ export const navigateToAuthPages = (href: string) => { if (process.server) { if (nuxtApp.ssrContext && nuxtApp.ssrContext.event) { - return nuxtApp.callHook('app:redirected').then(() => sendRedirect(nuxtApp.ssrContext!.event, href, 302)) + return nuxtApp.callHook('app:redirected').then(() => { + sendRedirect(nuxtApp.ssrContext!.event, href, 302) + + abortNavigation() + }) } } @@ -67,5 +71,7 @@ export const determineCallbackUrl = >(authCon return getOriginalTargetPath() } } + } else if (authConfig.globalAppMiddleware === true) { + return getOriginalTargetPath() } }