Skip to content

Commit 0d4176d

Browse files
authored
remove unsafe-inline and implement script nonces (#394)
1 parent 5f1ad76 commit 0d4176d

10 files changed

Lines changed: 42 additions & 37 deletions

File tree

package-lock.json

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

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "nostream",
3-
"version": "2.1.0",
3+
"version": "2.1.1",
44
"description": "A Nostr relay written in Typescript.",
55
"supportedNips": [
66
1,

resources/index.html

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
88
<link rel="stylesheet" href="./css/style.css">
99
</head>
10-
<body lang="en" onload="onLoad()">
10+
<body lang="en">
1111
<main class="container">
1212
<form method="post" action="/invoices">
1313
<div class="row">
@@ -128,7 +128,7 @@ <h5 class="modal-title">Terms of Service</h5>
128128
</div>
129129
</div>
130130
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.min.js" integrity="sha384-cuYeSxntonz0PPNlHhBs68uyIAVpIIOZZ5JqeqvYYIcEL727kskC66kF92t6Xl2V" crossorigin="anonymous"></script>
131-
<script>
131+
<script nonce="{{nonce}}">
132132
var processor = "{{processor}}"
133133
function attemptGetPubkey() {
134134
const maxRetries = 10
@@ -157,6 +157,7 @@ <h5 class="modal-title">Terms of Service</h5>
157157
document.body.classList.add('dark-theme');
158158
}
159159
}
160+
window.addEventListener('load', onLoad)
160161
</script>
161162
</body>
162163
</html>

resources/invoices.html

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,10 @@ <h1 class="mt-4 mb-4 text-center text-nowrap">{{name}}</h1>
3636
<div class="row justify-content-center">
3737
<div class="card pending col-8 col-lg-4 d-flex flex-column justify-content-center mb-4">
3838
<div class="card-body m-auto">
39-
<div id="invoice" onclick="sendPayment()"></div>
39+
<div id="invoice"></div>
4040
</div>
4141
<div class="card-body d-flex flex-row justify-content-center">
42-
<div class="input-group input-group-sm w-100 mw-256" onclick="copy()">
42+
<div class="input-group input-group-sm w-100 mw-256" id="invoiceCopyGroup">
4343
<input type="text" name="invoice" class="form-control form-control-sm" id="invoiceInput" value="{{invoice}}" readonly>
4444
<span class="input-group-text" id="invoiceAlert">copy</span>
4545
</div>
@@ -74,7 +74,7 @@ <h2 class="text-danger">Invoice expired!</h2>
7474
<div class="row pending d-none">
7575
<div class="col">
7676
<div class="d-flex justify-content-center mb-3">
77-
<button id="sendPaymentBtn" class="btn btn-lg btn-warning d-none" type="submit" onclick="sendPayment()">Pay with wallet</button>
77+
<button id="sendPaymentBtn" class="btn btn-lg btn-warning d-none" type="submit">Pay with wallet</button>
7878
</div>
7979
</div>
8080
</div>
@@ -97,7 +97,7 @@ <h2 class="text-danger">Invoice expired!</h2>
9797
</main>
9898
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.min.js" integrity="sha384-cuYeSxntonz0PPNlHhBs68uyIAVpIIOZZ5JqeqvYYIcEL727kskC66kF92t6Xl2V" crossorigin="anonymous"></script>
9999
<script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js" integrity="sha512-CNgIRecGo7nphbeZ04Sc13ka07paqdeTu0WR1IM4kNcpmBAUSHSQX0FslNhTDadL4O5SAGapGt4FodqL8My0mA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
100-
<script>
100+
<script nonce="{{nonce}}">
101101
// Check for system preference on load
102102
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
103103
document.body.classList.add('dark-theme');
@@ -263,6 +263,10 @@ <h2 class="text-danger">Invoice expired!</h2>
263263
if (processor === 'zebedee') {
264264
document.getElementById('powered-by-zebedee').classList.remove('d-none')
265265
}
266+
267+
document.getElementById('invoice').addEventListener('click', sendPayment)
268+
document.getElementById('invoiceCopyGroup').addEventListener('click', copy)
269+
document.getElementById('sendPaymentBtn').addEventListener('click', sendPayment)
266270
</script>
267271
</body>
268272
</html>

resources/terms.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ <h3>Terms of Service Agreement</h3>
6262
</div>
6363
</main>
6464
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.min.js" integrity="sha384-cuYeSxntonz0PPNlHhBs68uyIAVpIIOZZ5JqeqvYYIcEL727kskC66kF92t6Xl2V" crossorigin="anonymous"></script>
65-
<script>
65+
<script nonce="{{nonce}}">
6666
// Check for system preference on load
6767
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
6868
document.body.classList.add('dark-theme');

src/controllers/invoices/get-invoice-controller.ts

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { createSettings } from '../../factories/settings-factory'
66
import { FeeSchedule } from '../../@types/settings'
77
import { IController } from '../../@types/controllers'
88

9-
let pageCache: string
9+
1010

1111
export class GetInvoiceController implements IController {
1212
public async handleRequest(
@@ -17,16 +17,15 @@ export class GetInvoiceController implements IController {
1717

1818
if (pathEq(['payments', 'enabled'], true, settings)
1919
&& pathEq(['payments', 'feeSchedules', 'admission', '0', 'enabled'], true, settings)) {
20-
if (!pageCache) {
21-
const name = path<string>(['info', 'name'])(settings)
22-
const feeSchedule = path<FeeSchedule>(['payments', 'feeSchedules', 'admission', '0'], settings)
23-
pageCache = readFileSync('./resources/index.html', 'utf8')
24-
.replaceAll('{{name}}', name)
25-
.replaceAll('{{processor}}', settings.payments.processor)
26-
.replaceAll('{{amount}}', (BigInt(feeSchedule.amount) / 1000n).toString())
27-
}
20+
const name = path<string>(['info', 'name'])(settings)
21+
const feeSchedule = path<FeeSchedule>(['payments', 'feeSchedules', 'admission', '0'], settings)
22+
const page = readFileSync('./resources/index.html', 'utf8')
23+
.replaceAll('{{name}}', name)
24+
.replaceAll('{{processor}}', settings.payments.processor)
25+
.replaceAll('{{amount}}', (BigInt(feeSchedule.amount) / 1000n).toString())
26+
.replaceAll('{{nonce}}', res.locals.nonce)
2827

29-
res.status(200).setHeader('content-type', 'text/html; charset=utf8').send(pageCache)
28+
res.status(200).setHeader('content-type', 'text/html; charset=utf8').send(page)
3029
} else {
3130
res.status(404).send()
3231
}

src/controllers/invoices/post-invoice-controller.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { IUserRepository } from '../../@types/repositories'
1313
import { path } from 'ramda'
1414
import { readFileSync } from 'fs'
1515

16-
let pageCache: string
16+
1717

1818
const debug = createLogger('post-invoice-controller')
1919

@@ -26,9 +26,7 @@ export class PostInvoiceController implements IController {
2626
){}
2727

2828
public async handleRequest(request: Request, response: Response): Promise<void> {
29-
if (!pageCache) {
30-
pageCache = readFileSync('./resources/invoices.html', 'utf8')
31-
}
29+
3230

3331
debug('params: %o', request.params)
3432
debug('body: %o', request.body)
@@ -174,9 +172,11 @@ export class PostInvoiceController implements IController {
174172
processor: currentSettings.payments.processor,
175173
}
176174

175+
const pageContent = readFileSync('./resources/invoices.html', 'utf8')
177176
const body = Object
178177
.entries(replacements)
179-
.reduce((body, [key, value]) => body.replaceAll(`{{${key}}}`, value.toString()), pageCache)
178+
.reduce((body, [key, value]) => body.replaceAll(`{{${key}}}`, value.toString()), pageContent)
179+
.replaceAll('{{nonce}}', response.locals.nonce)
180180

181181
response
182182
.status(200)

src/factories/web-app-factory.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import express from 'express'
22
import helmet from 'helmet'
3+
import { randomBytes } from 'crypto'
34

45
import { createSettings } from './settings-factory'
56
import router from '../routes'
@@ -10,20 +11,19 @@ export const createWebApp = () => {
1011
.disable('x-powered-by')
1112
.use((req, res, next) => {
1213
const settings = createSettings()
14+
const nonce = randomBytes(16).toString('base64')
15+
res.locals.nonce = nonce
1316

1417
const relayUrl = new URL(settings.info.relay_url)
1518
const webRelayUrl = new URL(relayUrl.toString())
1619
webRelayUrl.protocol = (relayUrl.protocol === 'wss:') ? 'https:' : ':'
1720

1821
const directives = {
19-
/**
20-
* TODO: Remove 'unsafe-inline'
21-
*/
2222
'img-src': ["'self'", 'data:', 'https://cdn.zebedee.io/an/nostr/'],
2323
'connect-src': ["'self'", settings.info.relay_url as string, webRelayUrl.toString()],
2424
'default-src': ["'self'"],
25-
'script-src-attr': ["'unsafe-inline'"],
26-
'script-src': ["'self'", "'unsafe-inline'", 'https://cdn.jsdelivr.net/npm/', 'https://unpkg.com/', 'https://cdnjs.cloudflare.com/ajax/libs/'],
25+
'script-src-attr': [`'nonce-${nonce}'`],
26+
'script-src': ["'self'", `'nonce-${nonce}'`, 'https://cdn.jsdelivr.net/npm/', 'https://unpkg.com/', 'https://cdnjs.cloudflare.com/ajax/libs/'],
2727
'style-src': ["'self'", 'https://cdn.jsdelivr.net/npm/'],
2828
'font-src': ["'self'", 'https://cdn.jsdelivr.net/npm/'],
2929
}
@@ -32,7 +32,8 @@ export const createWebApp = () => {
3232
})
3333
.use('/favicon.ico', express.static('./resources/favicon.ico'))
3434
.use('/css', express.static('./resources/css'))
35-
.use(router)
35+
36+
app.use(router)
3637

3738
return app
3839
}

src/handlers/request-handlers/get-terms-request-handler.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@ import { readFileSync } from 'fs'
33

44
import { createSettings as settings } from '../../factories/settings-factory'
55

6-
let pageCache: string
6+
77

88
export const getTermsRequestHandler = (_req: Request, res: Response, next: NextFunction) => {
99
const { info: { name } } = settings()
1010

11-
if (!pageCache) {
12-
pageCache = readFileSync('./resources/terms.html', 'utf8').replaceAll('{{name}}', name)
13-
}
11+
const page = readFileSync('./resources/terms.html', 'utf8')
12+
.replaceAll('{{name}}', name)
13+
.replaceAll('{{nonce}}', res.locals.nonce)
1414

15-
res.status(200).setHeader('content-type', 'text/html; charset=utf8').send(pageCache)
15+
res.status(200).setHeader('content-type', 'text/html; charset=utf8').send(page)
1616
next()
1717
}

tsconfig.build.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"inlineSources": false,
1717
"resolveJsonModule": true,
1818
"esModuleInterop": true,
19-
"lib": ["ESNext"],
19+
"lib": ["ESNext", "DOM"],
2020
"incremental": true
2121
},
2222
"ts-node": {

0 commit comments

Comments
 (0)