Skip to content

Commit

Permalink
Merge pull request #429 from Baroshem/feat/unified-router-context
Browse files Browse the repository at this point in the history
feat(core): unified router context
  • Loading branch information
Baroshem committed Apr 26, 2024
2 parents 15bc896 + ea7d63a commit 15142c0
Show file tree
Hide file tree
Showing 71 changed files with 4,683 additions and 3,879 deletions.
34 changes: 28 additions & 6 deletions docs/components/content/Illustration.vue
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
@@ -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
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
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

0 comments on commit 15142c0

Please sign in to comment.