Skip to content

Commit fee6e26

Browse files
authored
export handleHttpResponseHeaders hook (#39)
1 parent 421cf74 commit fee6e26

13 files changed

+128
-65
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ node_modules
99
!.env.example
1010
vite.config.js.timestamp-*
1111
vite.config.ts.timestamp-*
12+
/coverage

.prettierignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,6 @@ yarn.lock
99

1010
# Changesets
1111
.changeset
12+
13+
# Code coverage
14+
/coverage

README.md

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,38 +12,33 @@ Adds HTTP headers to page responses from any SvelteKit web application enhancing
1212
npm install @faranglao/sveltekit-security-headers
1313
```
1414

15-
### Getting Started
15+
## Getting Started
1616

1717
To add HTTP Security Response Headers to a SvelteKit application follow these steps:
1818

1919
1. Install the `@faranglao/sveltekit-security-headers` package using `npm install @faranglao/sveltekit-security-headers`.
2020

21-
1. Add or update [src/hooks.server.ts](./src/hooks.server.ts) file:
21+
2. Add the `handleHttpResponseHeaders` Hook in [src/hooks.server.ts](./src/hooks.server.ts):
2222

23-
- Scenario: no previous Hooks defined in `src/hooks.server.ts`:
23+
- Scenario 1: no previous `handle` Hook defined in `src/hooks.server.ts`:
2424

2525
```ts
2626
import type { Handle } from '@sveltejs/kit';
27-
import { HttpResponseHeaders } from '@faranglao/sveltekit-security-headers';
27+
import { handleHttpResponseHeaders } from '@faranglao/sveltekit-security-headers';
2828

29-
export const handle: Handle = HttpResponseHeaders.handle;
29+
export const handle: Handle = handleHttpResponseHeaders;
3030
```
3131

32-
- Scenario: existing Hooks defined in `src/hooks.server.ts`:
32+
- Scenario 2: existing `handle` Hook defined in `src/hooks.server.ts`:
3333

34-
Use [the sequence helper function](https://kit.svelte.dev/docs/modules#sveltejs-kit-hooks) to wrap the existing hook and `HttpResponseHeaders.handle` hook as shown below.
34+
Use [the sequence helper function](https://kit.svelte.dev/docs/modules#sveltejs-kit-hooks) to wrap the existing hook and `handleHttpResponseHeaders` handler as shown below.
3535

3636
```ts
3737
import type { Handle } from '@sveltejs/kit';
3838
import { sequence } from '@sveltejs/kit/hooks';
39-
import { HttpResponseHeaders } from '@faranglao/sveltekit-security-headers';
40-
41-
export const handle: Handle = sequence(async ({ event, resolve }) => {
42-
// Do something with the inbound request
43-
const response = await resolve(event);
44-
// Do something with the outbound response before returning it
45-
return response;
46-
}, HttpResponseHeaders.handle);
39+
import { handleHttpResponseHeaders } from '@faranglao/sveltekit-security-headers';
40+
41+
export const handle: Handle = sequence(existingHook, handleHttpResponseHeaders);
4742
```
4843

4944
Then run the web application using `npm run dev` or `npm run build && npm run preview`.

package-lock.json

Lines changed: 31 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"package": "svelte-kit sync && svelte-package && publint",
2929
"prepublishOnly": "npm run package",
3030
"test": "npm run test:integration && npm run test:unit",
31+
"coverage": "vitest run --coverage",
3132
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
3233
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
3334
"lint": "prettier --check . && eslint .",
@@ -71,7 +72,8 @@
7172
"tslib": "^2.4.1",
7273
"typescript": "^5.0.0",
7374
"vite": "^5.1.1",
74-
"vitest": "^1.2.0"
75+
"vitest": "^1.2.0",
76+
"vitest-mock-extended": "^1.3.1"
7577
},
7678
"svelte": "./dist/index.js",
7779
"types": "./dist/index.d.ts"

src/hooks.server.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { handle } from './hooks.server.js';
3+
4+
describe('Server Hooks', () => {
5+
describe('handle function', () => {
6+
it('is defined', () => {
7+
expect(handle).toBeDefined();
8+
});
9+
});
10+
});

src/hooks.server.ts

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,12 @@
1-
// Scenario: no previous Hooks defined in src/hooks.server.ts
2-
import { HttpResponseHeaders } from './lib/headers.js';
1+
// src/hooks.server.ts
2+
import { handleHttpResponseHeaders } from './lib/hook.js';
33
import type { Handle } from '@sveltejs/kit';
44

5-
export const handle: Handle = HttpResponseHeaders.handle;
6-
7-
// Scenario: existing Hooks defined in src/hooks.server.ts:
8-
// import { HttpResponseHeaders } from "$lib/headers.js";
9-
// import type { Handle } from "@sveltejs/kit";
5+
// import sequence for scenario 2
106
// import { sequence } from "@sveltejs/kit/hooks";
117

12-
// export const handle: Handle = sequence( async ( { event, resolve } ) => {
13-
// // Do something with the inbound request
14-
// const response = await resolve( event );
15-
// // Do something with the response before returning it
16-
// return response;
17-
// }, HttpResponseHeaders.handle);
8+
// scenario 1: no previous handle Hook defined
9+
export const handle: Handle = handleHttpResponseHeaders;
10+
11+
// scenario 2: existing handle Hook defined
12+
// export const handle: Handle = sequence( existingHook, handleHttpResponseHeaders );

src/lib/headers.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import type { Handle } from '@sveltejs/kit';
21
import type { SecurityHeader } from './types.js';
32

43
const Rules = {
@@ -10,7 +9,10 @@ const Rules = {
109
]
1110
};
1211

13-
const applySecurityHeaders = (headers: Headers, securityHeaders: SecurityHeader[]) => {
12+
const applySecurityHeaders = (
13+
headers: Headers,
14+
securityHeaders: SecurityHeader[] = Rules.SecurityHeaders
15+
) => {
1416
securityHeaders.forEach((header) => {
1517
if (header.value !== undefined) {
1618
const currentValue = headers.get(header.name);
@@ -32,14 +34,7 @@ const applySecurityHeaders = (headers: Headers, securityHeaders: SecurityHeader[
3234
});
3335
};
3436

35-
const applySecurityHeadersHandler: Handle = async ({ event, resolve }) => {
36-
const response = await resolve(event);
37-
applySecurityHeaders(response.headers, Rules.SecurityHeaders);
38-
return response;
39-
};
40-
4137
export const HttpResponseHeaders = {
42-
handle: applySecurityHeadersHandler,
4338
applySecurityHeaders,
4439
Rules
4540
};

src/lib/hook.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/* eslint-disable @typescript-eslint/no-unused-vars */
2+
import { describe, it, expect, beforeEach } from 'vitest';
3+
import type { RequestEvent } from '@sveltejs/kit';
4+
import { mockDeep, type DeepMockProxy } from 'vitest-mock-extended';
5+
import { handleHttpResponseHeaders } from './hook.js';
6+
7+
describe('handleHttpResponseHeaders', () => {
8+
let mockEvent: DeepMockProxy<RequestEvent>;
9+
let mockResponse: DeepMockProxy<Response>;
10+
11+
beforeEach(() => {
12+
mockEvent = mockDeep<RequestEvent>();
13+
mockResponse = mockDeep<Response>();
14+
});
15+
16+
it('should apply security headers to response', async () => {
17+
const event = mockEvent;
18+
const resolve = () => mockResponse;
19+
20+
await handleHttpResponseHeaders({ event, resolve });
21+
22+
expect(mockResponse.headers.set).toHaveBeenCalledWith('X-Frame-Options', 'DENY');
23+
expect(mockResponse.headers.set).toHaveBeenCalledWith('X-Content-Type-Options', 'nosniff');
24+
expect(mockResponse.headers.set).toHaveBeenCalledWith(
25+
'Referrer-Policy',
26+
'strict-origin-when-cross-origin'
27+
);
28+
expect(mockResponse.headers.set).toHaveBeenCalledWith(
29+
'Permissions-Policy',
30+
'geolocation=(), camera=(), microphone=()'
31+
);
32+
});
33+
});

src/lib/hook.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import type { Handle } from '@sveltejs/kit';
2+
import { HttpResponseHeaders } from './headers.js';
3+
4+
export const handleHttpResponseHeaders: Handle = async ({ event, resolve }) => {
5+
const response = await resolve(event);
6+
HttpResponseHeaders.applySecurityHeaders(response.headers);
7+
return response;
8+
};

0 commit comments

Comments
 (0)