Skip to content
This repository was archived by the owner on Dec 13, 2024. It is now read-only.

Commit ebb71ac

Browse files
committed
feat: add security.txt
1 parent edfff08 commit ebb71ac

File tree

7 files changed

+121
-4
lines changed

7 files changed

+121
-4
lines changed

README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ This module is considered experimental and a work-in-progress.
2222
- [x] X-Content-Type-Options
2323
- [x] Referrer-Policy
2424
- [x] Feature-Policy
25-
- [ ] .well-known/security.txt
25+
- [x] security.txt
2626
- [ ] Documentation
2727

2828
[📖 **Release Notes**](./CHANGELOG.md)
@@ -50,7 +50,10 @@ yarn add @dansmaculotte/nuxt-security # or npm install @dansmaculotte/nuxt-secur
5050
/* module options */
5151
}
5252
]
53-
]
53+
],
54+
55+
// Top level options
56+
security: {}
5457
}
5558
```
5659

lib/module.js

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@ const csp = require('helmet-csp')
44
const refererPolicy = require('referrer-policy')
55
const featurePolicy = require('feature-policy')
66

7-
const logger = consola.withScope('nuxt:csp')
7+
const securityFileGenerator = require('./securityFile/generator')
8+
const securityFileFactory = require('./securityFile/factory')
9+
const securityFileMiddleware = require('./securityFile/middleware')
10+
11+
const logger = consola.withScope('nuxt:security')
812

913
module.exports = function(moduleOptions) {
1014
const defaults = {
@@ -13,11 +17,13 @@ module.exports = function(moduleOptions) {
1317
csp: null,
1418
referer: null,
1519
features: null,
20+
securityFile: null,
1621
additionalHeaders: false
1722
}
23+
1824
const options = {
1925
...defaults,
20-
...this.options['nuxt-csp'],
26+
...this.options.security,
2127
...moduleOptions
2228
}
2329

@@ -74,6 +80,21 @@ module.exports = function(moduleOptions) {
7480
if (options.additionalHeaders) {
7581
this.addServerMiddleware(configureAddtionnalHeaders())
7682
}
83+
84+
if (options.securityFile) {
85+
options.securityFile.wellKnowDir = '.well-known'
86+
options.securityFile.fileName = 'security.txt'
87+
88+
const securityFileContents = securityFileFactory(options.securityFile)
89+
90+
this.nuxt.hook('generate:done', () => {
91+
securityFileGenerator(options.securityFile, securityFileContents, this)
92+
})
93+
94+
this.nuxt.hook('render:setupMiddleware', () => {
95+
securityFileMiddleware(options.securityFile, securityFileContents, this)
96+
})
97+
}
7798
}
7899

79100
module.exports.meta = require('../package.json')

lib/securityFile/factory.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
const generatedBy =
2+
'# Generated by https://github.com/dansmaculotte/nuxt-security'
3+
4+
const contactOutput = contact => `Contact: ${contact}`
5+
const encryptionOuput = url => `Encryption: ${url}`
6+
const acknowledgmentOuput = url => `Acknowledgments: ${url}`
7+
const languagesOutput = languages => `Preferred-Languages: ${languages}`
8+
const canonicalOutput = url => `Canonical: ${url}`
9+
const policyOuput = url => `Policy: ${url}`
10+
const hiringOuput = url => `Hiring: ${url}`
11+
12+
const makeArray = arr => (Array.isArray(arr) ? arr : [arr])
13+
14+
module.exports = ({
15+
contacts,
16+
encryptions,
17+
acknowledgments,
18+
preferredLanguages,
19+
canonical,
20+
policies,
21+
hirings
22+
}) => {
23+
const contents = [generatedBy]
24+
25+
makeArray(contacts).map(contact => contents.push(contactOutput(contact)))
26+
27+
makeArray(encryptions).map(encryption =>
28+
contents.push(encryptionOuput(encryption))
29+
)
30+
31+
makeArray(acknowledgments).map(acknowledgment =>
32+
contents.push(acknowledgmentOuput(acknowledgment))
33+
)
34+
35+
if (preferredLanguages && preferredLanguages.length) {
36+
contents.push(languagesOutput(makeArray(preferredLanguages).join(',')))
37+
}
38+
39+
if (canonical) {
40+
contents.push(canonicalOutput(canonical))
41+
}
42+
43+
makeArray(policies).map(policy => contents.push(policyOuput(policy)))
44+
45+
makeArray(hirings).map(hiring => contents.push(hiringOuput(hiring)))
46+
47+
return contents.join('\n')
48+
}

lib/securityFile/generator.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
const { resolve } = require('path')
2+
const { writeFileSync, existsSync, mkdirSync } = require('fs')
3+
4+
module.exports = function(options, fileContents, nuxtInstance) {
5+
const {
6+
rootDir,
7+
generate: { dir: generateDir }
8+
} = nuxtInstance.options
9+
10+
// generate .well-know/security.txt in dist
11+
nuxtInstance.nuxt.hook('generate:done', () => {
12+
const generateDirPath = resolve(rootDir, generateDir, options.wellKnowDir)
13+
const generateFilePath = resolve(generateDirPath, options.fileName)
14+
15+
if (!existsSync(generateDirPath)) {
16+
mkdirSync(generateDirPath)
17+
}
18+
19+
writeFileSync(generateFilePath, fileContents)
20+
})
21+
}

lib/securityFile/middleware.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
module.exports = function(options, fileContents, nuxtInstance) {
2+
// render .well-know/security.txt via SSR
3+
nuxtInstance.addServerMiddleware({
4+
path: options.wellKnowDir + '/' + options.fileName,
5+
handler(req, res) {
6+
res.setHeader('Content-Type', 'text/plain')
7+
res.end(fileContents)
8+
}
9+
})
10+
}

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
"main": "lib/module.js",
2727
"scripts": {
2828
"dev": "nuxt test/fixture",
29+
"dev:generate": "nuxt generate test/fixture",
30+
"dev:build": "nuxt build test/fixture",
2931
"lint": "eslint --ext .js,.vue lib test",
3032
"release": "yarn test && standard-version && git push --follow-tags && npm publish",
3133
"test": "yarn lint && jest"

test/fixture/nuxt.config.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,18 @@ module.exports = {
3737
features: {
3838
notifications: ["'none'"]
3939
},
40+
securityFile: {
41+
contacts: [
42+
'mailto:security@example.com',
43+
'https://example.com/security'
44+
],
45+
canonical: 'https://example.com/.well-know/security.txt',
46+
preferredLanguages: ['fr', 'en'],
47+
encryptions: ['https://example.com/pgp-key.txt'],
48+
acknowledgments: ['https://example.com/hall-of-fame.html'],
49+
policies: ['https://example.com/policy.html'],
50+
hirings: ['https://example.com/jobs.html']
51+
},
4052
additionalHeaders: true
4153
}
4254
}

0 commit comments

Comments
 (0)