-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add factory for coop header field
- Loading branch information
1 parent
ca640ae
commit 9883dce
Showing
8 changed files
with
279 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
export { | ||
assert, | ||
assertEquals, | ||
assertThrows, | ||
} from "https://deno.land/[email protected]/testing/asserts.ts"; | ||
export { describe, it } from "https://deno.land/[email protected]/testing/bdd.ts"; | ||
export { equalsResponse } from "https://deno.land/x/[email protected]/response.ts"; | ||
export { CrossOriginOpenerPolicyValue, PolicyHeader } from "./constants.ts"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
// Copyright 2023-latest the httpland authors. All rights reserved. MIT license. | ||
// This module is browser compatible. | ||
|
||
/** Cross-origin embedded policy value. | ||
* @see [cross-origin opener policy value](https://html.spec.whatwg.org/multipage/browsers.html#cross-origin-opener-policy-value) | ||
*/ | ||
export enum CrossOriginOpenerPolicyValue { | ||
/** The document will occupy the same [top-level browsing context](https://html.spec.whatwg.org/multipage/document-sequences.html#top-level-browsing-context) as its predecessor, | ||
* unless that document specified a different [cross-origin opener policy](https://html.spec.whatwg.org/multipage/browsers.html#cross-origin-opener-policy). | ||
* @see ["unsafe-none"](https://html.spec.whatwg.org/multipage/browsers.html#coop-unsafe-none) | ||
*/ | ||
UnsafeNone = "unsafe-none", | ||
|
||
/** This forces the creation of a new [top-level browsing context](https://html.spec.whatwg.org/multipage/document-sequences.html#top-level-browsing-context) for the document, | ||
* unless its predecessor specified the same [cross-origin opener policy](https://html.spec.whatwg.org/multipage/browsers.html#cross-origin-opener-policy) and they are [same origin](https://html.spec.whatwg.org/multipage/browsers.html#same-origin). | ||
* @see ["same-origin-allow-popups"](https://html.spec.whatwg.org/multipage/browsers.html#coop-same-origin-allow-popups) | ||
*/ | ||
SameOriginAllowPopups = "same-origin-allow-popups", | ||
|
||
/** This behaves the same as {@link CrossOriginOpenerPolicyValue.SameOriginAllowPopups}, | ||
* with the addition that any [auxiliary browsing context](https://html.spec.whatwg.org/multipage/document-sequences.html#auxiliary-browsing-context) created needs to contain [same origin](https://html.spec.whatwg.org/multipage/browsers.html#same-origin) documents | ||
* that also have the same [cross-origin opener policy](https://html.spec.whatwg.org/multipage/browsers.html#cross-origin-opener-policy) or it will appear closed to the opener. | ||
* @see ["same-origin"](https://html.spec.whatwg.org/multipage/browsers.html#coop-same-origin) | ||
*/ | ||
SameOrigin = "same-origin", | ||
} | ||
|
||
export const enum PolicyHeader { | ||
CrossOriginOpenerPolicy = "cross-origin-opener-policy", | ||
CrossOriginOpenerPolicyReportOnly = | ||
`${PolicyHeader.CrossOriginOpenerPolicy}-report-only`, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
// Copyright 2023-latest the httpland authors. All rights reserved. MIT license. | ||
// This module is browser compatible. | ||
|
||
export { | ||
type Handler, | ||
type Middleware, | ||
} from "https://deno.land/x/[email protected]/mod.ts"; | ||
export { withHeader } from "https://deno.land/x/[email protected]/message.ts"; | ||
export { isString } from "https://deno.land/x/[email protected]/is_string.ts"; | ||
export { | ||
Item, | ||
Parameters, | ||
stringifySfv, | ||
Token, | ||
} from "https://deno.land/x/[email protected]/mod.ts"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
// Copyright 2023-latest the httpland authors. All rights reserved. MIT license. | ||
// This module is browser compatible. | ||
|
||
import { CrossOriginOpenerPolicyValue, PolicyHeader } from "./constants.ts"; | ||
import { CrossOriginOpenerPolicy } from "./types.ts"; | ||
import { type Middleware, withHeader } from "./deps.ts"; | ||
import { stringifyCrossOriginOpenerPolicy } from "./utils.ts"; | ||
|
||
/** Middleware options. */ | ||
export interface Options | ||
extends Partial<Pick<CrossOriginOpenerPolicy, "reportTo">> { | ||
/** Opener policy value. | ||
* @default "same-origin" | ||
*/ | ||
readonly policy?: `${CrossOriginOpenerPolicyValue}`; | ||
|
||
/** Whether header is report-only or not. | ||
* Depending on the value, the header will be: | ||
* - `true`: `Cross-Origin-Opener-Policy-Report-Only` | ||
* - `false`: `Cross-Origin-Opener-Policy` | ||
* @default false | ||
*/ | ||
readonly reportOnly?: boolean; | ||
} | ||
|
||
/** Create cross-origin opener policy middleware. | ||
* | ||
* @example | ||
* ```ts | ||
* import { | ||
* coop, | ||
* type Handler, | ||
* } from "https://deno.land/x/coop_middleware@$VERSION/mod.ts"; | ||
* import { assert } from "https://deno.land/std/testing/asserts.ts"; | ||
* | ||
* declare const request: Request; | ||
* declare const handler: Handler; | ||
* | ||
* const middleware = coop(); | ||
* const response = await middleware(request, handler); | ||
* | ||
* assert(response.headers.has("cross-origin-opener-policy")); | ||
* ``` | ||
*/ | ||
export function coop(options?: Options): Middleware { | ||
const { | ||
policy: value = CrossOriginOpenerPolicyValue.SameOrigin, | ||
reportOnly, | ||
reportTo, | ||
} = options ?? {}; | ||
|
||
const fieldValue = stringifyCrossOriginOpenerPolicy({ value, reportTo }); | ||
const fieldName = reportOnly | ||
? PolicyHeader.CrossOriginOpenerPolicyReportOnly | ||
: PolicyHeader.CrossOriginOpenerPolicy; | ||
|
||
return async (request, next) => { | ||
const response = await next(request); | ||
|
||
if (response.headers.has(fieldName)) return response; | ||
|
||
return withHeader(response, fieldName, fieldValue); | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
import { coop } from "./middleware.ts"; | ||
import { | ||
assert, | ||
assertThrows, | ||
CrossOriginOpenerPolicyValue, | ||
describe, | ||
equalsResponse, | ||
it, | ||
PolicyHeader, | ||
} from "./_dev_deps.ts"; | ||
|
||
describe("coop", () => { | ||
it("should return same response if the response include the header", async () => { | ||
const middleware = coop(); | ||
const initResponse = new Response(null, { | ||
headers: { | ||
[PolicyHeader.CrossOriginOpenerPolicy]: "", | ||
}, | ||
}); | ||
const response = await middleware( | ||
new Request("test:"), | ||
() => initResponse, | ||
); | ||
|
||
assert(response === initResponse); | ||
}); | ||
|
||
it("should return response what include coop header and the value is same-origin by default", async () => { | ||
const middleware = coop(); | ||
const response = await middleware( | ||
new Request("test:"), | ||
() => new Response(), | ||
); | ||
|
||
assert(equalsResponse( | ||
response, | ||
new Response(null, { | ||
headers: { | ||
[PolicyHeader.CrossOriginOpenerPolicy]: | ||
CrossOriginOpenerPolicyValue.SameOrigin, | ||
}, | ||
}), | ||
)); | ||
}); | ||
|
||
it("should change coop header via arg", async () => { | ||
const middleware = coop({ | ||
policy: CrossOriginOpenerPolicyValue.SameOriginAllowPopups, | ||
}); | ||
const response = await middleware( | ||
new Request("test:"), | ||
() => new Response(), | ||
); | ||
|
||
assert(equalsResponse( | ||
response, | ||
new Response(null, { | ||
headers: { | ||
[PolicyHeader.CrossOriginOpenerPolicy]: | ||
CrossOriginOpenerPolicyValue.SameOriginAllowPopups, | ||
}, | ||
}), | ||
)); | ||
}); | ||
|
||
it("should add report-to param via endpoint", async () => { | ||
const reportTo = "default"; | ||
const middleware = coop({ reportTo }); | ||
const response = await middleware( | ||
new Request("test:"), | ||
() => new Response(), | ||
); | ||
|
||
assert(equalsResponse( | ||
response, | ||
new Response(null, { | ||
headers: { | ||
[PolicyHeader.CrossOriginOpenerPolicy]: | ||
`${CrossOriginOpenerPolicyValue.SameOrigin};report-to=${reportTo}`, | ||
}, | ||
}), | ||
)); | ||
}); | ||
|
||
it("should change to report only header", async () => { | ||
const middleware = coop({ reportOnly: true }); | ||
const response = await middleware( | ||
new Request("test:"), | ||
() => new Response(), | ||
); | ||
|
||
assert(equalsResponse( | ||
response, | ||
new Response(null, { | ||
headers: { | ||
[PolicyHeader.CrossOriginOpenerPolicyReportOnly]: | ||
CrossOriginOpenerPolicyValue.SameOrigin, | ||
}, | ||
}), | ||
)); | ||
}); | ||
|
||
it("should throw error if the Cross origin opener policy is invalid", () => { | ||
assertThrows(() => coop({ reportTo: "?" })); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
// Copyright 2023-latest the httpland authors. All rights reserved. MIT license. | ||
// This module is browser compatible. | ||
|
||
export { coop, type Options } from "./middleware.ts"; | ||
export { CrossOriginOpenerPolicyValue } from "./constants.ts"; | ||
export { type Handler, type Middleware } from "./deps.ts"; | ||
export type { CrossOriginOpenerPolicy } from "./types.ts"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
// Copyright 2023-latest the httpland authors. All rights reserved. MIT license. | ||
// This module is browser compatible. | ||
|
||
import { type CrossOriginOpenerPolicyValue } from "./constants.ts"; | ||
|
||
/** Cross-origin opener policy API. */ | ||
export interface CrossOriginOpenerPolicy { | ||
/** opener policy value. */ | ||
readonly value: `${CrossOriginOpenerPolicyValue}`; | ||
|
||
/** Reporting endpoint name. */ | ||
readonly reportTo?: string; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
// Copyright 2023-latest the httpland authors. All rights reserved. MIT license. | ||
// This module is browser compatible. | ||
|
||
import type { CrossOriginOpenerPolicy } from "./types.ts"; | ||
import { isString, Item, Parameters, stringifySfv, Token } from "./deps.ts"; | ||
|
||
const enum Param { | ||
ReportTo = "report-to", | ||
} | ||
|
||
/** Serialize {@link CrossOriginOpenerPolicy} into string. | ||
* @throws {TypeError} If the {@link CrossOriginOpenerPolicy} is invalid. | ||
*/ | ||
export function stringifyCrossOriginOpenerPolicy( | ||
policy: CrossOriginOpenerPolicy, | ||
): string { | ||
const token = new Token(policy.value); | ||
const parameters = isString(policy.reportTo) | ||
? new Parameters({ | ||
[Param.ReportTo]: new Token(policy.reportTo), | ||
}) | ||
: new Parameters(); | ||
const item = new Item([token, parameters]); | ||
|
||
try { | ||
return stringifySfv(item); | ||
} catch (cause) { | ||
throw TypeError(Msg.InvalidCrossOriginOpenerPolicy, { cause }); | ||
} | ||
} | ||
|
||
const enum Msg { | ||
InvalidCrossOriginOpenerPolicy = "invalid CrossOriginOpenerPolicy format.", | ||
} |