Skip to content

Commit

Permalink
Merge pull request #63 from manchenkoff/3-global-app-middleware
Browse files Browse the repository at this point in the history
feat: implemented global middleware
  • Loading branch information
manchenkoff authored Apr 1, 2024
2 parents f0f273f + 6322fd3 commit cfb7023
Show file tree
Hide file tree
Showing 11 changed files with 173 additions and 53 deletions.
6 changes: 0 additions & 6 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,6 @@ yarn lint
yarn types
```

To fix the issues automatically, you can run the following command:

```bash
yarn fix:fmt
```

# Releasing

Once all the changes are merged into main branch, run the following command to release a new version:
Expand Down
46 changes: 46 additions & 0 deletions playground/error.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<script setup lang="ts">
import { useSanctumAuth } from '#imports';
interface ErrorProps {
error?: {
url: string;
statusCode: number;
statusMessage: string;
message: string;
description: string;
data?: any;
};
}
const props = defineProps<ErrorProps>();
const { isAuthenticated, logout } = useSanctumAuth();
</script>

<template>
<NuxtLoadingIndicator />

<h1>Nuxt - Laravel Sanctum application sample</h1>

<nav class="menu">
<NuxtLink to="/">Home</NuxtLink>
<NuxtLink to="/login">Login</NuxtLink>
<NuxtLink to="/logout">Logout</NuxtLink>
<NuxtLink to="/profile">Profile</NuxtLink>
<NuxtLink to="/welcome">Welcome</NuxtLink>

<button v-if="isAuthenticated" @click="logout">Logout</button>
</nav>

<hr />

<strong>Error</strong>
<p>{{ props.error }}</p>
</template>

<style scoped>
.menu {
display: flex;
gap: 1rem;
}
</style>
1 change: 1 addition & 0 deletions playground/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{
"extends": "./.nuxt/tsconfig.json",
"exclude": ["../dist"],
}
6 changes: 6 additions & 0 deletions src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,9 @@ declare module 'nuxt/schema' {
sanctum: Partial<SanctumModuleOptions>;
}
}

declare module '#app' {
interface PageMeta {
excludeFromSanctum?: boolean;
}
}
48 changes: 31 additions & 17 deletions src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ import {
import { defu } from 'defu';
import type { SanctumModuleOptions } from './types';

export default defineNuxtModule<Partial<SanctumModuleOptions>>({
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};

export default defineNuxtModule<DeepPartial<SanctumModuleOptions>>({
meta: {
name: 'nuxt-auth-sanctum',
configKey: 'sanctum',
Expand Down Expand Up @@ -40,30 +44,40 @@ export default defineNuxtModule<Partial<SanctumModuleOptions>>({
onAuthOnly: '/login',
onGuestOnly: '/',
},
globalMiddleware: {
enabled: false,
allow404WithoutAuth: true,
},
},

setup(options, nuxt) {
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
);
const runtimeConfigOverrides =
nuxt.options.runtimeConfig.public.sanctum;

addImportsDir(resolver.resolve('./runtime/composables'));
const sanctumConfig = defu(runtimeConfigOverrides as any, options);

addRouteMiddleware({
name: 'sanctum:auth',
path: resolver.resolve('./runtime/middleware/sanctum.auth'),
});
addRouteMiddleware({
name: 'sanctum:guest',
path: resolver.resolve('./runtime/middleware/sanctum.guest'),
});
nuxt.options.runtimeConfig.public.sanctum = sanctumConfig;

addPlugin(resolver.resolve('./runtime/plugin'));
addImportsDir(resolver.resolve('./runtime/composables'));

if (sanctumConfig.globalMiddleware.enabled) {
addRouteMiddleware({
name: 'sanctum:auth:global',
path: resolver.resolve('./runtime/middleware/sanctum.global'),
global: true,
});
} else {
addRouteMiddleware({
name: 'sanctum:auth',
path: resolver.resolve('./runtime/middleware/sanctum.auth'),
});
addRouteMiddleware({
name: 'sanctum:guest',
path: resolver.resolve('./runtime/middleware/sanctum.guest'),
});
}
},
});
24 changes: 11 additions & 13 deletions src/runtime/httpFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@ import {
useCookie,
useRequestEvent,
useRequestHeaders,
useRequestURL,
navigateTo,
useNuxtApp,
} from '#app';
import { useSanctumUser } from './composables/useSanctumUser';
import { useRequestURL } from 'nuxt/app';
import { useSanctumConfig } from './composables/useSanctumConfig';

export const SECURE_METHODS = new Set(['post', 'delete', 'put', 'patch']);
type Headers = HeadersInit | undefined;

const SECURE_METHODS = new Set(['post', 'delete', 'put', 'patch']);
const COOKIE_OPTIONS: { readonly: true } = { readonly: true };

export function createHttpClient(): $Fetch {
const options = useSanctumConfig();
Expand All @@ -23,17 +26,13 @@ export function createHttpClient(): $Fetch {
* @param headers Headers collection to extend
* @returns {HeadersInit}
*/
async function buildClientHeaders(
headers: HeadersInit | undefined
): Promise<HeadersInit> {
async function buildClientHeaders(headers: Headers): Promise<HeadersInit> {
await $fetch(options.endpoints.csrf, {
baseURL: options.baseUrl,
credentials: 'include',
});

const csrfToken = useCookie(options.csrf.cookie, {
readonly: true,
}).value;
const csrfToken = useCookie(options.csrf.cookie, COOKIE_OPTIONS).value;

return {
...headers,
Expand All @@ -46,10 +45,8 @@ export function createHttpClient(): $Fetch {
* @param headers Headers collection to extend
* @returns { HeadersInit }
*/
function buildServerHeaders(headers: HeadersInit | undefined): HeadersInit {
const csrfToken = useCookie(options.csrf.cookie, {
readonly: true,
}).value;
function buildServerHeaders(headers: Headers): HeadersInit {
const csrfToken = useCookie(options.csrf.cookie, COOKIE_OPTIONS).value;
const clientCookies = useRequestHeaders(['cookie']);
const origin = options.origin ?? useRequestURL().origin;

Expand Down Expand Up @@ -123,7 +120,8 @@ export function createHttpClient(): $Fetch {
if (
options.redirect.onLogout === false ||
options.redirect.onLogout === currentRoute.path ||
options.redirect.onAuthOnly === currentRoute.path
options.redirect.onAuthOnly === currentRoute.path ||
options.globalMiddleware.enabled === true
) {
return;
}
Expand Down
8 changes: 3 additions & 5 deletions src/runtime/middleware/sanctum.auth.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import { defineNuxtRouteMiddleware, navigateTo, createError } from '#app';
import type { RouteLocationRaw } from 'vue-router';
import { useSanctumUser } from '../composables/useSanctumUser';
import { useSanctumConfig } from '../composables/useSanctumConfig';
import { useSanctumAuth } from '../composables/useSanctumAuth';

export default defineNuxtRouteMiddleware((to) => {
const user = useSanctumUser();
const options = useSanctumConfig();
const { isAuthenticated } = useSanctumAuth();

const isAuthenticated = user.value !== null;

if (isAuthenticated === true) {
if (isAuthenticated.value === true) {
return;
}

Expand Down
53 changes: 53 additions & 0 deletions src/runtime/middleware/sanctum.global.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { defineNuxtRouteMiddleware, navigateTo } from '#app';
import type { RouteLocationRaw } from 'vue-router';
import { useSanctumConfig } from '../composables/useSanctumConfig';
import { useSanctumAuth } from '../composables/useSanctumAuth';

export default defineNuxtRouteMiddleware((to) => {
const options = useSanctumConfig();
const { isAuthenticated } = useSanctumAuth();

const [homePage, loginPage] = [
options.redirect.onGuestOnly,
options.redirect.onAuthOnly,
];

if (homePage === false) {
throw new Error(
'You must define onGuestOnly route when using global middleware.'
);
}

if (loginPage === false) {
throw new Error(
'You must define onAuthOnly route when using global middleware.'
);
}

if (isAuthenticated.value === true) {
if (to.path === loginPage) {
return navigateTo(homePage, { replace: true });
}

return;
}

if (to.path === loginPage || to.meta.excludeFromSanctum === true) {
return;
}

if (
options.globalMiddleware.allow404WithoutAuth &&
to.matched.length === 0
) {
return;
}

const redirect: RouteLocationRaw = { path: loginPage };

if (options.redirect.keepRequestedRoute) {
redirect.query = { redirect: to.fullPath };
}

return navigateTo(redirect, { replace: true });
});
8 changes: 3 additions & 5 deletions src/runtime/middleware/sanctum.guest.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import { defineNuxtRouteMiddleware, navigateTo, createError } from '#app';
import { useSanctumAuth } from '../composables/useSanctumAuth';
import { useSanctumConfig } from '../composables/useSanctumConfig';
import { useSanctumUser } from '../composables/useSanctumUser';

export default defineNuxtRouteMiddleware(() => {
const user = useSanctumUser();
const options = useSanctumConfig();
const { isAuthenticated } = useSanctumAuth();

const isAuthenticated = user.value !== null;

if (isAuthenticated === false) {
if (isAuthenticated.value === false) {
return;
}

Expand Down
18 changes: 18 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,20 @@ export interface RedirectOptions {
onGuestOnly: string | false;
}

/**
* Configuration of the global application-wide middleware.
*/
export interface GlobalMiddlewareOptions {
/**
* Determines whether the global middleware is enabled.
*/
enabled: boolean;
/**
* Determines whether to allow 404 pages without authentication.
*/
allow404WithoutAuth: boolean;
}

/**
* Options to be passed to the plugin.
*/
Expand Down Expand Up @@ -110,4 +124,8 @@ export interface SanctumModuleOptions {
* Behavior of the plugin redirects when user is authenticated or not.
*/
redirect: RedirectOptions;
/**
* Behavior of the global middleware.
*/
globalMiddleware: GlobalMiddlewareOptions;
}
8 changes: 1 addition & 7 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
{
"extends": "./.nuxt/tsconfig.json",
"exclude": [
"./node_modules",
"./node_modules/nuxt/node_modules",
"./dist",
"./.output",
"./playground",
],
"exclude": ["./node_modules", "./dist", "./playground"],
}

0 comments on commit cfb7023

Please sign in to comment.