From 4b109a95c8b261ad91a29cdb7b28656000c2aa07 Mon Sep 17 00:00:00 2001 From: Mohamed Mansour Date: Mon, 1 Jul 2024 18:03:30 +0300 Subject: [PATCH 1/4] feat: add authFetch composable --- src/runtime/composables/local/useAuth.ts | 26 +++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/runtime/composables/local/useAuth.ts b/src/runtime/composables/local/useAuth.ts index e2a60e3a..ac36b1ae 100644 --- a/src/runtime/composables/local/useAuth.ts +++ b/src/runtime/composables/local/useAuth.ts @@ -138,9 +138,32 @@ const signUp = async (credentials: Credentials, signInOptions?: SecondarySignInO return signIn(credentials, signInOptions) } +const authFetch = async (path: string, options: Parameters[1]): Promise => { + const nuxt = useNuxtApp() + + const config = useTypedBackendConfig(useRuntimeConfig(), 'local') + + const { token: tokenState, _internal } = useAuthState() + let token = tokenState.value + token ??= formatToken(_internal.rawTokenCookie.value) + + if (path) { + const headers = { + ...options?.headers, + ...(token ? { [config.token.headerName]: token } : {}) // append auth token if it exists + } + try { + return await _fetch(nuxt, path, { ...options, headers }) + } catch (err) { + console.error(`Error during authenticated fetch:, ${(err as Error).message}`) + } + } +} + interface UseAuthReturn extends CommonUseAuthReturn { signUp: typeof signUp - token: Readonly> + authFetch: typeof authFetch + token: Readonly>, } export const useAuth = (): UseAuthReturn => { const { @@ -159,6 +182,7 @@ export const useAuth = (): UseAuthReturn => { signIn, signOut, signUp, + authFetch, refresh: getSession } } From be76502f857b6b3cbb9878128029833cc65af12f Mon Sep 17 00:00:00 2001 From: Mohamed Mansour Date: Thu, 4 Jul 2024 18:11:31 +0300 Subject: [PATCH 2/4] refactor: remove any type --- src/runtime/composables/local/useAuth.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/runtime/composables/local/useAuth.ts b/src/runtime/composables/local/useAuth.ts index ac36b1ae..e1ab9371 100644 --- a/src/runtime/composables/local/useAuth.ts +++ b/src/runtime/composables/local/useAuth.ts @@ -138,7 +138,7 @@ const signUp = async (credentials: Credentials, signInOptions?: SecondarySignInO return signIn(credentials, signInOptions) } -const authFetch = async (path: string, options: Parameters[1]): Promise => { +const authFetch = async (path: string, options: Parameters[1]) => { const nuxt = useNuxtApp() const config = useTypedBackendConfig(useRuntimeConfig(), 'local') @@ -153,7 +153,7 @@ const authFetch = async (path: string, options: Parameters[1]): P ...(token ? { [config.token.headerName]: token } : {}) // append auth token if it exists } try { - return await _fetch(nuxt, path, { ...options, headers }) + return await _fetch(nuxt, path, { ...options, headers }) } catch (err) { console.error(`Error during authenticated fetch:, ${(err as Error).message}`) } From cf2594486dc03484e3400fea5b9bf60b2a6303c5 Mon Sep 17 00:00:00 2001 From: Mohamed Mansour Date: Thu, 4 Jul 2024 18:29:52 +0300 Subject: [PATCH 3/4] feat: add docs for authFetch --- .../3.application-side/2.session-access-and-management.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/content/3.application-side/2.session-access-and-management.md b/docs/content/3.application-side/2.session-access-and-management.md index aba38dc4..03c81f28 100644 --- a/docs/content/3.application-side/2.session-access-and-management.md +++ b/docs/content/3.application-side/2.session-access-and-management.md @@ -62,6 +62,7 @@ const { signUp, signIn, signOut, + authFetch, } = useAuth() // Session status, either `unauthenticated`, `loading`, `authenticated` @@ -108,6 +109,9 @@ await signOut() // Trigger a sign-out and send the user to the sign-out page afterwards await signOut({ callbackUrl: '/signout' }) + +// Make an authenticated fetch that passes the token in request headers (options is a NitroFetchOptions object https://nuxt.com/docs/api/composables/use-fetch#params) +await authFetch(path, options) ``` ```ts [refresh] const { From 7535c4bdff6d7e01dfc7a6475ad8d710bfe4ec2d Mon Sep 17 00:00:00 2001 From: Mohamed Mansour Date: Thu, 4 Jul 2024 19:04:53 +0300 Subject: [PATCH 4/4] feat: add a test for authFetch --- playground-local/app.vue | 6 +++++- playground-local/tests/local.spec.ts | 9 ++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/playground-local/app.vue b/playground-local/app.vue index c80b0ca0..860ce4a6 100644 --- a/playground-local/app.vue +++ b/playground-local/app.vue @@ -2,7 +2,7 @@ import { ref } from 'vue' import { useAuth } from '#imports' -const { signIn, token, data, status, lastRefreshedAt, signOut, getSession } = useAuth() +const { signIn, token, data, status, lastRefreshedAt, signOut, getSession, authFetch } = useAuth() const username = ref('') const password = ref('') @@ -32,6 +32,10 @@ const password = ref('') sign out
+ +
diff --git a/playground-local/tests/local.spec.ts b/playground-local/tests/local.spec.ts index 73488e7f..b273ee7d 100644 --- a/playground-local/tests/local.spec.ts +++ b/playground-local/tests/local.spec.ts @@ -11,7 +11,7 @@ describe('Local Provider', async () => { browser: true }) - test('load, sign in, reload, refresh, sign out', async () => { + test('load, sign in, reload, refresh, auth fetch, sign out', async () => { const page = await createPage('/') const [ usernameInput, @@ -19,6 +19,7 @@ describe('Local Provider', async () => { submitButton, status, signoutButton, + authFetchButton, refreshRequiredFalseButton, refreshRequiredTrueButton ] = await Promise.all([ @@ -27,6 +28,7 @@ describe('Local Provider', async () => { page.getByTestId('submit'), page.getByTestId('status'), page.getByTestId('signout'), + page.getByTestId('auth-fetch'), page.getByTestId('refresh-required-false'), page.getByTestId('refresh-required-true') ]) @@ -47,6 +49,11 @@ describe('Local Provider', async () => { await page.reload() await playwrightExpect(status).toHaveText(STATUS_AUTHENTICATED) + // Ensure that a manual authenticated fetch is successful while authenticated + const authFetchResponse = page.waitForResponse(/\/api\/auth\/user/) + await authFetchButton.click() + await playwrightExpect((await authFetchResponse).status()).toBe(200) + // Refresh (required: false), status should not change await refreshRequiredFalseButton.click() await playwrightExpect(status).toHaveText(STATUS_AUTHENTICATED)