Skip to content

Commit

Permalink
Import Expect-CT (expect-ct) middleware
Browse files Browse the repository at this point in the history
This imports the [expect-ct package][0] into this repo as part of my
effort to make Helmet a monorepo. You can find its prior history in the
old repo.

Similar to:

* e933c28 which imported
  `dns-prefetch-control`
* `13b496f801ee3c77ae9cf91f13c6838263786cc3` which imported `ienoopen`
  • Loading branch information
EvanHahn committed Jun 26, 2020
1 parent 16243e6 commit d03c555
Show file tree
Hide file tree
Showing 11 changed files with 201 additions and 10 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## Unreleased

### Changed

- `helmet.expectCt` is no longer a separate package. This should have no effect on end users.

## 3.23.2 - 2020-06-23

### Changed
Expand Down
3 changes: 2 additions & 1 deletion index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { IncomingMessage, ServerResponse } from "http";
import expectCt from "./middlewares/expect-ct";
import xDnsPrefetchControl from "./middlewares/x-dns-prefetch-control";
import xDownloadOptions from "./middlewares/x-download-options";
import depd = require("depd");
Expand Down Expand Up @@ -128,7 +129,7 @@ function helmet(options: Readonly<HelmetOptions> = {}) {

helmet.contentSecurityPolicy = require("helmet-csp");
helmet.dnsPrefetchControl = xDnsPrefetchControl;
helmet.expectCt = require("expect-ct");
helmet.expectCt = expectCt;
helmet.frameguard = require("frameguard");
helmet.hidePoweredBy = require("hide-powered-by");
helmet.hsts = require("hsts");
Expand Down
29 changes: 29 additions & 0 deletions middlewares/expect-ct/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Changelog

## Unreleased

### Changed

- If `maxAge` is `undefined`, it will be set to `0`
- If `maxAge` is not an integer, it will be rounded down

## 0.3.0 - 2019-09-01

### Changed

- Dropped support for Node <8
- You must now pass a positive integer for `maxAge` (instead of any positive number)
- You cannot pass `undefined` for `maxAge` (though you can still omit the property)

## 0.2.0 - 2019-05-04

### Added

- TypeScript type definitions. See [helmetjs/helmet#188](https://github.com/helmetjs/helmet/issues/188)
- Additional package metadata (bugs, homepage, etc)

### Changed

- Updated documentation

Changes in versions 0.1.1 and below can be found in [Helmet's changelog](https://github.com/helmetjs/helmet/blob/master/CHANGELOG.md).
29 changes: 29 additions & 0 deletions middlewares/expect-ct/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Expect-CT middleware

The `Expect-CT` HTTP header tells browsers to expect Certificate Transparency. For more, see [this blog post](https://scotthelme.co.uk/a-new-security-header-expect-ct/) and the [article on MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Expect-CT).

Usage:

```javascript
const expectCt = require("expect-ct");

// Sets Expect-CT: max-age=123
app.use(expectCt({ maxAge: 123 }));

// Sets Expect-CT: enforce, max-age=123
app.use(
expectCt({
enforce: true,
maxAge: 123,
})
);

// Sets Expect-CT: enforce, max-age=30, report-uri="https://example.com/report"
app.use(
expectCt({
enforce: true,
maxAge: 30,
reportUri: "https://example.com/report",
})
);
```
51 changes: 51 additions & 0 deletions middlewares/expect-ct/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { IncomingMessage, ServerResponse } from "http";

export interface ExpectCtOptions {
maxAge?: number;
enforce?: boolean;
reportUri?: string;
}

function parseMaxAge(value: void | number): number {
if (value === undefined) {
return 0;
} else if (typeof value === "number" && value >= 0) {
return Math.floor(value);
} else {
throw new Error(
`${value} is not a valid value for maxAge. Please choose a positive integer.`
);
}
}

function getHeaderValueFromOptions(options: Readonly<ExpectCtOptions>): string {
const directives: string[] = [];

if (options.enforce) {
directives.push("enforce");
}

directives.push(`max-age=${parseMaxAge(options.maxAge)}`);

if (options.reportUri) {
directives.push(`report-uri="${options.reportUri}"`);
}

return directives.join(", ");
}

function expectCt(options: Readonly<ExpectCtOptions> = {}) {
const headerValue = getHeaderValueFromOptions(options);

return function expectCtMiddleware(
_req: IncomingMessage,
res: ServerResponse,
next: () => void
) {
res.setHeader("Expect-CT", headerValue);
next();
};
}

module.exports = expectCt;
export default expectCt;
1 change: 1 addition & 0 deletions middlewares/expect-ct/package-files.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
["index.js", "index.d.ts"]
9 changes: 9 additions & 0 deletions middlewares/expect-ct/package-overrides.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "expect-ct",
"author": "Evan Hahn <[email protected]> (https://evanhahn.com)",
"contributors": [],
"description": "Middleware to set the Expect-CT header",
"version": "0.3.0",
"keywords": ["express", "security", "expect-ct"],
"homepage": "https://helmetjs.github.io/docs/expect-ct/"
}
5 changes: 0 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@
"dependencies": {
"depd": "2.0.0",
"dont-sniff-mimetype": "1.1.0",
"expect-ct": "0.2.0",
"feature-policy": "0.3.0",
"frameguard": "3.1.0",
"helmet-crossdomain": "0.4.0",
Expand Down
71 changes: 71 additions & 0 deletions test/expect-ct.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { check } from "./helpers";
import expectCt from "../middlewares/expect-ct";

describe("Expect-CT middleware", () => {
it("sets the max-age to 0 when passed no max-age", async () => {
await check(expectCt(), {
"expect-ct": "max-age=0",
});
await check(expectCt({}), {
"expect-ct": "max-age=0",
});
await check(expectCt({ maxAge: undefined }), {
"expect-ct": "max-age=0",
});
});

it("sets the max-age to a provided integer", async () => {
await check(expectCt({ maxAge: 123 }), {
"expect-ct": "max-age=123",
});
await check(expectCt({ maxAge: 0 }), {
"expect-ct": "max-age=0",
});
});

it("rounds non-integers down", async () => {
await check(expectCt({ maxAge: 123.4 }), {
"expect-ct": "max-age=123",
});
await check(expectCt({ maxAge: 123.5 }), {
"expect-ct": "max-age=123",
});
});

it("rejects negative max-ages", async () => {
expect(() => expectCt({ maxAge: -123 })).toThrow();
expect(() => expectCt({ maxAge: -0.1 })).toThrow();
});

it("can enable enforcement", async () => {
await check(expectCt({ enforce: true }), {
"expect-ct": "enforce, max-age=0",
});
});

it("can explicitly disable enforcement", async () => {
await check(expectCt({ enforce: false }), {
"expect-ct": "max-age=0",
});
});

it("can set a report-uri", async () => {
await check(expectCt({ reportUri: "https://example.com/report" }), {
"expect-ct": 'max-age=0, report-uri="https://example.com/report"',
});
});

it("can set enforcement, max-age, and a report-uri", async () => {
await check(
expectCt({
enforce: true,
maxAge: 123,
reportUri: "https://example.com/report",
}),
{
"expect-ct":
'enforce, max-age=123, report-uri="https://example.com/report"',
}
);
});
});
6 changes: 3 additions & 3 deletions test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import helmet = require("..");
import { IncomingMessage, ServerResponse } from "http";
import connect = require("connect");
import request = require("supertest");
import expectCt from "../middlewares/expect-ct";
import xDnsPrefetchControl from "../middlewares/x-dns-prefetch-control";
import xDowloadOptions from "../middlewares/x-download-options";

Expand All @@ -17,9 +18,8 @@ describe("helmet", function () {
expect(helmet.noSniff).toBe(pkg);
});

it('aliases "expect-ct"', function () {
const pkg = require("expect-ct");
expect(helmet.expectCt).toBe(pkg);
it("aliases the Expect-CT middleware to helmet.expectCt", function () {
expect(helmet.expectCt.name).toBe(expectCt.name);
});

// This test will be removed in helmet@4.
Expand Down

0 comments on commit d03c555

Please sign in to comment.