Skip to content

Commit e7606c2

Browse files
authored
Merge pull request #558 from architect/csrf
first pass at adding basic csrf tokens
2 parents 6179b18 + d6c7cb3 commit e7606c2

File tree

6 files changed

+61
-0
lines changed

6 files changed

+61
-0
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"main": "src/index",
1212
"types": "types/index.d.ts",
1313
"scripts": {
14+
"test:one": "cross-env tape 'test/unit/src/http/csrf/*-test.js' | tap-arc",
1415
"lint": "eslint --fix .",
1516
"test": "npm run lint && npm run test:integration && npm run coverage && npm run test:types",
1617
"test:nolint": "npm run test:integration && npm run coverage && npm run test:types",

readme.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ let {
4646
- [`http()`](https://arc.codes/docs/en/reference/runtime-helpers/node.js#arc.http)
4747
- [`http` middleware](https://arc.codes/docs/en/reference/runtime-helpers/node.js#middleware)
4848
- [`http.session`](https://arc.codes/docs/en/reference/runtime-helpers/node.js#arc.http.session)
49+
- [`http.csrf`](https://arc.codes/docs/en/reference/runtime-helpers/node.js#arc.http.csrf)
4950

5051
**[`@queues` methods](https://arc.codes/docs/en/reference/runtime-helpers/node.js#arc.queues)**
5152
- [`queues.subscribe()`](https://arc.codes/docs/en/reference/runtime-helpers/node.js#arc.queues.subscribe())

src/http/csrf/create.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
const crypto = require('node:crypto')
2+
const fiveMinutes = 300000
3+
4+
/** creates a signed token [rando].[timestamp].[sig] */
5+
module.exports = function create (data, ts) {
6+
data = data || Buffer.from(crypto.randomUUID().replace(/-/g, ''))
7+
ts = ts || Date.now() + fiveMinutes
8+
const secret = process.env.ARC_APP_SECRET || process.env.ARC_APP_NAME || 'fallback'
9+
return `${data}.${ts}.${crypto.createHmac('sha256', secret).update(data).digest('hex').toString()}`
10+
}

src/http/csrf/verify.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
let create = require('./create')
2+
3+
/** ensures payload is valid token that hasn't expired */
4+
module.exports = function verify (payload) {
5+
const [ data, ts, sig ] = payload.split('.')
6+
if (Date.now() > ts) return false
7+
const gen = create(data, ts)
8+
const sig2 = gen.split('.').pop()
9+
return sig2 === sig
10+
}

src/http/index.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ let bodyParser = require('./helpers/body-parser')
44
let interpolate = require('./helpers/params')
55
let url = require('./helpers/url')
66
let responseFormatter = require('./_res-fmt')
7+
let create = require('./csrf/create')
8+
let verify = require('./csrf/verify')
79

810
// Unified async / callback HTTP handler
911
function httpHandler (isAsync, ...fns) {
@@ -95,6 +97,9 @@ http.helpers = { bodyParser, interpolate, url }
9597
// Session
9698
http.session = { read, write }
9799

100+
// CSRF
101+
http.csrf = { create, verify }
102+
98103
module.exports = http
99104

100105
/**
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
let http = require('../../../../../src/http')
2+
let test = require('tape')
3+
4+
test('exists', t => {
5+
t.plan(2)
6+
t.ok(http.csrf.create, 'create')
7+
t.ok(http.csrf.verify, 'verify')
8+
})
9+
10+
test('create a value', t => {
11+
t.plan(1)
12+
let val = http.csrf.create()
13+
t.ok(val, 'created value')
14+
})
15+
16+
test('verify a value', t => {
17+
t.plan(1)
18+
let val = http.csrf.create()
19+
t.ok(http.csrf.verify(val), 'value verified')
20+
})
21+
22+
test('tampered token is falsy', t => {
23+
t.plan(1)
24+
let tamperedToken = "3d879d515ab241429c97dfea6d1e1927.1584118407000.b0b34563d569030cbe9a4ea63312f23729813b838478420e3811c0bfeaf3add1"
25+
t.ok(http.csrf.verify(tamperedToken) === false, 'value falsy')
26+
})
27+
28+
test('token expired is falsy', t => {
29+
t.plan(1)
30+
let expiredToken = "3d879d515ab241419c97dfea6d1e1927.1584118407000.b0b34563d569030cbe9a4ea63312f23729813b838478420e3811c0bfeaf3add1"
31+
t.ok(http.csrf.verify(expiredToken) === false, 'value falsy')
32+
})
33+
34+

0 commit comments

Comments
 (0)