From 1672be4d38e82529796612fe592f6716182ff21a Mon Sep 17 00:00:00 2001 From: Gustavo Fenilli Date: Sun, 17 Mar 2024 20:39:18 -0300 Subject: [PATCH 01/10] feat: adds config file and AuthTokenStorage adds possibility to write a config file to be read, and also to pass AuthTokenStorage for storage. --- src/config.ts | 59 +++++++++++++++++++++ src/index.d.ts | 2 +- src/module.ts | 41 +++----------- src/runtime/composables/useSanctumAuth.ts | 32 ++++++----- src/runtime/composables/useSanctumConfig.ts | 8 +++ src/runtime/composables/useSanctumUser.ts | 8 +-- src/runtime/httpFactory.ts | 37 +++++++------ src/runtime/middleware/sanctum.auth.ts | 9 ++-- src/runtime/middleware/sanctum.guest.ts | 7 ++- src/runtime/plugin.ts | 9 ++-- src/types.ts | 36 ++++++++++++- test/fixtures/basic/nuxt.config.ts | 8 --- test/fixtures/basic/sanctum.config.ts | 5 ++ 13 files changed, 170 insertions(+), 91 deletions(-) create mode 100644 src/config.ts create mode 100644 src/runtime/composables/useSanctumConfig.ts create mode 100644 test/fixtures/basic/sanctum.config.ts diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000..1ed3349 --- /dev/null +++ b/src/config.ts @@ -0,0 +1,59 @@ +import { existsSync } from 'fs'; +import type { SanctumModuleOptions } from './types'; +import { createResolver, useNuxt, addPluginTemplate } from '@nuxt/kit'; + +export function addNuxtAuthSanctumConfig(options: SanctumModuleOptions) { + const resolver = createResolver(import.meta.url); + const nuxt = useNuxt(); + + const configPath = resolver.resolve(nuxt.options.rootDir, options.configFile ?? 'sanctum.config'); + if (!existsSync(configPath)) throw new Error( + `Nuxt Auth Sanctum configuration was not located at "${configPath}".` + ); + + addPluginTemplate({ + filename: 'nuxt-auth-sanctum-config.mjs', + getContents() { + return ` + import { defineNuxtPlugin } from '#imports'; + import defu from 'defu'; + import sanctumConfig from '${configPath}'; + + export default defineNuxtPlugin((nuxtApp) => { + const defaultConfig = { + userStateKey: 'sanctum.user.identity', + redirectIfAuthenticated: false, + endpoints: { + csrf: '/sanctum/csrf-cookie', + login: '/login', + logout: '/logout', + user: '/api/user', + }, + csrf: { + cookie: 'XSRF-TOKEN', + header: 'X-XSRF-TOKEN', + }, + client: { + retry: false, + }, + redirect: { + keepRequestedRoute: false, + onLogin: '/', + onLogout: '/', + onAuthOnly: '/login', + onGuestOnly: '/', + }, + }; + + const config = defu(sanctumConfig, defaultConfig); + + return { + provide: { + sanctumConfig: config, + }, + }; + }); + `; + }, + }); +}; diff --git a/src/index.d.ts b/src/index.d.ts index cc7e98e..f9e9587 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -2,6 +2,6 @@ import { SanctumModuleOptions } from './types'; declare module 'nuxt/schema' { interface PublicRuntimeConfig { - sanctum: Partial; + sanctum: SanctumModuleOptions; } } diff --git a/src/module.ts b/src/module.ts index be66eb1..8fbfe32 100644 --- a/src/module.ts +++ b/src/module.ts @@ -1,3 +1,4 @@ +import type { SanctumModuleOptions, SanctumConfigOptions } from './types'; import { defineNuxtModule, addPlugin, @@ -5,10 +6,9 @@ import { addImportsDir, addRouteMiddleware, } from '@nuxt/kit'; -import { defu } from 'defu'; -import type { SanctumModuleOptions } from './types'; +import { addNuxtAuthSanctumConfig } from './config'; -export default defineNuxtModule>({ +export default defineNuxtModule({ meta: { name: 'nuxt-auth-sanctum', configKey: 'sanctum', @@ -18,40 +18,13 @@ export default defineNuxtModule>({ }, defaults: { - userStateKey: 'sanctum.user.identity', - redirectIfAuthenticated: false, - endpoints: { - csrf: '/sanctum/csrf-cookie', - login: '/login', - logout: '/logout', - user: '/api/user', - }, - csrf: { - cookie: 'XSRF-TOKEN', - header: 'X-XSRF-TOKEN', - }, - client: { - retry: false, - }, - redirect: { - keepRequestedRoute: false, - onLogin: '/', - onLogout: '/', - onAuthOnly: '/login', - onGuestOnly: '/', - }, + configFile: 'sanctum.config.ts', }, - setup(options, nuxt) { + setup(options) { const resolver = createResolver(import.meta.url); - const publicConfig = nuxt.options.runtimeConfig.public; - const userModuleConfig = publicConfig.sanctum; - - nuxt.options.runtimeConfig.public.sanctum = defu( - userModuleConfig as any, - options - ); + addNuxtAuthSanctumConfig(options); addImportsDir(resolver.resolve('./runtime/composables')); @@ -67,3 +40,5 @@ export default defineNuxtModule>({ addPlugin(resolver.resolve('./runtime/plugin')); }, }); + +export const defineNuxtAuthSanctumConfig = (config: Partial) => config; diff --git a/src/runtime/composables/useSanctumAuth.ts b/src/runtime/composables/useSanctumAuth.ts index 3c19de2..5cf144e 100644 --- a/src/runtime/composables/useSanctumAuth.ts +++ b/src/runtime/composables/useSanctumAuth.ts @@ -1,8 +1,8 @@ import { type Ref, computed } from 'vue'; import { useSanctumClient } from './useSanctumClient'; import { useSanctumUser } from './useSanctumUser'; -import { navigateTo, useNuxtApp, useRoute, useRuntimeConfig } from '#app'; -import type { SanctumModuleOptions } from '../../types'; +import { useSanctumConfig } from './useSanctumConfig'; +import { navigateTo, useNuxtApp, useRoute } from '#app'; export interface SanctumAuth { user: Ref; @@ -22,14 +22,14 @@ export const useSanctumAuth = (): SanctumAuth => { const user = useSanctumUser(); const client = useSanctumClient(); - const options = useRuntimeConfig().public.sanctum as SanctumModuleOptions; + const config = useSanctumConfig(); const isAuthenticated = computed(() => { return user.value !== null; }); async function refreshIdentity() { - user.value = await client(options.endpoints.user); + user.value = await client(config.endpoints.user); } /** @@ -39,27 +39,29 @@ export const useSanctumAuth = (): SanctumAuth => { */ async function login(credentials: Record) { if (isAuthenticated.value === true) { - if (options.redirectIfAuthenticated === false) { + if (config.redirectIfAuthenticated === false) { throw new Error('User is already authenticated'); } - if (options.redirect.onLogin === false) { + if (config.redirect.onLogin === false) { return; } - const redirect = options.redirect.onLogin as string; + const redirect = config.redirect.onLogin as string; await nuxtApp.runWithContext(() => navigateTo(redirect)); } - await client(options.endpoints.login, { + const endpointResult = await client(config.endpoints.login, { method: 'post', body: credentials, }); + if (config.authTokenStorage) config.authTokenStorage.add(endpointResult); + await refreshIdentity(); - if (options.redirect.keepRequestedRoute) { + if (config.redirect.keepRequestedRoute) { const route = useRoute(); const requestedRoute = route.query.redirect as string | undefined; if (requestedRoute) { @@ -68,8 +70,8 @@ export const useSanctumAuth = (): SanctumAuth => { } } - if (options.redirect.onLogin) { - const redirect = options.redirect.onLogin as string; + if (config.redirect.onLogin) { + const redirect = config.redirect.onLogin as string; await nuxtApp.runWithContext(() => navigateTo(redirect)); } } @@ -82,12 +84,14 @@ export const useSanctumAuth = (): SanctumAuth => { throw new Error('User is not authenticated'); } - await client(options.endpoints.logout, { method: 'post' }); + await client(config.endpoints.logout, { method: 'post' }); + + if (config.authTokenStorage) config.authTokenStorage.delete(); user.value = null; - if (options.redirect.onLogout) { - const redirect = options.redirect.onLogout as string; + if (config.redirect.onLogout) { + const redirect = config.redirect.onLogout as string; await nuxtApp.runWithContext(() => navigateTo(redirect)); } diff --git a/src/runtime/composables/useSanctumConfig.ts b/src/runtime/composables/useSanctumConfig.ts new file mode 100644 index 0000000..ea8b915 --- /dev/null +++ b/src/runtime/composables/useSanctumConfig.ts @@ -0,0 +1,8 @@ +import type { SanctumConfigOptions } from '../../types'; +import { useNuxtApp } from '#app'; + +export const useSanctumConfig = (): SanctumConfigOptions => { + const { $sanctumConfig } = useNuxtApp(); + + return $sanctumConfig as SanctumConfigOptions; +}; diff --git a/src/runtime/composables/useSanctumUser.ts b/src/runtime/composables/useSanctumUser.ts index 77f856f..87fc199 100644 --- a/src/runtime/composables/useSanctumUser.ts +++ b/src/runtime/composables/useSanctumUser.ts @@ -1,15 +1,15 @@ -import { useState, useRuntimeConfig } from '#app'; +import { useState } from '#app'; +import { useSanctumConfig } from './useSanctumConfig'; import { type Ref } from 'vue'; -import type { SanctumModuleOptions } from '../../types'; /** * Returns a current authenticated user information. * @returns Reference to the user state as T. */ export const useSanctumUser = (): Ref => { - const options = useRuntimeConfig().public.sanctum as SanctumModuleOptions; + const config = useSanctumConfig(); - const user = useState(options.userStateKey, () => null); + const user = useState(config.userStateKey, () => null); return user; }; diff --git a/src/runtime/httpFactory.ts b/src/runtime/httpFactory.ts index 6cbe31b..e894731 100644 --- a/src/runtime/httpFactory.ts +++ b/src/runtime/httpFactory.ts @@ -3,18 +3,17 @@ import { useCookie, useRequestEvent, useRequestHeaders, - useRuntimeConfig, navigateTo, useNuxtApp, } from '#app'; -import type { SanctumModuleOptions } from '../types'; import { useSanctumUser } from './composables/useSanctumUser'; +import { useSanctumConfig } from './composables/useSanctumConfig'; import { useRequestURL } from 'nuxt/app'; export const SECURE_METHODS = new Set(['post', 'delete', 'put', 'patch']); export function createHttpClient(): $Fetch { - const options = useRuntimeConfig().public.sanctum as SanctumModuleOptions; + const config = useSanctumConfig(); const event = useRequestEvent(); const user = useSanctumUser(); const nuxtApp = useNuxtApp(); @@ -27,18 +26,18 @@ export function createHttpClient(): $Fetch { async function buildClientHeaders( headers: HeadersInit | undefined ): Promise { - await $fetch(options.endpoints.csrf, { - baseURL: options.baseUrl, + await $fetch(config.endpoints.csrf, { + baseURL: config.baseUrl, credentials: 'include', }); - const csrfToken = useCookie(options.csrf.cookie, { + const csrfToken = useCookie(config.csrf.cookie, { readonly: true, }).value; return { ...headers, - ...(csrfToken && { [options.csrf.header]: csrfToken }), + ...(csrfToken && { [config.csrf.header]: csrfToken }), }; } @@ -48,26 +47,26 @@ export function createHttpClient(): $Fetch { * @returns { HeadersInit } */ function buildServerHeaders(headers: HeadersInit | undefined): HeadersInit { - const csrfToken = useCookie(options.csrf.cookie, { + const csrfToken = useCookie(config.csrf.cookie, { readonly: true, }).value; const clientCookies = useRequestHeaders(['cookie']); - const origin = options.origin ?? useRequestURL().origin; + const origin = config.origin ?? useRequestURL().origin; return { ...headers, // use the origin from the request headers if not set Referer: origin, ...(clientCookies.cookie && clientCookies), - ...(csrfToken && { [options.csrf.header]: csrfToken }), + ...(csrfToken && { [config.csrf.header]: csrfToken }), }; } const httpOptions: FetchOptions = { - baseURL: options.baseUrl, + baseURL: config.baseUrl, credentials: 'include', redirect: 'manual', - retry: options.client.retry, + retry: config.client.retry, async onRequest({ options }): Promise { const method = options.method?.toLowerCase() ?? 'get'; @@ -77,6 +76,14 @@ export function createHttpClient(): $Fetch { ...options.headers, }; + if (config.authTokenStorage) { + const authToken = config.authTokenStorage.get(); + if (authToken) options.headers = { + ...options.headers, + Authorization: `Bearer ${authToken}`, + }; + } + // https://laravel.com/docs/10.x/routing#form-method-spoofing if (options.body instanceof FormData && method === 'put') { options.method = 'POST'; @@ -119,15 +126,15 @@ export function createHttpClient(): $Fetch { if (response.status === 401) { // do not redirect when requesting the user endpoint // this prevents an infinite loop (ERR_TOO_MANY_REDIRECTS) - if (request.toString().endsWith(options.endpoints.user)) { + if (request.toString().endsWith(config.endpoints.user)) { return; } user.value = null; - if (options.redirect.onLogout) { + if (config.redirect.onLogout) { await nuxtApp.runWithContext(() => - navigateTo(options.redirect.onLogout as string) + navigateTo(config.redirect.onLogout as string) ); } } diff --git a/src/runtime/middleware/sanctum.auth.ts b/src/runtime/middleware/sanctum.auth.ts index dd274cf..398d747 100644 --- a/src/runtime/middleware/sanctum.auth.ts +++ b/src/runtime/middleware/sanctum.auth.ts @@ -1,16 +1,15 @@ import { defineNuxtRouteMiddleware, navigateTo, - useRuntimeConfig, createError, } from '#app'; import type { RouteLocationRaw } from 'vue-router'; import { useSanctumUser } from '../composables/useSanctumUser'; -import type { SanctumModuleOptions } from '../../types'; +import { useSanctumConfig } from '../composables/useSanctumConfig'; export default defineNuxtRouteMiddleware((to) => { const user = useSanctumUser(); - const options = useRuntimeConfig().public.sanctum as SanctumModuleOptions; + const config = useSanctumConfig(); const isAuthenticated = user.value !== null; @@ -18,7 +17,7 @@ export default defineNuxtRouteMiddleware((to) => { return; } - const endpoint = options.redirect.onAuthOnly; + const endpoint = config.redirect.onAuthOnly; if (endpoint === false) { throw createError({ statusCode: 403 }); @@ -26,7 +25,7 @@ export default defineNuxtRouteMiddleware((to) => { const redirect: RouteLocationRaw = { path: endpoint }; - if (options.redirect.keepRequestedRoute) { + if (config.redirect.keepRequestedRoute) { redirect.query = { redirect: to.fullPath }; } diff --git a/src/runtime/middleware/sanctum.guest.ts b/src/runtime/middleware/sanctum.guest.ts index 16f671a..affee24 100644 --- a/src/runtime/middleware/sanctum.guest.ts +++ b/src/runtime/middleware/sanctum.guest.ts @@ -1,15 +1,14 @@ import { defineNuxtRouteMiddleware, navigateTo, - useRuntimeConfig, createError, } from '#app'; -import type { SanctumModuleOptions } from '../../types'; import { useSanctumUser } from '../composables/useSanctumUser'; +import { useSanctumConfig } from '../composables/useSanctumConfig'; export default defineNuxtRouteMiddleware(() => { const user = useSanctumUser(); - const options = useRuntimeConfig().public.sanctum as SanctumModuleOptions; + const config = useSanctumConfig(); const isAuthenticated = user.value !== null; @@ -17,7 +16,7 @@ export default defineNuxtRouteMiddleware(() => { return; } - const endpoint = options.redirect.onGuestOnly; + const endpoint = config.redirect.onGuestOnly; if (endpoint === false) { throw createError({ statusCode: 403 }); diff --git a/src/runtime/plugin.ts b/src/runtime/plugin.ts index e24ea0e..d70c72d 100644 --- a/src/runtime/plugin.ts +++ b/src/runtime/plugin.ts @@ -2,7 +2,7 @@ import { FetchError } from 'ofetch'; import { defineNuxtPlugin } from '#app'; import { createHttpClient } from './httpFactory'; import { useSanctumUser } from './composables/useSanctumUser'; -import type { SanctumModuleOptions } from '../types'; +import { useSanctumConfig } from './composables/useSanctumConfig'; function handleIdentityLoadError(error: Error) { if ( @@ -16,15 +16,14 @@ function handleIdentityLoadError(error: Error) { } } -export default defineNuxtPlugin(async (nuxtApp) => { +export default defineNuxtPlugin(async () => { const user = useSanctumUser(); + const config = useSanctumConfig(); const client = createHttpClient(); - const options = nuxtApp.$config.public.sanctum as SanctumModuleOptions; - if (user.value === null) { try { - user.value = await client(options.endpoints.user); + user.value = await client(config.endpoints.user); } catch (error) { handleIdentityLoadError(error as Error); } diff --git a/src/types.ts b/src/types.ts index c8f9a55..a59aba6 100644 --- a/src/types.ts +++ b/src/types.ts @@ -75,9 +75,37 @@ export interface RedirectOptions { } /** - * Options to be passed to the plugin. + * Interface for auth token storages to share. + */ +export interface AuthTokenStorage { + /** + * Adds token from store. + */ + add(clientResult: any): void; + /** + * Removes token from store. + */ + delete(): void; + /** + * Gets the token from store. + */ + get(): string | undefined; +} + +/** + * Options to be passed to the module plugin. */ export interface SanctumModuleOptions { + /** + * The path for the configuration file, defaults to "sanctum.config.ts". + */ + configFile?: string; +} + +/** + * Options to be passed to the configuration file. + */ +export interface SanctumConfigOptions { /** * The base URL of the Laravel API. */ @@ -110,4 +138,8 @@ export interface SanctumModuleOptions { * Behavior of the plugin redirects when user is authenticated or not. */ redirect: RedirectOptions; -} + /** + * Storage to be used when saving an auth token. + */ + authTokenStorage?: AuthTokenStorage; +}; diff --git a/test/fixtures/basic/nuxt.config.ts b/test/fixtures/basic/nuxt.config.ts index 1b57023..67fed93 100644 --- a/test/fixtures/basic/nuxt.config.ts +++ b/test/fixtures/basic/nuxt.config.ts @@ -2,12 +2,4 @@ import MyModule from '../../../src/module'; export default defineNuxtConfig({ modules: [MyModule], - - runtimeConfig: { - public: { - sanctum: { - baseUrl: 'http://localhost:80', - }, - }, - }, }); diff --git a/test/fixtures/basic/sanctum.config.ts b/test/fixtures/basic/sanctum.config.ts new file mode 100644 index 0000000..148e6ec --- /dev/null +++ b/test/fixtures/basic/sanctum.config.ts @@ -0,0 +1,5 @@ +import { defineNuxtAuthSanctumConfig } from '../../../src/module' + +export default defineNuxtAuthSanctumConfig({ + baseUrl: 'http://localhost:80', +}); From 6e92a21fdb65f39e7f367838848060e80d83a73e Mon Sep 17 00:00:00 2001 From: Gustavo Fenilli Date: Sun, 17 Mar 2024 22:07:29 -0300 Subject: [PATCH 02/10] fix: fixes config file not found when no config file --- src/config.ts | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/config.ts b/src/config.ts index 1ed3349..b453c4a 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,4 +1,3 @@ -import { existsSync } from 'fs'; import type { SanctumModuleOptions } from './types'; import { createResolver, useNuxt, addPluginTemplate } from '@nuxt/kit'; @@ -7,17 +6,14 @@ export function addNuxtAuthSanctumConfig(options: SanctumModuleOptions) { const nuxt = useNuxt(); const configPath = resolver.resolve(nuxt.options.rootDir, options.configFile ?? 'sanctum.config'); - if (!existsSync(configPath)) throw new Error( - `Nuxt Auth Sanctum configuration was not located at "${configPath}".` - ); addPluginTemplate({ filename: 'nuxt-auth-sanctum-config.mjs', getContents() { return ` import { defineNuxtPlugin } from '#imports'; - import defu from 'defu'; - import sanctumConfig from '${configPath}'; + ${configPath ? "import defu from 'defu';" : ''}; + ${configPath ? `import sanctumConfig from '${configPath}';` : ''}; export default defineNuxtPlugin((nuxtApp) => { const defaultConfig = { @@ -45,7 +41,7 @@ export function addNuxtAuthSanctumConfig(options: SanctumModuleOptions) { }, }; - const config = defu(sanctumConfig, defaultConfig); + const config = ${configPath ? "defu(sanctumConfig, defaultConfig)" : 'defaultConfig'}; return { provide: { From 3bffee757979d9f6bd544606939dfe729824d564 Mon Sep 17 00:00:00 2001 From: Gustavo Fenilli Date: Sun, 17 Mar 2024 22:07:43 -0300 Subject: [PATCH 03/10] fix: fixes direct import from entry file --- package.json | 6 +++++- src/index.ts | 5 +++++ src/module.ts | 4 +--- 3 files changed, 11 insertions(+), 4 deletions(-) create mode 100644 src/index.ts diff --git a/package.json b/package.json index 8c2b836..042c7f4 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,10 @@ "types": "./dist/types.d.ts", "import": "./dist/module.mjs", "require": "./dist/module.cjs" + }, + "./config": { + "import": "./dist/index.mjs", + "require": "./dist/index.cjs" } }, "main": "./dist/module.cjs", @@ -63,4 +67,4 @@ "vue-tsc": "^1.8.26" }, "packageManager": "yarn@3.6.3" -} \ No newline at end of file +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..158abbb --- /dev/null +++ b/src/index.ts @@ -0,0 +1,5 @@ +import type { SanctumConfigOptions } from './types'; + +export const defineSanctumConfig = (config: Partial) => config; + +export type { SanctumConfigOptions }; diff --git a/src/module.ts b/src/module.ts index 8fbfe32..549cb0f 100644 --- a/src/module.ts +++ b/src/module.ts @@ -1,4 +1,4 @@ -import type { SanctumModuleOptions, SanctumConfigOptions } from './types'; +import type { SanctumModuleOptions } from './types'; import { defineNuxtModule, addPlugin, @@ -40,5 +40,3 @@ export default defineNuxtModule({ addPlugin(resolver.resolve('./runtime/plugin')); }, }); - -export const defineNuxtAuthSanctumConfig = (config: Partial) => config; From 97123f6a44f245b95e71f463b095b1d473216daf Mon Sep 17 00:00:00 2001 From: Gustavo Fenilli Date: Sun, 17 Mar 2024 22:11:51 -0300 Subject: [PATCH 04/10] fix: fix test import --- test/fixtures/basic/sanctum.config.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/fixtures/basic/sanctum.config.ts b/test/fixtures/basic/sanctum.config.ts index 148e6ec..ad004b5 100644 --- a/test/fixtures/basic/sanctum.config.ts +++ b/test/fixtures/basic/sanctum.config.ts @@ -1,5 +1,5 @@ -import { defineNuxtAuthSanctumConfig } from '../../../src/module' +import { defineSanctumConfig } from '../../../src' -export default defineNuxtAuthSanctumConfig({ +export default defineSanctumConfig({ baseUrl: 'http://localhost:80', }); From 211431aff6d851fb0b8433ecb67b63c7c84206ca Mon Sep 17 00:00:00 2001 From: Gustavo Fenilli Date: Mon, 18 Mar 2024 14:28:39 -0300 Subject: [PATCH 05/10] fix: fixes provided config being undefined --- package.json | 4 -- playground/nuxt.config.ts | 23 ----------- playground/sanctum.config.ts | 4 ++ src/config.ts | 55 --------------------------- src/index.ts | 5 --- src/module.ts | 55 ++++++++++++++++++++++++--- test/fixtures/basic/sanctum.config.ts | 6 +-- 7 files changed, 56 insertions(+), 96 deletions(-) create mode 100644 playground/sanctum.config.ts delete mode 100644 src/config.ts delete mode 100644 src/index.ts diff --git a/package.json b/package.json index 042c7f4..dd398f6 100644 --- a/package.json +++ b/package.json @@ -15,10 +15,6 @@ "types": "./dist/types.d.ts", "import": "./dist/module.mjs", "require": "./dist/module.cjs" - }, - "./config": { - "import": "./dist/index.mjs", - "require": "./dist/index.cjs" } }, "main": "./dist/module.cjs", diff --git a/playground/nuxt.config.ts b/playground/nuxt.config.ts index 3966d24..67cd437 100644 --- a/playground/nuxt.config.ts +++ b/playground/nuxt.config.ts @@ -5,28 +5,5 @@ export default defineNuxtConfig({ typeCheck: true, }, ssr: true, - runtimeConfig: { - public: { - sanctum: { - baseUrl: 'http://localhost:80', - userStateKey: 'sanctum.user.identity', - }, - }, - }, - sanctum: { - redirect: { - keepRequestedRoute: true, - onAuthOnly: '/login', - onGuestOnly: '/profile', - onLogin: '/welcome', - onLogout: '/logout', - }, - endpoints: { - csrf: '/sanctum/csrf-cookie', - login: '/api/login', - logout: '/api/logout', - user: '/api/user', - }, - }, devtools: { enabled: true }, }); diff --git a/playground/sanctum.config.ts b/playground/sanctum.config.ts new file mode 100644 index 0000000..6e209a8 --- /dev/null +++ b/playground/sanctum.config.ts @@ -0,0 +1,4 @@ +export default { + baseUrl: 'http://localhost:80', + userStateKey: 'sanctum.user.identity', +}; diff --git a/src/config.ts b/src/config.ts deleted file mode 100644 index b453c4a..0000000 --- a/src/config.ts +++ /dev/null @@ -1,55 +0,0 @@ -import type { SanctumModuleOptions } from './types'; -import { createResolver, useNuxt, addPluginTemplate } from '@nuxt/kit'; - -export function addNuxtAuthSanctumConfig(options: SanctumModuleOptions) { - const resolver = createResolver(import.meta.url); - const nuxt = useNuxt(); - - const configPath = resolver.resolve(nuxt.options.rootDir, options.configFile ?? 'sanctum.config'); - - addPluginTemplate({ - filename: 'nuxt-auth-sanctum-config.mjs', - getContents() { - return ` - import { defineNuxtPlugin } from '#imports'; - ${configPath ? "import defu from 'defu';" : ''}; - ${configPath ? `import sanctumConfig from '${configPath}';` : ''}; - - export default defineNuxtPlugin((nuxtApp) => { - const defaultConfig = { - userStateKey: 'sanctum.user.identity', - redirectIfAuthenticated: false, - endpoints: { - csrf: '/sanctum/csrf-cookie', - login: '/login', - logout: '/logout', - user: '/api/user', - }, - csrf: { - cookie: 'XSRF-TOKEN', - header: 'X-XSRF-TOKEN', - }, - client: { - retry: false, - }, - redirect: { - keepRequestedRoute: false, - onLogin: '/', - onLogout: '/', - onAuthOnly: '/login', - onGuestOnly: '/', - }, - }; - - const config = ${configPath ? "defu(sanctumConfig, defaultConfig)" : 'defaultConfig'}; - - return { - provide: { - sanctumConfig: config, - }, - }; - }); - `; - }, - }); -}; diff --git a/src/index.ts b/src/index.ts deleted file mode 100644 index 158abbb..0000000 --- a/src/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { SanctumConfigOptions } from './types'; - -export const defineSanctumConfig = (config: Partial) => config; - -export type { SanctumConfigOptions }; diff --git a/src/module.ts b/src/module.ts index 549cb0f..06b7d86 100644 --- a/src/module.ts +++ b/src/module.ts @@ -1,3 +1,4 @@ +import { existsSync } from 'node:fs'; import type { SanctumModuleOptions } from './types'; import { defineNuxtModule, @@ -5,8 +6,8 @@ import { createResolver, addImportsDir, addRouteMiddleware, + addPluginTemplate, } from '@nuxt/kit'; -import { addNuxtAuthSanctumConfig } from './config'; export default defineNuxtModule({ meta: { @@ -21,10 +22,56 @@ export default defineNuxtModule({ configFile: 'sanctum.config.ts', }, - setup(options) { + setup(options, nuxt) { const resolver = createResolver(import.meta.url); - addNuxtAuthSanctumConfig(options); + const configBase = resolver.resolve(nuxt.options.rootDir, options.configFile ?? 'sanctum.config'); + + addPlugin(resolver.resolve('./runtime/plugin')); + + addPluginTemplate({ + filename: 'sanctum-config.mjs', + async getContents() { + const configPath = await resolver.resolvePath(configBase); + const configPathExists = existsSync(configBase); + + return ` + import { defineNuxtPlugin } from '#imports'; + ${configPathExists ? "import defu from 'defu';" : ''} + ${configPathExists ? `import sanctumConfig from '${configPath}'` : ''} + + export default defineNuxtPlugin((nuxtApp) => { + const defaultConfig = { + userStateKey: 'sanctum.user.identity', + redirectIfAuthenticated: false, + endpoints: { + csrf: '/sanctum/csrf-cookie', + login: '/login', + logout: '/logout', + user: '/api/user', + }, + csrf: { + cookie: 'XSRF-TOKEN', + header: 'X-XSRF-TOKEN', + }, + client: { + retry: false, + }, + redirect: { + keepRequestedRoute: false, + onLogin: '/', + onLogout: '/', + onAuthOnly: '/login', + onGuestOnly: '/', + }, + }; + + const config = ${configPathExists ? `defu(sanctumConfig, defaultConfig)` : `defaultConfig`}; + nuxtApp.provide('sanctumConfig', config); + }); + `; + }, + }); addImportsDir(resolver.resolve('./runtime/composables')); @@ -36,7 +83,5 @@ export default defineNuxtModule({ name: 'sanctum:guest', path: resolver.resolve('./runtime/middleware/sanctum.guest'), }); - - addPlugin(resolver.resolve('./runtime/plugin')); }, }); diff --git a/test/fixtures/basic/sanctum.config.ts b/test/fixtures/basic/sanctum.config.ts index ad004b5..8ed75d3 100644 --- a/test/fixtures/basic/sanctum.config.ts +++ b/test/fixtures/basic/sanctum.config.ts @@ -1,5 +1,3 @@ -import { defineSanctumConfig } from '../../../src' - -export default defineSanctumConfig({ +export default { baseUrl: 'http://localhost:80', -}); +}; From 828101a41602344c24aa10eb29ab658c01b73d7b Mon Sep 17 00:00:00 2001 From: Gustavo Fenilli Date: Mon, 18 Mar 2024 18:52:47 -0300 Subject: [PATCH 06/10] fix: fixes storage not dealing with Promises --- src/runtime/httpFactory.ts | 2 +- src/types.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/runtime/httpFactory.ts b/src/runtime/httpFactory.ts index e894731..48b5257 100644 --- a/src/runtime/httpFactory.ts +++ b/src/runtime/httpFactory.ts @@ -77,7 +77,7 @@ export function createHttpClient(): $Fetch { }; if (config.authTokenStorage) { - const authToken = config.authTokenStorage.get(); + const authToken = await config.authTokenStorage.get(); if (authToken) options.headers = { ...options.headers, Authorization: `Bearer ${authToken}`, diff --git a/src/types.ts b/src/types.ts index a59aba6..48cd780 100644 --- a/src/types.ts +++ b/src/types.ts @@ -89,7 +89,7 @@ export interface AuthTokenStorage { /** * Gets the token from store. */ - get(): string | undefined; + get(): string | undefined | Promise; } /** From 921d16ff499023d74001ac4baf86c37d31284787 Mon Sep 17 00:00:00 2001 From: Gustavo Fenilli Date: Tue, 19 Mar 2024 15:06:29 -0300 Subject: [PATCH 07/10] feat: adds possibility to pass function as config adds possibility to pass function as config, so you can have access to nuxt composables. --- src/module.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/module.ts b/src/module.ts index 06b7d86..1574fba 100644 --- a/src/module.ts +++ b/src/module.ts @@ -66,7 +66,7 @@ export default defineNuxtModule({ }, }; - const config = ${configPathExists ? `defu(sanctumConfig, defaultConfig)` : `defaultConfig`}; + const config = ${configPathExists ? `defu(typeof sanctumConfig === 'function' ? sanctumConfig() : sanctumConfig, defaultConfig)` : `defaultConfig`}; nuxtApp.provide('sanctumConfig', config); }); `; From 2e1a2a505f0050768a3cba5b4f3f2a9e89891526 Mon Sep 17 00:00:00 2001 From: Gustavo Fenilli Date: Sat, 23 Mar 2024 15:45:59 -0300 Subject: [PATCH 08/10] style: runs yarn fmt --- src/module.ts | 5 ++++- src/runtime/composables/useSanctumAuth.ts | 3 ++- src/runtime/httpFactory.ts | 9 +++++---- src/runtime/middleware/sanctum.auth.ts | 6 +----- src/runtime/middleware/sanctum.guest.ts | 6 +----- src/types.ts | 2 +- 6 files changed, 14 insertions(+), 17 deletions(-) diff --git a/src/module.ts b/src/module.ts index 1574fba..6839b70 100644 --- a/src/module.ts +++ b/src/module.ts @@ -25,7 +25,10 @@ export default defineNuxtModule({ setup(options, nuxt) { const resolver = createResolver(import.meta.url); - const configBase = resolver.resolve(nuxt.options.rootDir, options.configFile ?? 'sanctum.config'); + const configBase = resolver.resolve( + nuxt.options.rootDir, + options.configFile ?? 'sanctum.config' + ); addPlugin(resolver.resolve('./runtime/plugin')); diff --git a/src/runtime/composables/useSanctumAuth.ts b/src/runtime/composables/useSanctumAuth.ts index 5cf144e..94fae1e 100644 --- a/src/runtime/composables/useSanctumAuth.ts +++ b/src/runtime/composables/useSanctumAuth.ts @@ -57,7 +57,8 @@ export const useSanctumAuth = (): SanctumAuth => { body: credentials, }); - if (config.authTokenStorage) config.authTokenStorage.add(endpointResult); + if (config.authTokenStorage) + config.authTokenStorage.add(endpointResult); await refreshIdentity(); diff --git a/src/runtime/httpFactory.ts b/src/runtime/httpFactory.ts index 48b5257..c69b96d 100644 --- a/src/runtime/httpFactory.ts +++ b/src/runtime/httpFactory.ts @@ -78,10 +78,11 @@ export function createHttpClient(): $Fetch { if (config.authTokenStorage) { const authToken = await config.authTokenStorage.get(); - if (authToken) options.headers = { - ...options.headers, - Authorization: `Bearer ${authToken}`, - }; + if (authToken) + options.headers = { + ...options.headers, + Authorization: `Bearer ${authToken}`, + }; } // https://laravel.com/docs/10.x/routing#form-method-spoofing diff --git a/src/runtime/middleware/sanctum.auth.ts b/src/runtime/middleware/sanctum.auth.ts index 398d747..b331028 100644 --- a/src/runtime/middleware/sanctum.auth.ts +++ b/src/runtime/middleware/sanctum.auth.ts @@ -1,8 +1,4 @@ -import { - defineNuxtRouteMiddleware, - navigateTo, - createError, -} from '#app'; +import { defineNuxtRouteMiddleware, navigateTo, createError } from '#app'; import type { RouteLocationRaw } from 'vue-router'; import { useSanctumUser } from '../composables/useSanctumUser'; import { useSanctumConfig } from '../composables/useSanctumConfig'; diff --git a/src/runtime/middleware/sanctum.guest.ts b/src/runtime/middleware/sanctum.guest.ts index affee24..3574d75 100644 --- a/src/runtime/middleware/sanctum.guest.ts +++ b/src/runtime/middleware/sanctum.guest.ts @@ -1,8 +1,4 @@ -import { - defineNuxtRouteMiddleware, - navigateTo, - createError, -} from '#app'; +import { defineNuxtRouteMiddleware, navigateTo, createError } from '#app'; import { useSanctumUser } from '../composables/useSanctumUser'; import { useSanctumConfig } from '../composables/useSanctumConfig'; diff --git a/src/types.ts b/src/types.ts index 48cd780..276f514 100644 --- a/src/types.ts +++ b/src/types.ts @@ -142,4 +142,4 @@ export interface SanctumConfigOptions { * Storage to be used when saving an auth token. */ authTokenStorage?: AuthTokenStorage; -}; +} From cd86f3be4d95b8e07068d8d00d0427bd6373b772 Mon Sep 17 00:00:00 2001 From: Gustavo Fenilli Date: Sat, 23 Mar 2024 17:29:02 -0300 Subject: [PATCH 09/10] feat: adds backwards compatibility and static config file name --- playground/nuxt.config.ts | 22 +++++++ playground/sanctum.config.ts | 1 - src/module.ts | 70 ++++++++++----------- src/runtime/composables/useSanctumConfig.ts | 4 +- src/runtime/plugin.ts | 8 +-- src/types.ts | 28 ++++----- 6 files changed, 71 insertions(+), 62 deletions(-) diff --git a/playground/nuxt.config.ts b/playground/nuxt.config.ts index 67cd437..91e5038 100644 --- a/playground/nuxt.config.ts +++ b/playground/nuxt.config.ts @@ -5,5 +5,27 @@ export default defineNuxtConfig({ typeCheck: true, }, ssr: true, + runtimeConfig: { + public: { + sanctum: { + userStateKey: 'sanctum.user.identity', + }, + }, + }, + sanctum: { + redirect: { + keepRequestedRoute: true, + onAuthOnly: '/login', + onGuestOnly: '/profile', + onLogin: '/welcome', + onLogout: '/logout', + }, + endpoints: { + csrf: '/sanctum/csrf-cookie', + login: '/api/login', + logout: '/api/logout', + user: '/api/user', + }, + }, devtools: { enabled: true }, }); diff --git a/playground/sanctum.config.ts b/playground/sanctum.config.ts index 6e209a8..8ed75d3 100644 --- a/playground/sanctum.config.ts +++ b/playground/sanctum.config.ts @@ -1,4 +1,3 @@ export default { baseUrl: 'http://localhost:80', - userStateKey: 'sanctum.user.identity', }; diff --git a/src/module.ts b/src/module.ts index 6839b70..1e8725c 100644 --- a/src/module.ts +++ b/src/module.ts @@ -8,8 +8,9 @@ import { addRouteMiddleware, addPluginTemplate, } from '@nuxt/kit'; +import defu from 'defu'; -export default defineNuxtModule({ +export default defineNuxtModule>({ meta: { name: 'nuxt-auth-sanctum', configKey: 'sanctum', @@ -19,60 +20,55 @@ export default defineNuxtModule({ }, defaults: { - configFile: 'sanctum.config.ts', + userStateKey: 'sanctum.user.identity', + redirectIfAuthenticated: false, + endpoints: { + csrf: '/sanctum/csrf-cookie', + login: '/login', + logout: '/logout', + user: '/api/user', + }, + csrf: { + cookie: 'XSRF-TOKEN', + header: 'X-XSRF-TOKEN', + }, + client: { + retry: false, + }, + redirect: { + keepRequestedRoute: false, + onLogin: '/', + onLogout: '/', + onAuthOnly: '/login', + onGuestOnly: '/', + }, }, setup(options, nuxt) { const resolver = createResolver(import.meta.url); - const configBase = resolver.resolve( - nuxt.options.rootDir, - options.configFile ?? 'sanctum.config' - ); - addPlugin(resolver.resolve('./runtime/plugin')); addPluginTemplate({ - filename: 'sanctum-config.mjs', + filename: 'sanctum-plugin.mjs', async getContents() { - const configPath = await resolver.resolvePath(configBase); - const configPathExists = existsSync(configBase); + const configPath = await resolver.resolvePath(resolver.resolve( + nuxt.options.rootDir, + 'sanctum.config' + )); + const configPathExists = existsSync(configPath); return ` import { defineNuxtPlugin } from '#imports'; ${configPathExists ? "import defu from 'defu';" : ''} - ${configPathExists ? `import sanctumConfig from '${configPath}'` : ''} + ${configPathExists ? `import sanctumConfig from '${configPath}';` : ''} export default defineNuxtPlugin((nuxtApp) => { - const defaultConfig = { - userStateKey: 'sanctum.user.identity', - redirectIfAuthenticated: false, - endpoints: { - csrf: '/sanctum/csrf-cookie', - login: '/login', - logout: '/logout', - user: '/api/user', - }, - csrf: { - cookie: 'XSRF-TOKEN', - header: 'X-XSRF-TOKEN', - }, - client: { - retry: false, - }, - redirect: { - keepRequestedRoute: false, - onLogin: '/', - onLogout: '/', - onAuthOnly: '/login', - onGuestOnly: '/', - }, - }; - + const defaultConfig = ${JSON.stringify(defu(nuxt.options.runtimeConfig.public.sanctum, options))}; const config = ${configPathExists ? `defu(typeof sanctumConfig === 'function' ? sanctumConfig() : sanctumConfig, defaultConfig)` : `defaultConfig`}; nuxtApp.provide('sanctumConfig', config); }); - `; + ` }, }); diff --git a/src/runtime/composables/useSanctumConfig.ts b/src/runtime/composables/useSanctumConfig.ts index ea8b915..706a61a 100644 --- a/src/runtime/composables/useSanctumConfig.ts +++ b/src/runtime/composables/useSanctumConfig.ts @@ -1,8 +1,8 @@ import type { SanctumConfigOptions } from '../../types'; import { useNuxtApp } from '#app'; -export const useSanctumConfig = (): SanctumConfigOptions => { +export const useSanctumConfig = () => { const { $sanctumConfig } = useNuxtApp(); - return $sanctumConfig as SanctumConfigOptions; + return $sanctumConfig as Readonly>; }; diff --git a/src/runtime/plugin.ts b/src/runtime/plugin.ts index d70c72d..687bc8d 100644 --- a/src/runtime/plugin.ts +++ b/src/runtime/plugin.ts @@ -16,7 +16,7 @@ function handleIdentityLoadError(error: Error) { } } -export default defineNuxtPlugin(async () => { +export default defineNuxtPlugin(async (nuxtApp) => { const user = useSanctumUser(); const config = useSanctumConfig(); const client = createHttpClient(); @@ -29,9 +29,5 @@ export default defineNuxtPlugin(async () => { } } - return { - provide: { - sanctumClient: client, - }, - }; + nuxtApp.provide('sanctumClient', client); }); diff --git a/src/types.ts b/src/types.ts index 276f514..efdd4fa 100644 --- a/src/types.ts +++ b/src/types.ts @@ -96,16 +96,6 @@ export interface AuthTokenStorage { * Options to be passed to the module plugin. */ export interface SanctumModuleOptions { - /** - * The path for the configuration file, defaults to "sanctum.config.ts". - */ - configFile?: string; -} - -/** - * Options to be passed to the configuration file. - */ -export interface SanctumConfigOptions { /** * The base URL of the Laravel API. */ @@ -117,27 +107,33 @@ export interface SanctumConfigOptions { /** * The key to use to store the user identity in the `useState` variable. */ - userStateKey: string; + userStateKey?: string; /** * Determine to redirect when user is authenticated. */ - redirectIfAuthenticated: boolean; + redirectIfAuthenticated?: boolean; /** * Laravel Sanctum endpoints to be used by the client. */ - endpoints: SanctumEndpoints; + endpoints?: SanctumEndpoints; /** * CSRF token specific options. */ - csrf: CsrfOptions; + csrf?: CsrfOptions; /** * OFetch client specific options. */ - client: ClientOptions; + client?: ClientOptions; /** * Behavior of the plugin redirects when user is authenticated or not. */ - redirect: RedirectOptions; + redirect?: RedirectOptions; +} + +/** + * Options to be passed to the configuration file. + */ +export interface SanctumConfigOptions extends SanctumModuleOptions { /** * Storage to be used when saving an auth token. */ From 6fdf8276d83b3c569a664f7c8be994e4dfa1cf81 Mon Sep 17 00:00:00 2001 From: Gustavo Fenilli Date: Sat, 23 Mar 2024 17:46:31 -0300 Subject: [PATCH 10/10] style: fix eslint prettier errors --- src/module.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/module.ts b/src/module.ts index 1e8725c..ad59ba3 100644 --- a/src/module.ts +++ b/src/module.ts @@ -52,10 +52,9 @@ export default defineNuxtModule>({ addPluginTemplate({ filename: 'sanctum-plugin.mjs', async getContents() { - const configPath = await resolver.resolvePath(resolver.resolve( - nuxt.options.rootDir, - 'sanctum.config' - )); + const configPath = await resolver.resolvePath( + resolver.resolve(nuxt.options.rootDir, 'sanctum.config') + ); const configPathExists = existsSync(configPath); return ` @@ -68,7 +67,7 @@ export default defineNuxtModule>({ const config = ${configPathExists ? `defu(typeof sanctumConfig === 'function' ? sanctumConfig() : sanctumConfig, defaultConfig)` : `defaultConfig`}; nuxtApp.provide('sanctumConfig', config); }); - ` + `; }, });