-
-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: added support for custom fetch interceptors
- Loading branch information
1 parent
0a75a4c
commit 619388a
Showing
5 changed files
with
253 additions
and
149 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import type { NuxtApp } from '#app'; | ||
import type { FetchContext } from 'ofetch'; | ||
import type { ConsolaInstance } from 'consola'; | ||
import { useSanctumUser } from '../../composables/useSanctumUser'; | ||
import { useSanctumConfig } from '../../composables/useSanctumConfig'; | ||
|
||
export default async function handleResponseError( | ||
app: NuxtApp, | ||
ctx: FetchContext, | ||
logger: ConsolaInstance | ||
) { | ||
if (ctx.response === undefined) { | ||
logger.debug('No response to process'); | ||
|
||
return; | ||
} | ||
|
||
const user = useSanctumUser(); | ||
const config = useSanctumConfig(); | ||
|
||
if (ctx.response.status === 419) { | ||
logger.warn('CSRF token mismatch, check your API configuration'); | ||
|
||
return; | ||
} | ||
|
||
if ( | ||
ctx.response.status === 401 && | ||
ctx.request.toString().endsWith(config.endpoints.user) && | ||
user.value !== null | ||
) { | ||
logger.warn( | ||
'User session is not set in API or expired, resetting identity' | ||
); | ||
user.value = null; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { type NuxtApp } from '#app'; | ||
import type { FetchContext } from 'ofetch'; | ||
|
||
export default async function handleRequestHeaders( | ||
app: NuxtApp, | ||
ctx: FetchContext | ||
) { | ||
const method = ctx.options.method?.toLowerCase() ?? 'get'; | ||
|
||
ctx.options.headers = { | ||
Accept: 'application/json', | ||
...ctx.options.headers, | ||
}; | ||
|
||
// https://laravel.com/docs/10.x/routing#form-method-spoofing | ||
if (method === 'put' && ctx.options.body instanceof FormData) { | ||
ctx.options.method = 'POST'; | ||
ctx.options.body.append('_method', 'PUT'); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
import { | ||
useCookie, | ||
useRequestHeaders, | ||
useRequestURL, | ||
type NuxtApp, | ||
} from '#app'; | ||
import type { FetchContext } from 'ofetch'; | ||
import type { ConsolaInstance } from 'consola'; | ||
import { useSanctumConfig } from '../../composables/useSanctumConfig'; | ||
import type { SanctumModuleOptions } from '../../types'; | ||
|
||
type Headers = HeadersInit | undefined; | ||
|
||
const SECURE_METHODS = new Set(['post', 'delete', 'put', 'patch']); | ||
const COOKIE_OPTIONS: { readonly: true } = { readonly: true }; | ||
|
||
/** | ||
* Pass all cookies, headers and referrer from the client to the API | ||
* @param headers Headers collection to extend | ||
* @param config Module configuration | ||
* @returns {HeadersInit} | ||
*/ | ||
function buildServerHeaders( | ||
headers: Headers, | ||
config: SanctumModuleOptions | ||
): HeadersInit { | ||
const clientCookies = useRequestHeaders(['cookie']); | ||
const origin = config.origin ?? useRequestURL().origin; | ||
|
||
return { | ||
...headers, | ||
Referer: origin, | ||
Origin: origin, | ||
...(clientCookies.cookie && clientCookies), | ||
}; | ||
} | ||
|
||
/** | ||
* Request a new CSRF cookie from the API | ||
* @param config Module configuration | ||
* @param logger Logger instance | ||
* @returns {Promise<void>} | ||
*/ | ||
async function initCsrfCookie( | ||
config: SanctumModuleOptions, | ||
logger: ConsolaInstance | ||
): Promise<void> { | ||
await $fetch(config.endpoints.csrf, { | ||
baseURL: config.baseUrl, | ||
credentials: 'include', | ||
}); | ||
|
||
logger.debug('CSRF cookie has been initialized'); | ||
} | ||
|
||
/** | ||
* Add CSRF token to the headers collection to pass from the client to the API | ||
* @param headers Headers collection to extend | ||
* @param config Module configuration | ||
* @param logger Logger instance | ||
* @returns {Promise<HeadersInit>} | ||
*/ | ||
async function useCsrfHeader( | ||
headers: Headers, | ||
config: SanctumModuleOptions, | ||
logger: ConsolaInstance | ||
): Promise<HeadersInit> { | ||
let csrfToken = useCookie(config.csrf.cookie, COOKIE_OPTIONS); | ||
|
||
if (!csrfToken.value) { | ||
await initCsrfCookie(config, logger); | ||
|
||
csrfToken = useCookie(config.csrf.cookie, COOKIE_OPTIONS); | ||
} | ||
|
||
if (!csrfToken.value) { | ||
logger.warn( | ||
`${config.csrf.cookie} cookie is missing, unable to set ${config.csrf.header} header` | ||
); | ||
|
||
return headers as HeadersInit; | ||
} | ||
|
||
logger.debug(`Added ${config.csrf.header} header to pass to the API`); | ||
|
||
return { | ||
...headers, | ||
...(csrfToken.value && { | ||
[config.csrf.header]: csrfToken.value, | ||
}), | ||
}; | ||
} | ||
|
||
export default async function handleRequestCookies( | ||
app: NuxtApp, | ||
ctx: FetchContext, | ||
logger: ConsolaInstance | ||
) { | ||
const config = useSanctumConfig(); | ||
const method = ctx.options.method?.toLowerCase() ?? 'get'; | ||
|
||
if (import.meta.server) { | ||
ctx.options.headers = buildServerHeaders(ctx.options.headers, config); | ||
} | ||
|
||
if (SECURE_METHODS.has(method)) { | ||
ctx.options.headers = await useCsrfHeader( | ||
ctx.options.headers, | ||
config, | ||
logger | ||
); | ||
} | ||
} |
Oops, something went wrong.