From a80fc92cf24da561f8e02fbd0c2134069a6b35d7 Mon Sep 17 00:00:00 2001 From: Amer Date: Sat, 15 Jul 2023 16:52:31 +0200 Subject: [PATCH 1/6] Updated tsconfig.json. File ./playground/.nuxt/tsconfig.json does not exists. Typo fix: inviteAccept renamed to acceptInvite. --- docs/content/4.Types/8.DirectusInvite.md | 2 +- src/runtime/composables/useDirectusAuth.ts | 2 +- tsconfig.json | 7 +++++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/content/4.Types/8.DirectusInvite.md b/docs/content/4.Types/8.DirectusInvite.md index 841d9427..2a047028 100644 --- a/docs/content/4.Types/8.DirectusInvite.md +++ b/docs/content/4.Types/8.DirectusInvite.md @@ -9,7 +9,7 @@ export interface DirectusInviteCreation { invite_url?: string }; -export interface DirectusInviteAccept { +export interface DirectusAcceptInvite { token: string; password: string }; diff --git a/src/runtime/composables/useDirectusAuth.ts b/src/runtime/composables/useDirectusAuth.ts index ab133c70..8b5ce1c3 100644 --- a/src/runtime/composables/useDirectusAuth.ts +++ b/src/runtime/composables/useDirectusAuth.ts @@ -185,7 +185,7 @@ export const useDirectusAuth = () => { createUser, register, inviteUser, - inviteAccept, + acceptInvite, loginWithProvider } } diff --git a/tsconfig.json b/tsconfig.json index 9dd826f9..18453631 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,3 +1,6 @@ { - "extends": "./playground/.nuxt/tsconfig.json" -} + "compilerOptions": { + "moduleResolution": "node", + "module": "ESNext" + } +} \ No newline at end of file From 013437db774cc7805c9bcebe9a875ec73cd05b28 Mon Sep 17 00:00:00 2001 From: Amer Date: Sat, 15 Jul 2023 20:40:39 +0200 Subject: [PATCH 2/6] Added sameSiteRefreshToken and isSecureRefreshToken settings. --- src/module.ts | 24 +++++++++++- src/runtime/composables/useDirectusToken.ts | 43 ++++++++++----------- 2 files changed, 43 insertions(+), 24 deletions(-) diff --git a/src/module.ts b/src/module.ts index 816767d9..d4cbad98 100644 --- a/src/module.ts +++ b/src/module.ts @@ -65,6 +65,20 @@ export interface ModuleOptions { * @type string */ maxAgeRefreshToken?: number; + + /** + * The SameSite attribute for the refresh token cookie. + * @type string + * @default 'strict' + */ + sameSiteRefreshToken?: 'strict' | 'lax' | 'none' | undefined; + + /** + * The Secure attribute for the refresh token cookie. + * @type boolean + * @default true + */ + isSecureRefreshToken?: boolean; } export default defineNuxtModule({ @@ -83,7 +97,11 @@ export default defineNuxtModule({ devtools: false, cookieNameToken: 'directus_token', cookieNameRefreshToken: 'directus_refresh_token', - maxAgeRefreshToken: 604800 + + // Nuxt Cookies Docs @ https://nuxt.com/docs/api/composables/use-cookie + maxAgeRefreshToken: 604800, + sameSiteRefreshToken: 'lax', + isSecureRefreshToken: false }, setup (options, nuxt) { nuxt.options.runtimeConfig.public = nuxt.options.runtimeConfig.public || {} @@ -97,7 +115,9 @@ export default defineNuxtModule({ devtools: options.devtools, cookieNameToken: options.cookieNameToken, cookieNameRefreshToken: options.cookieNameRefreshToken, - maxAgeRefreshToken: options.maxAgeRefreshToken + maxAgeRefreshToken: options.maxAgeRefreshToken, + sameSiteRefreshToken: options.sameSiteRefreshToken, + isSecureRefreshToken: options.isSecureRefreshToken }) const runtimeDir = fileURLToPath(new URL('./runtime', import.meta.url)) diff --git a/src/runtime/composables/useDirectusToken.ts b/src/runtime/composables/useDirectusToken.ts index f4cc8de0..7fe0e18d 100644 --- a/src/runtime/composables/useDirectusToken.ts +++ b/src/runtime/composables/useDirectusToken.ts @@ -8,37 +8,36 @@ export const useDirectusToken = () => { const baseUrl = useDirectusUrl() const config = useRuntimeConfig().public - const token = (): CookieRef => { + /** + * Get or set cookie. + * @param name + * @private + */ + const _getOrSetCookie = (name: string) => { nuxtApp._cookies = nuxtApp._cookies || {} - if (nuxtApp._cookies[config.directus.cookieNameToken]) { - return nuxtApp._cookies[config.directus.cookieNameToken] + if (nuxtApp._cookies[name]) { + return nuxtApp._cookies[name] } - const cookie = useCookie(config.directus.cookieNameToken) - nuxtApp._cookies[config.directus.cookieNameToken] = cookie + const cookie = useCookie(name, { + maxAge: config.directus.maxAgeRefreshToken, + sameSite: config.directus.sameSiteRefreshToken, + secure: config.directus.isSecureRefreshToken + }) + nuxtApp._cookies[name] = cookie return cookie } - const refreshToken = (): CookieRef => { - nuxtApp._cookies = nuxtApp._cookies || {} - if (nuxtApp._cookies[config.directus.cookieNameRefreshToken]) { - return nuxtApp._cookies[config.directus.cookieNameRefreshToken] - } + const token = (): CookieRef => { + return _getOrSetCookie(config.directus.cookieNameToken) + } - const cookie = useCookie(config.directus.cookieNameRefreshToken, { maxAge: config.directus.maxAgeRefreshToken }) - nuxtApp._cookies[config.directus.cookieNameRefreshToken] = cookie - return cookie + const refreshToken = (): CookieRef => { + return _getOrSetCookie(config.directus.cookieNameRefreshToken) } const expires = (): CookieRef => { - nuxtApp._cookies = nuxtApp._cookies || {} - if (nuxtApp._cookies.directus_token_expired_at) { - return nuxtApp._cookies.directus_token_expired_at - } - - const cookie = useCookie('directus_token_expired_at') - nuxtApp._cookies.directus_token_expired_at = cookie - return cookie + return _getOrSetCookie('directus_token_expired_at') } const refreshTokens = async (): Promise => { @@ -46,7 +45,7 @@ export const useDirectusToken = () => { const body = { refresh_token: refreshToken().value } - const data = await $fetch<{data: DirectusAuthResponse}>('/auth/refresh', { + const data = await $fetch<{ data: DirectusAuthResponse }>('/auth/refresh', { baseURL: baseUrl, body, method: 'POST' From 262fc97384c71458b01c12177f87b94895491251 Mon Sep 17 00:00:00 2001 From: Amer Date: Mon, 17 Jul 2023 00:51:21 +0200 Subject: [PATCH 3/6] [BUG FIX] Run autoFetch + autoRefresh on plugin setup, without 'app:created' hook. As the 'app:created' hook is not called on SSR=true (static generation). --- src/runtime/plugin.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/runtime/plugin.ts b/src/runtime/plugin.ts index 887664ea..9bc850ee 100644 --- a/src/runtime/plugin.ts +++ b/src/runtime/plugin.ts @@ -5,7 +5,7 @@ import { useDirectusUser } from './composables/useDirectusUser'; import { useDirectusAuth } from './composables/useDirectusAuth'; export default defineNuxtPlugin(async (nuxtApp) => { - + const config = useRuntimeConfig(); const { fetchUser } = useDirectusAuth(); const { token, checkAutoRefresh } = useDirectusToken(); @@ -19,12 +19,10 @@ export default defineNuxtPlugin(async (nuxtApp) => { } } - nuxtApp.hook('app:created', async () => { - if (process.server) { - await checkAutoRefresh(); - await checkIfUserExists(); - } - }) + // do the checks server-side, instead of using hook 'app:created', + // as this hook is not called on SSR=true (static generation) + await checkAutoRefresh(); + await checkIfUserExists(); nuxtApp.hook('page:start', async () => { if (process.client) { From 3305e89536d5bdcfe53847eeda02641f2e1948b6 Mon Sep 17 00:00:00 2001 From: Amer Date: Mon, 17 Jul 2023 01:04:23 +0200 Subject: [PATCH 4/6] Renamed cookie params + update docs. - cookieMaxAge (from maxAgeRefreshToken) - cookieSameSite (from sameSiteRefreshToken) - cookieSecure (from isSecureRefreshToken) --- docs/content/1.getting-started/2.options.md | 14 +++++++++- src/module.ts | 29 +++++++++++---------- src/runtime/composables/useDirectusToken.ts | 6 ++--- 3 files changed, 31 insertions(+), 18 deletions(-) diff --git a/docs/content/1.getting-started/2.options.md b/docs/content/1.getting-started/2.options.md index 2118d4a0..bbd756e7 100644 --- a/docs/content/1.getting-started/2.options.md +++ b/docs/content/1.getting-started/2.options.md @@ -38,7 +38,7 @@ Auto refesh tokens The function that get called if the `autoRefresh` fail -## `maxAgeRefreshToken` +## `cookieMaxAge` - Default: `604800` @@ -46,6 +46,18 @@ Need to be the same as specified in your directus config; this is the max amount Auto refesh tokens +## `cookieSameSite` + +- Default: `lax` + +The SameSite attribute for auth cookies. + +## `cookieSecure` + +- Default: false + +The Secure attribute for auth cookies. + ## `fetchUserParams` - No default - **Optional** diff --git a/src/module.ts b/src/module.ts index d4cbad98..99be5a24 100644 --- a/src/module.ts +++ b/src/module.ts @@ -60,25 +60,26 @@ export interface ModuleOptions { cookieNameRefreshToken?: string; /** - * The max age for the refresh token cookie in seconds. + * The max age for auth cookies in seconds. * This should match your directus env key REFRESH_TOKEN_TTL * @type string + * @default 604800 */ - maxAgeRefreshToken?: number; + cookieMaxAge?: number; /** - * The SameSite attribute for the refresh token cookie. + * The SameSite attribute for auth cookies. * @type string - * @default 'strict' + * @default 'lax' */ - sameSiteRefreshToken?: 'strict' | 'lax' | 'none' | undefined; + cookieSameSite?: 'strict' | 'lax' | 'none' | undefined; /** - * The Secure attribute for the refresh token cookie. + * The Secure attribute for auth cookies. * @type boolean - * @default true + * @default false */ - isSecureRefreshToken?: boolean; + cookieSecure?: boolean; } export default defineNuxtModule({ @@ -99,9 +100,9 @@ export default defineNuxtModule({ cookieNameRefreshToken: 'directus_refresh_token', // Nuxt Cookies Docs @ https://nuxt.com/docs/api/composables/use-cookie - maxAgeRefreshToken: 604800, - sameSiteRefreshToken: 'lax', - isSecureRefreshToken: false + cookieMaxAge: 604800, + cookieSameSite: 'lax', + cookieSecure: false }, setup (options, nuxt) { nuxt.options.runtimeConfig.public = nuxt.options.runtimeConfig.public || {} @@ -115,9 +116,9 @@ export default defineNuxtModule({ devtools: options.devtools, cookieNameToken: options.cookieNameToken, cookieNameRefreshToken: options.cookieNameRefreshToken, - maxAgeRefreshToken: options.maxAgeRefreshToken, - sameSiteRefreshToken: options.sameSiteRefreshToken, - isSecureRefreshToken: options.isSecureRefreshToken + cookieMaxAge: options.cookieMaxAge, + cookieSameSite: options.cookieSameSite, + cookieSecure: options.cookieSecure }) const runtimeDir = fileURLToPath(new URL('./runtime', import.meta.url)) diff --git a/src/runtime/composables/useDirectusToken.ts b/src/runtime/composables/useDirectusToken.ts index 7fe0e18d..011bd357 100644 --- a/src/runtime/composables/useDirectusToken.ts +++ b/src/runtime/composables/useDirectusToken.ts @@ -20,9 +20,9 @@ export const useDirectusToken = () => { } const cookie = useCookie(name, { - maxAge: config.directus.maxAgeRefreshToken, - sameSite: config.directus.sameSiteRefreshToken, - secure: config.directus.isSecureRefreshToken + maxAge: config.directus.cookieMaxAge, + sameSite: config.directus.cookieSameSite, + secure: config.directus.cookieSecure }) nuxtApp._cookies[name] = cookie return cookie From 035b382bc628091313d59ea1a02966ad9645fc2e Mon Sep 17 00:00:00 2001 From: Matt <85650530+casualmatt@users.noreply.github.com> Date: Tue, 18 Jul 2023 18:48:32 +0200 Subject: [PATCH 5/6] feat: warning "maxAgeRefreshToken deprecated" --- playground/nuxt.config.ts | 1 + src/module.ts | 51 +++++++++++++++++++++++++-------------- 2 files changed, 34 insertions(+), 18 deletions(-) diff --git a/playground/nuxt.config.ts b/playground/nuxt.config.ts index aa1293bc..598c0294 100644 --- a/playground/nuxt.config.ts +++ b/playground/nuxt.config.ts @@ -5,5 +5,6 @@ export default defineNuxtConfig({ directus: { url: 'http://localhost:8055/', devtools: true, + maxAgeRefreshToken: 10000, } }) diff --git a/src/module.ts b/src/module.ts index 99be5a24..ca7ae788 100644 --- a/src/module.ts +++ b/src/module.ts @@ -1,7 +1,7 @@ import { resolve } from 'path' import { fileURLToPath } from 'url' import { defu } from 'defu' -import { defineNuxtModule, addPlugin, addImportsDir, isNuxt2 } from '@nuxt/kit' +import { defineNuxtModule, addPlugin, addImportsDir } from '@nuxt/kit' import { joinURL } from 'ufo' import { DirectusQueryParams } from './runtime/types' @@ -67,6 +67,14 @@ export interface ModuleOptions { */ cookieMaxAge?: number; + /** + * The max age for auth cookies in seconds. + * This should match your directus env key REFRESH_TOKEN_TTL + * @type string + * @default 604800 + */ + maxAgeRefreshToken?: number; + /** * The SameSite attribute for auth cookies. * @type string @@ -104,28 +112,35 @@ export default defineNuxtModule({ cookieSameSite: 'lax', cookieSecure: false }, - setup (options, nuxt) { - nuxt.options.runtimeConfig.public = nuxt.options.runtimeConfig.public || {} - nuxt.options.runtimeConfig.public.directus = defu(nuxt.options.runtimeConfig.public.directus, { - url: options.url, - autoFetch: options.autoFetch, - autoRefresh: options.autoRefresh, - onAutoRefreshFailure: options.onAutoRefreshFailure, - fetchUserParams: options.fetchUserParams, - token: options.token, - devtools: options.devtools, - cookieNameToken: options.cookieNameToken, - cookieNameRefreshToken: options.cookieNameRefreshToken, - cookieMaxAge: options.cookieMaxAge, - cookieSameSite: options.cookieSameSite, - cookieSecure: options.cookieSecure - }) - + setup(options, nuxt) { + nuxt.options.runtimeConfig.public = nuxt.options.runtimeConfig.public || {}; + nuxt.options.runtimeConfig.public.directus = defu( + nuxt.options.runtimeConfig.public.directus, + { + url: options.url, + autoFetch: options.autoFetch, + autoRefresh: options.autoRefresh, + onAutoRefreshFailure: options.onAutoRefreshFailure, + fetchUserParams: options.fetchUserParams, + token: options.token, + devtools: options.devtools, + cookieNameToken: options.cookieNameToken, + cookieNameRefreshToken: options.cookieNameRefreshToken, + cookieMaxAge: options.cookieMaxAge || options.maxAgeRefreshToken, + cookieSameSite: options.cookieSameSite, + cookieSecure: options.cookieSecure + }) + const runtimeDir = fileURLToPath(new URL('./runtime', import.meta.url)) nuxt.options.build.transpile.push(runtimeDir) addPlugin(resolve(runtimeDir, 'plugin')) addImportsDir(resolve(runtimeDir, 'composables')) + if (options.maxAgeRefreshToken) { + console.warn( + 'maxAgeRefreshToken is deprecated, please use cookieMaxAge instead' + ); + } if (options.devtools) { const adminUrl = joinURL(nuxt.options.runtimeConfig.public.directus.url, '/admin/') From 9be624f744c4dd4e7125751165edb70b82aa71ed Mon Sep 17 00:00:00 2001 From: Amer Date: Wed, 19 Jul 2023 00:08:33 +0200 Subject: [PATCH 6/6] [FEATURE] Added an export for setAuthCookies(). This way I can create a custom login function. > TODO: Add code and tutorial for passwordless login using Directus 10. --- src/runtime/composables/useDirectusAuth.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/runtime/composables/useDirectusAuth.ts b/src/runtime/composables/useDirectusAuth.ts index 8b5ce1c3..c2e09f24 100644 --- a/src/runtime/composables/useDirectusAuth.ts +++ b/src/runtime/composables/useDirectusAuth.ts @@ -186,6 +186,7 @@ export const useDirectusAuth = () => { register, inviteUser, acceptInvite, - loginWithProvider + loginWithProvider, + setAuthCookies } }