From 29b92f35a573463c01bec09132f4b6f011f0e2c5 Mon Sep 17 00:00:00 2001 From: benjipott Date: Thu, 9 Nov 2023 07:20:58 +0100 Subject: [PATCH] feat: local with cookie session --- src/module.ts | 23 ++++++++------ src/runtime/composables/local/useAuth.ts | 20 +++++++----- src/runtime/composables/local/useAuthState.ts | 22 +++++++++---- src/runtime/types.ts | 31 ++++++++++++++++--- 4 files changed, 68 insertions(+), 28 deletions(-) diff --git a/src/module.ts b/src/module.ts index 4809978d..a1bbfc36 100644 --- a/src/module.ts +++ b/src/module.ts @@ -1,22 +1,22 @@ import { - defineNuxtModule, - useLogger, - createResolver, - addTemplate, + addImports, addPlugin, + addRouteMiddleware, addServerPlugin, - addImports, - addRouteMiddleware + addTemplate, + createResolver, + defineNuxtModule, + useLogger } from '@nuxt/kit' import { defu } from 'defu' -import { joinURL } from 'ufo' import { genInterface } from 'knitwork' import type { DeepRequired } from 'ts-essentials' +import { joinURL } from 'ufo' import { getOriginAndPathnameFromURL, isProduction } from './runtime/helpers' import type { + AuthProviders, ModuleOptions, - SupportedAuthProviders, - AuthProviders + SupportedAuthProviders } from './runtime/types' const topLevelDefaults = { @@ -52,8 +52,11 @@ const defaultsByBackend: { signInResponseTokenPointer: '/token', type: 'Bearer', headerName: 'Authorization', + sameSiteAttribute: 'lax', + name: 'auth:token', maxAgeInSeconds: 30 * 60, - sameSiteAttribute: 'lax' + secure: false, + domain: undefined }, sessionDataType: { id: 'string | number' } }, diff --git a/src/runtime/composables/local/useAuth.ts b/src/runtime/composables/local/useAuth.ts index e9127d3a..fdbd59bd 100644 --- a/src/runtime/composables/local/useAuth.ts +++ b/src/runtime/composables/local/useAuth.ts @@ -1,13 +1,13 @@ -import { readonly, Ref } from 'vue' import { callWithNuxt } from '#app/nuxt' -import { CommonUseAuthReturn, SignOutFunc, SignInFunc, GetSessionFunc, SecondarySignInOptions } from '../../types' -import { _fetch } from '../../utils/fetch' +import { readonly, Ref } from 'vue' import { jsonPointerGet, useTypedBackendConfig } from '../../helpers' +import { CommonUseAuthReturn, GetSessionFunc, SecondarySignInOptions, SignInFunc, SignOutFunc } from '../../types' import { getRequestURLWN } from '../../utils/callWithNuxt' +import { _fetch } from '../../utils/fetch' import { useAuthState } from './useAuthState' // @ts-expect-error - #auth not defined import type { SessionData } from '#auth' -import { useNuxtApp, useRuntimeConfig, nextTick, navigateTo } from '#imports' +import { navigateTo, nextTick, useNuxtApp, useRuntimeConfig } from '#imports' type Credentials = { username?: string, email?: string, password?: string } & Record @@ -49,7 +49,7 @@ const signOut: SignOutFunc = async (signOutOptions) => { const config = useTypedBackendConfig(runtimeConfig, 'local') const { data, rawToken, token } = await callWithNuxt(nuxt, useAuthState) - const headers = new Headers({ [config.token.headerName]: token.value } as HeadersInit) + const headers = new Headers(config.token.headerName ? { [config.token.headerName]: token.value } as HeadersInit : undefined) data.value = null rawToken.value = null @@ -80,7 +80,7 @@ const getSession: GetSessionFunc = async (getSessionO return } - const headers = new Headers(token.value ? { [config.token.headerName]: token.value } as HeadersInit : undefined) + const headers = new Headers(token.value && config.token.headerName ? { [config.token.headerName]: token.value } as HeadersInit : undefined) loading.value = true try { @@ -114,7 +114,13 @@ const signUp = async (credentials: Credentials, signInOptions?: SecondarySignInO body: credentials }) - return signIn(credentials, signInOptions) + await nextTick(getSession) + + const { callbackUrl, redirect = true, external } = signInOptions ?? {} + if (redirect) { + const urlToNavigateTo = callbackUrl ?? await getRequestURLWN(nuxt) + return navigateTo(urlToNavigateTo, { external }) + } } interface UseAuthReturn extends CommonUseAuthReturn { diff --git a/src/runtime/composables/local/useAuthState.ts b/src/runtime/composables/local/useAuthState.ts index bc634f07..528e618c 100644 --- a/src/runtime/composables/local/useAuthState.ts +++ b/src/runtime/composables/local/useAuthState.ts @@ -1,9 +1,9 @@ -import { computed, watch, ComputedRef } from 'vue' -import type { CookieRef } from '#app' +import type { CookieRef } from '#app/nuxt' +import { ComputedRef, computed, watch } from 'vue' +import { useTypedBackendConfig } from '../../helpers' import { CommonUseAuthStateReturn } from '../../types' import { makeCommonAuthState } from '../commonAuthState' -import { useTypedBackendConfig } from '../../helpers' -import { useRuntimeConfig, useCookie, useState } from '#imports' +import { useCookie, useRuntimeConfig, useState } from '#imports' // @ts-expect-error - #auth not defined import type { SessionData } from '#auth' @@ -19,7 +19,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: config.token.sameSiteAttribute }) + const _rawTokenCookie = useCookie(config.token.name, { default: () => null, maxAge: config.token.maxAgeInSeconds, sameSite: config.token.sameSiteAttribute, secure: config.token.secure, domain: config.token.domain }) const rawToken = useState('auth:raw-token', () => _rawTokenCookie.value) watch(rawToken, () => { _rawTokenCookie.value = rawToken.value }) @@ -28,7 +28,17 @@ export const useAuthState = (): UseAuthStateReturn => { if (rawToken.value === null) { return null } - return config.token.type.length > 0 ? `${config.token.type} ${rawToken.value}` : rawToken.value + + if (config.token.type.length > 0) { + switch (config.token.type) { + case 'Cookie': + return `${config.token.name}=${rawToken.value}` + case 'Bearer': + default: + return `${config.token.type} ${rawToken.value}` + } + } + return rawToken.value }) const setToken = (newToken: string | null) => { diff --git a/src/runtime/types.ts b/src/runtime/types.ts index 7b88f668..0156fc85 100644 --- a/src/runtime/types.ts +++ b/src/runtime/types.ts @@ -1,5 +1,5 @@ -import type { Ref, ComputedRef } from 'vue' import { RouterMethod } from 'h3' +import type { ComputedRef, Ref } from 'vue' import { SupportedProviders } from './composables/authjs/useAuth' /** @@ -119,6 +119,12 @@ type ProviderLocal = { * Settings for the authentication-token that `nuxt-auth` receives from the `signIn` endpoint and that can be used to authenticate subsequent requests. */ token?: { + /** + * The name of the cookie to store the authentication-token in. + * + * @default auth:token Access the cookie `auth:token` from session + */ + name?: string, /** * How to extract the authentication-token from the sign-in response. * @@ -142,14 +148,14 @@ type ProviderLocal = { * Header name to be used in requests that need to be authenticated, e.g., to be used in the `getSession` request. * * @default Authorization - * @example Auth + * @example Cookie */ headerName?: string; /** * Maximum age to store the authentication token for. After the expiry time the token is automatically deleted on the application side, i.e., in the users' browser. * * Note: Your backend may reject / expire the token earlier / differently. - * @default 1800 + * @default undefined * @example 60 * 60 * 24 */ maxAgeInSeconds?: number; @@ -159,8 +165,23 @@ type ProviderLocal = { * @default 'lax' * @example 'strict' */ - sameSiteAttribute?: boolean | 'lax' | 'strict' | 'none' | undefined; - }; + sameSiteAttribute?: boolean | 'lax' | 'strict' | 'none' | undefined, + /** + * Specifies the boolean value for the Secure Set-Cookie attribute. When truthy, the Secure attribute is set; otherwise it is not. By default, the Secure attribute is not set. + * Note: Be careful when setting this to true, as compliant clients will not allow client-side JavaScript to see the cookie in document.cookie. + * @default false + * @example true + */ + secure?: boolean, + /** + * Specifies the value for the Domain Set-Cookie attribute. By default, no domain is set, and most clients will consider applying the cookie only to the current domain. + * + * @default undefined use + * @example 'domain.com' + */ + domain?: boolean, + + }, /** * Define an interface for the session data object that `nuxt-auth` expects to receive from the `getSession` endpoint. *