Skip to content

Commit

Permalink
Merge pull request #1 from KindSpells/improve-api
Browse files Browse the repository at this point in the history
feat!: improve public api
  • Loading branch information
castarco authored Feb 11, 2024
2 parents fa132a1 + 30f8b6b commit ac88392
Show file tree
Hide file tree
Showing 7 changed files with 166 additions and 100 deletions.
15 changes: 15 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
root = true

[*]
end_of_line = lf
insert_final_newline = true

[*.{js,mjs,json,ts}]
charset = utf-8
indent_style = tab
indent_size = 2
trim_trailing_whitespace = true

[*.md]
indent_style = space
indent_size = 2
47 changes: 24 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,56 +14,57 @@ hook performs 3 steps:
them later for other purposes, such as configuring your
`Content-Security-Policy` headers.

### Known limitations

- For now, the SRI hashes calculation is done only for inlined resources. This
will be solved in future releases.
- For now, this integration only works for generated static content (the
exported subresource integrity hashes could be used in dynamic contexts, but
that does not cover the whole SSG use case)

## How to install

```bash
# With NPM
npm install @kindspells/astro-sri-csp
npm install --save-dev @kindspells/astro-sri-csp

# With Yarn
yarn add @kindspells/astro-sri-csp
yarn add --dev @kindspells/astro-sri-csp

# With PNPM
pnpm add @kindspells/astro-sri-csp
pnpm add --save-dev @kindspells/astro-sri-csp
```

## How to use

In your `astro.config.mjs` file:

```javascript
import { join } from 'node:path'
import { resolve } from 'node:path'

import { defineConfig } from 'astro/config'
import { sriCSP } from '@kindspells/astro-sri-csp'

const rootDir = new URL('.', import.meta.url).pathname

// In this example we set dist/client because we assume a "hybrid" output, and
// in that case it makes no sense to traverse the server-side generated code.
// If your site is 100% static, we shouldn't add the 'client' part.
const distDir = join(rootDir, 'dist', 'client')

// This is the path where we'll generate the module containing the SRI hashes
// for your scripts and styles. There's no need to pass this parameter if you
// don't need this data, but it can be useful to configure your CSP policies.
const sriHashesModule = join(rootDir, 'src', 'utils', 'sriHashes.mjs')

export default defineConfig({
integrations: [
sriCSP(distDir, sriHashesModule)
sriCSP({
// This is the path where we'll generate the module containing the SRI
// hashes for your scripts and styles. There's no need to pass this
// parameter if you don't need this data, but it can be useful to
// configure your CSP policies.
sriHashesModule: resolve(rootDir, 'src', 'utils', 'sriHashes.mjs'),
})
]
})
```
## Known limitations
- For now, the SRI hashes calculation is done only for inlined resources. This
will be solved in future releases.
- For now, this integration only works for generated static content (the
exported subresource integrity hashes could be used in dynamic contexts, but
that does not cover the whole SSG use case)
- The SRI hashes will be regenerated only when running `astro build`. This means
that if you need them to be up to date when you run `astro dev`, then you will
have to manually run `astro build`.
## License
This library is released under [MIT License](LICENSE).
9 changes: 2 additions & 7 deletions biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,8 @@
"indentWidth": 2,
"lineWidth": 80,
"lineEnding": "lf",
"ignore": [
"./.sst/**/*",
"./dist/**/*",
"./node_modules/**/*",
"./src/content/emailTemplates/**/*"
],
"include": ["./*.json", "./*.js", "./*.mjs", "./*.d.ts"]
"ignore": ["./node_modules/**/*"],
"include": ["./*.json", "./*.mjs", "./*.d.ts"]
},
"javascript": {
"formatter": {
Expand Down
16 changes: 14 additions & 2 deletions main.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
export type SriCspOptions = {
/**
* Specifies the path for the auto-generated module that will contain the SRI
* hashes. Note that:
* - The generated module will be an ESM module
* - The generated module should be treated as source code, and not as a build
* artifact.
*/
sriHashesModule?: string | undefined
}

export type StrictSriCspOptions = SriCspOptions & { distDir: string }

export function sriCSP(
distDir: string,
hashesOutputModule?: string | undefined,
sriCspOptions: SriCspOptions,
): import('astro').AstroIntegration

export default sriCSP
42 changes: 22 additions & 20 deletions main.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@

import { createHash } from 'node:crypto'
import { readFile, readdir, stat, writeFile } from 'node:fs/promises'
import { extname, join } from 'node:path'
import { extname, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'

/**
* @param {string} data
Expand Down Expand Up @@ -109,7 +110,7 @@ const scanDirectory = async dirPath => {
const inlineStyleHashes = /** @type {Set<string>} */ (new Set())

for (const file of await readdir(dirPath)) {
const filePath = join(dirPath, file)
const filePath = resolve(dirPath, file)
const stats = await stat(filePath)

const hashes = stats.isDirectory()
Expand Down Expand Up @@ -168,26 +169,25 @@ const arraysEqual = (a, b) => {
}

/**
* @param {string} distDir
* @param {string | undefined} hashesOutputModule
* @param {import('./main.d.ts').StrictSriCspOptions} sriCspOptions
*/
const generateSRIHashes = async (distDir, hashesOutputModule) => {
const generateSRIHashes = async ({ distDir, sriHashesModule }) => {
const hashes = await scanDirectory(distDir)

const inlineScriptHashes = Array.from(hashes.inlineScriptHashes).sort()
const inlineStyleHashes = Array.from(hashes.inlineStyleHashes).sort()

if (!hashesOutputModule) {
if (!sriHashesModule) {
return
}

let persistHashes = false

if (await doesFileExist(hashesOutputModule)) {
if (await doesFileExist(sriHashesModule)) {
const hashesModule = /** @type {{
inlineScriptHashes?: string[] | undefined
inlineStyleHashes?: string[] | undefined
}} */ (await import(hashesOutputModule))
}} */ (await import(sriHashesModule))

persistHashes =
!arraysEqual(inlineScriptHashes, hashesModule.inlineScriptHashes ?? []) ||
Expand All @@ -205,22 +205,24 @@ const generateSRIHashes = async (distDir, hashesOutputModule) => {
inlineStyleHashes,
)})\n`

await writeFile(hashesOutputModule, hashesFileContent)
await writeFile(sriHashesModule, hashesFileContent)
}
}

/**
* @param {string} distDir
* @param {string | undefined} hashesOutputModule
* @param {import('./main.d.ts').SriCspOptions} sriCspOptions
* @returns {import('astro').AstroIntegration}
*/
export const sriCSP = (distDir, hashesOutputModule) => ({
name: 'scp-sri-postbuild',
hooks: {
'astro:build:done': async () =>
await generateSRIHashes(distDir, hashesOutputModule),
'astro:server:setup': async () =>
await generateSRIHashes(distDir, hashesOutputModule),
},
})
export const sriCSP = sriCspOptions =>
/** @type {import('astro').AstroIntegration} */ ({
name: 'scp-sri-postbuild',
hooks: {
'astro:build:done': async ({ dir }) =>
await generateSRIHashes({
distDir: fileURLToPath(dir),
sriHashesModule: sriCspOptions.sriHashesModule,
}),
},
})

export default sriCSP
101 changes: 53 additions & 48 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,50 +1,55 @@
{
"name": "@kindspells/astro-sri-csp",
"version": "0.1.8",
"description": "An Astro plugin to compute and inject SRI hashes for script and style tags",
"private": false,
"type": "module",
"main": "main.mjs",
"types": "main.d.ts",
"exports": {
"types": "./main.d.ts",
"default": "./main.mjs"
},
"files": [
"main.d.ts",
"main.mjs"
],
"scripts": {
"format": "biome format --write .",
"lint": "biome lint ."
},
"keywords": [
"astro",
"astro-component",
"astro-integration",
"code-generation",
"csp",
"content-security-policy",
"security",
"sri",
"subresource-integrity",
"withastro"
],
"author": "KindSpells Labs S.L.",
"license": "MIT",
"devDependencies": {
"@biomejs/biome": "^1.5.3",
"astro": "^4.3.5"
},
"repository": "github:KindSpells/astro-sri-csp",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/kindspells-labs"
},
{
"type": "individual",
"url": "https://ko-fi.com/coderspirit"
}
]
"name": "@kindspells/astro-sri-csp",
"version": "0.2.1",
"description": "An Astro plugin to compute and inject SRI hashes for script and style tags",
"private": false,
"type": "module",
"main": "main.mjs",
"types": "main.d.ts",
"exports": {
"types": "./main.d.ts",
"default": "./main.mjs"
},
"files": ["main.d.ts", "main.mjs"],
"scripts": {
"format": "biome format --write .",
"lint": "pnpm run lint:biome && pnpm run lint:tsc",
"lint:biome": "biome lint .",
"lint:tsc": "tsc -p ."
},
"keywords": [
"astro",
"astro-component",
"astro-integration",
"code-generation",
"csp",
"content-security-policy",
"security",
"sri",
"subresource-integrity",
"withastro"
],
"author": "KindSpells Labs S.L.",
"license": "MIT",
"devDependencies": {
"@biomejs/biome": "^1.5.3",
"@types/node": "^20.11.17",
"astro": "^4.3.5",
"typescript": "^5.3.3"
},
"repository": "github:KindSpells/astro-sri-csp",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/kindspells-labs"
},
{
"type": "individual",
"url": "https://ko-fi.com/coderspirit"
}
],
"packageManager": "[email protected]",
"engines": {
"node": ">= 18.0.0"
}
}
36 changes: 36 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"compilerOptions": {
"target": "es2022",
"module": "Node16",
"moduleResolution": "Node16",

"baseUrl": ".",
"noEmit": true,

"allowJs": true,
"checkJs": true,

"isolatedModules": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,

"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"useUnknownInCatchVariables": true,
"alwaysStrict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"exactOptionalPropertyTypes": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,

"skipLibCheck": true
},
"include": ["*.mjs", "*.d.ts", "*.ts"]
}

0 comments on commit ac88392

Please sign in to comment.