Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(core): unified router context #429

Merged
merged 22 commits into from
Apr 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 28 additions & 6 deletions docs/components/content/Illustration.vue
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,10 @@
filterUnits="userSpaceOnUse"
color-interpolation-filters="sRGB"
>
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<feFlood
flood-opacity="0"
result="BackgroundImageFix"
/>
<feColorMatrix
in="SourceAlpha"
type="matrix"
Expand All @@ -236,7 +239,10 @@
/>
<feOffset dy="4" />
<feGaussianBlur stdDeviation="2" />
<feComposite in2="hardAlpha" operator="out" />
<feComposite
in2="hardAlpha"
operator="out"
/>
<feColorMatrix
type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.44 0"
Expand All @@ -262,7 +268,11 @@
gradientUnits="userSpaceOnUse"
>
<stop stop-color="white" />
<stop offset="1" stop-color="white" stop-opacity="0" />
<stop
offset="1"
stop-color="white"
stop-opacity="0"
/>
</linearGradient>
<linearGradient
id="paint1_linear_1180_138"
Expand All @@ -273,7 +283,11 @@
gradientUnits="userSpaceOnUse"
>
<stop stop-color="white" />
<stop offset="1" stop-color="white" stop-opacity="0" />
<stop
offset="1"
stop-color="white"
stop-opacity="0"
/>
</linearGradient>
<linearGradient
id="paint2_linear_1180_138"
Expand All @@ -284,10 +298,18 @@
gradientUnits="userSpaceOnUse"
>
<stop stop-color="white" />
<stop offset="1" stop-color="white" stop-opacity="0" />
<stop
offset="1"
stop-color="white"
stop-opacity="0"
/>
</linearGradient>
<clipPath id="clip0_1180_138">
<rect width="403" height="226" fill="white" />
<rect
width="403"
height="226"
fill="white"
/>
</clipPath>
</defs>
</svg>
Expand Down
9 changes: 6 additions & 3 deletions docs/components/content/Releases.vue
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
<template>
<GithubReleases v-slot="{ releases }">
<div v-for="release in releases" :key="release.name">
<div
v-for="release in releases"
:key="release.name"
>
<ProseH2 :id="release.name">
<Badge :type="release.prerelease ? 'warning' : 'info'">
{{ release.prerelease ? "Pre-release" : "Release" }} </Badge
>{{ release.name }}
{{ release.prerelease ? "Pre-release" : "Release" }}
</Badge>{{ release.name }}
</ProseH2>
<details>
<summary>
Expand Down
151 changes: 114 additions & 37 deletions docs/content/1.documentation/1.getting-started/3.usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ Nuxt Security by default registers a set of **global** Nuxt `routeRules` that wi

## Global configuration

To override default behavior for Nuxt Security globally, follow this pattern:
To override the default behavior for Nuxt Security globally, follow this pattern:

```ts
```ts{}[nuxt.config.ts]
export default defineNuxtConfig({
security: {
headers: {
Expand All @@ -36,7 +36,7 @@ export default defineNuxtConfig({

To enable per-route configuration, use the `routeRules` like following:

```ts
```ts{}[nuxt.config.ts]
export default defineNuxtConfig({
routeRules: {
'/custom-route': {
Expand Down Expand Up @@ -74,11 +74,96 @@ If your application defines conflicting headers at both levels, the `security` p

For more information on `routeRules` please see the [Nuxt documentation](https://nuxt.com/docs/guide/concepts/rendering#hybrid-rendering)

## Nested route configuration

Nuxt Security will recursively resolve nested routes using your `routeRules` definitions:
## Runtime hooks

```ts
If you need to change the configuration at runtime, it is possible to do it through the `nuxt-security:routeRules` hook.

In order to use the runtime hooks feature, you will need to create a Nitro plugin.

In the `server/plugins` directory, create a new file with the name of your choice:

```ts{}[server/plugins/filename.ts]
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('nuxt-security:routeRules', async(routeRules) => {
// You can fetch configuration data asynchronously from an external source
const validDomain = await $fetch('https://some-site.com/rules')
// You can then override the security options of any route
routeRules['/some/route'] = {
headers: {
contentSecurityPolicy: {
"connect-src": ["'self'", validDomain]
},
xFrameOptions: false
},
hidePoweredBy: false
}
})
})
```

## Configuration priority order

Nuxt-Security applies your rules in the following prority order:


1. Default rules

Nuxt-Security default values.
See [here](/documentation/getting-started/configuration#default)


2. Inline module options

```ts{}[nuxt.config.ts]
export default defineNuxtConfig({
modules: [
['nuxt-security', { /* Inline Options */ }]
]
})
```


3. Global module options

```ts{}[nuxt.config.ts]
export default defineNuxtConfig({
security: {
// Global Options
}
})
```

4. Per-route options

```ts{}[nuxt.config.ts]
export default defineNuxtConfig({
routeRules: {
'/some-route': {
security: {
// Per-route Options
}
}
}
})
```

5. Runtime-hook options

```ts{}[server/plugins/filename.ts]
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('nuxt-security:routeRules', routeRules => {
// Runtime Options
})
})
```


## Route merging strategy (nested router)

If you define nested route rules in your `routeRules` definitions, Nuxt Security will recursively merge the options to resolve the security rules of a given route:

```ts{}[nuxt.config.ts]
export default defineNuxtConfig({
// Global
security: {
Expand Down Expand Up @@ -146,7 +231,7 @@ experimental: {

To disable certain middleware or headers, follow this pattern:

```ts
```ts{}[nuxt.config.ts]
export default defineNuxtConfig({
// global
security: {
Expand All @@ -170,40 +255,32 @@ export default defineNuxtConfig({
})
```

## Runtime configuration

If you need to change the headers configuration at runtime, it is possible to do it through `nuxt-security:headers` hook.
## Overwriting or modifying existing values

### Enabling the option
Within your runtime hooks, you can either overwrite or modify the existing values for any security option.
One of the easiest way to merge existing rules with your own is to use `defu`:

This feature is optional, you can enable it with
```ts{}[server/plugins/filename.ts]
import defu from 'defu'

```ts
export default defineNuxtConfig({
modules: ['nuxt-security'],
security: {
runtimeHooks: true
}
})
```

### Usage

Within your nitro plugin. You can override the previous configuration of a route with `nuxt-security:headers`.

```ts
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('nuxt-security:ready', () => {
nitroApp.hooks.callHook('nuxt-security:headers',
{
route: '/**',
headers: {
contentSecurityPolicy: {
"script-src": ["'self'", "'unsafe-inline'"],
},
xFrameOptions: false
}
})
})
nitroApp.hooks.hook('nuxt-security:routeRules', async(routeRules) => {
// You can fetch configuration data asynchronously from an external source
const validDomain = await $fetch('https://some-site.com/rules')
// You can then override the security options of any route
routeRules['/some/route'] = defu(
{
headers: {
contentSecurityPolicy: {
"connect-src": ["'self'", validDomain]
},
xFrameOptions: false
},
hidePoweredBy: false
},
routeRules['/some/route']
)
})
})
```
78 changes: 31 additions & 47 deletions docs/content/1.documentation/2.headers/1.csp.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,46 +54,34 @@ The `contentSecurityPolicy` header can be configured with following values.

```ts
contentSecurityPolicy: {
'child-src'?: CSPSourceValue[] | string | false;
'connect-src'?: CSPSourceValue[] | string | false;
'default-src'?: CSPSourceValue[] | string | false;
'font-src'?: CSPSourceValue[] | string | false;
'frame-src'?: CSPSourceValue[] | string | false;
'img-src'?: CSPSourceValue[] | string | false;
'manifest-src'?: CSPSourceValue[] | string | false;
'media-src'?: CSPSourceValue[] | string | false;
'object-src'?: CSPSourceValue[] | string | false;
'prefetch-src'?: CSPSourceValue[] | string | false;
'script-src'?: CSPSourceValue[] | string | false;
'script-src-elem'?: CSPSourceValue[] | string | false;
'script-src-attr'?: CSPSourceValue[] | string | false;
'style-src'?: CSPSourceValue[] | string | false;
'style-src-elem'?: CSPSourceValue[] | string | false;
'style-src-attr'?: CSPSourceValue[] | string | false;
'worker-src'?: CSPSourceValue[] | string | false;
'base-uri'?: CSPSourceValue[] | string | false;
'sandbox'?: CSPSandboxValue[] | string | false;
'form-action'?: CSPSourceValue[] | string | false;
'frame-ancestors'?: ("'self'" | "'none'" | string)[] | string | false;
'navigate-to'?: ("'self'" | "'none'" | "'unsafe-allow-redirects'" | string)[] | string | false;
'report-uri'?: string[] | string | false;
'child-src'?: CSPSourceValue[] false;
'connect-src'?: CSPSourceValue[] | false;
'default-src'?: CSPSourceValue[] | false;
'font-src'?: CSPSourceValue[] | false;
'frame-src'?: CSPSourceValue[] | false;
'img-src'?: CSPSourceValue[] | false;
'manifest-src'?: CSPSourceValue[] | false;
'media-src'?: CSPSourceValue[] | false;
'object-src'?: CSPSourceValue[] | false;
'prefetch-src'?: CSPSourceValue[] | false;
'script-src'?: CSPSourceValue[] | false;
'script-src-elem'?: CSPSourceValue[] | false;
'script-src-attr'?: CSPSourceValue[] | false;
'style-src'?: CSPSourceValue[] | false;
'style-src-elem'?: CSPSourceValue[] | false;
'style-src-attr'?: CSPSourceValue[] | false;
'worker-src'?: CSPSourceValue[] | false;
'base-uri'?: CSPSourceValue[] | false;
'sandbox'?: CSPSandboxValue[] | false;
'form-action'?: CSPSourceValue[] | false;
'frame-ancestors'?: ("'self'" | "'none'" | string)[] | false;
'navigate-to'?: ("'self'" | "'none'" | "'unsafe-allow-redirects'" | string)[] | false;
'report-uri'?: string[] | false;
'report-to'?: string | false;
'upgrade-insecure-requests'?: boolean;
} | false
```

::callout
#summary
Array and String syntaxes
#content
Directives can be written using the array syntax or the string syntax.

- Array syntax for clear definition of policies: `"script-src": ["'self'", "https:", "'unsafe-inline'"]`
- String syntax if you prefer to stick with native MDN syntax: `"script-src": "'self' https: 'unsafe-inline'"`

Please note that these two syntaxes behave differently for deeply nested route definitions: see [Per-route Configuration](#per-route-configuration)
::

::callout
#summary
CSPSourceValue type
Expand Down Expand Up @@ -342,7 +330,10 @@ Note that this is not necessary if you use our default configuration settings.

## Per-route configuration

All Content Security Policy options can be defined on a per-route level.
All Content Security Policy options can be defined on a per-route level.

As examplified below, Nuxt Security resolves the `contentSecurityPolicy` options substitutively at each nested level.

```ts
export default defineNuxtConfig({
// Global
Expand All @@ -367,17 +358,17 @@ export default defineNuxtConfig({
'/some-prefix/some-route/**': {
security: {
headers: {
contentSecurityPolicy: { // With array syntax : additive
'img-src': ["https:"] // Self-hosted AND https: images can be loaded for routes beginning with /some-prefix/some-route/
contentSecurityPolicy: {
'img-src': ["'self'", "https:"] // Self-hosted AND https: images can be loaded for routes beginning with /some-prefix/some-route/
}
}
}
},
'/some-prefix/some-route/some-page': {
security: {
headers: {
contentSecurityPolicy: { // With string syntax : substitutive
'img-src': 'self' // ONLY self-hosted images can be loaded on /some-prefix/some-route/some-page
contentSecurityPolicy: {
'img-src': ["'self'"] // ONLY self-hosted images can be loaded on /some-prefix/some-route/some-page
}
}
}
Expand All @@ -386,13 +377,6 @@ export default defineNuxtConfig({
})
```

Nuxt Security resolves the `contentSecurityPolicy` options using the native Nitro router strategy:
- **Additive merging** with the array syntax: If you write your rules with the array syntax (e.g. `"img-src": ["'self'", "https:"]`), the new route policies will be added to the policies defined for higher-level routes.
Use this strategy if you need to add specific policy values to your route without deleting the existing ones.

- **Substitutive merging** with the string syntax: If you write your rules with the string syntax (e.g. `"img-src": "'self' https:"`), the new route policies will be substituted to the policies defined for higher-level routes.
Use this strategy if you need to delete existing policies before setting your specific route policies.


## Including External Scripts

Expand Down
Loading
Loading