-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add back prepublish script and exclude csrf-koa from coverage add example for koa 2 to readme
- Loading branch information
1 parent
b84e3f3
commit 15191f3
Showing
5 changed files
with
218 additions
and
4 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
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,61 @@ | ||
"use strict"; | ||
|
||
const Promise = require("bluebird"); | ||
const uuid = require("uuid"); | ||
const csrf = require("./csrf"); | ||
|
||
const MISSING_SECRET = "MISSING_SECRET"; | ||
const INVALID_JWT = "INVALID_JWT"; | ||
|
||
function csrfMiddleware(options) { | ||
if (!options || !options.secret) { | ||
throw new Error(MISSING_SECRET); | ||
} | ||
|
||
function middleware(ctx, next) { | ||
|
||
function createToken() { | ||
const id = uuid.v4(); | ||
const headerPayload = {type: "header", uuid: id}; | ||
const cookiePayload = {type: "cookie", uuid: id}; | ||
|
||
return Promise.all([ | ||
csrf.create(headerPayload, options), | ||
csrf.create(cookiePayload, options) | ||
]).spread((headerToken, cookieToken) => { | ||
ctx.set("x-csrf-jwt", headerToken); | ||
ctx.cookies.set("x-csrf-jwt", cookieToken); | ||
return next(); | ||
}); | ||
} | ||
|
||
function verifyAndCreateToken() { | ||
const headerPayload = {token: ctx.headers["x-csrf-jwt"], secret: options.secret}; | ||
const cookiePayload = {token: ctx.cookies.get("x-csrf-jwt"), secret: options.secret}; | ||
|
||
return Promise.all([ | ||
csrf.verify(headerPayload), | ||
csrf.verify(cookiePayload) | ||
]).spread((headerToken, cookieToken) => { | ||
if (headerToken.uuid === cookieToken.uuid && | ||
headerToken.type === "header" && cookieToken.type === "cookie") { | ||
return createToken(); | ||
} | ||
ctx.throw(new Error(INVALID_JWT)); | ||
}).catch((err) => { | ||
ctx.throw(err); | ||
}); | ||
} | ||
|
||
const method = ctx.method.toUpperCase(); | ||
if (method !== "GET" && method !== "HEAD") { | ||
return verifyAndCreateToken(); | ||
} | ||
|
||
return createToken(); | ||
} | ||
|
||
return middleware; | ||
} | ||
|
||
module.exports = csrfMiddleware; |
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
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
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,130 @@ | ||
"use strict"; | ||
|
||
const bodyParser = require("koa-bodyparser"); | ||
const Router = require("koa-router"); | ||
const Koa = require("koa"); | ||
const csrfMiddleware = require("../../lib/index").koaMiddleware; | ||
const jwt = require("jsonwebtoken"); | ||
|
||
const fetch = require("isomorphic-fetch"); | ||
|
||
const secret = "test"; | ||
const url = "http://localhost:4000"; | ||
let server; | ||
|
||
describe("test register", () => { | ||
it("should fail with bad options", () => { | ||
const app = new Koa(); | ||
try { | ||
app.use(csrfMiddleware()); | ||
} catch (e) { | ||
expect(e.message).to.equal("MISSING_SECRET"); | ||
} | ||
}); | ||
}); | ||
|
||
describe("test csrf-jwt koa middleware", () => { | ||
before(() => { | ||
const app = new Koa(); | ||
const router = new Router(); | ||
|
||
app.use(bodyParser()); | ||
|
||
const options = { | ||
secret, | ||
expiresIn: "2d", | ||
ignoreThisParam: "ignore" | ||
}; | ||
app.use(csrfMiddleware(options)); | ||
|
||
router.get("/1", (ctx) => { | ||
ctx.body = "valid"; | ||
}); | ||
|
||
router.post("/2", (ctx) => { | ||
expect(ctx.request.body.message).to.equal("hello"); | ||
ctx.body = "valid"; | ||
}); | ||
|
||
app.use(router.routes()); | ||
|
||
server = require("http").createServer(app.callback()); | ||
server.listen(4000); | ||
}); | ||
|
||
after(() => { | ||
server.close(); | ||
}); | ||
|
||
it("should return success", () => { | ||
return fetch(`${url}/1`) | ||
.then((res) => { | ||
expect(res.status).to.equal(200); | ||
const csrfHeader = res.headers.get("x-csrf-jwt"); | ||
const csrfCookie = res.headers.get("set-cookie"); | ||
expect(csrfHeader).to.exist; | ||
expect(csrfCookie).to.contain("x-csrf-jwt="); | ||
|
||
return fetch(`${url}/2`, { | ||
method: "POST", | ||
headers: { | ||
"Accept": "application/json", | ||
"Content-Type": "application/json", | ||
"x-csrf-jwt": csrfHeader, | ||
"Cookie": csrfCookie | ||
}, | ||
body: JSON.stringify({message: "hello"}) | ||
}).then((res) => { | ||
expect(res.status).to.equal(200); | ||
expect(res.headers.get("x-csrf-jwt")).to.exist; | ||
expect(res.headers.get("set-cookie")).to.contain("x-csrf-jwt="); | ||
}); | ||
}); | ||
}); | ||
|
||
it("should return 500 for missing jwt", () => { | ||
return fetch(`${url}/2`, { | ||
method: "POST", | ||
headers: { | ||
"Accept": "application/json", | ||
"Content-Type": "application/json" | ||
}, | ||
body: JSON.stringify({message: "hello"}) | ||
}).then((res) => { | ||
expect(res.status).to.equal(500); | ||
expect(res.headers.get("x-csrf-jwt")).to.not.exist; | ||
expect(res.headers.get("set-cookie")).to.not.exist; | ||
}); | ||
}); | ||
|
||
it("should return 500 for invalid jwt", () => { | ||
return fetch(`${url}/1`) | ||
.then(() => { | ||
const token = jwt.sign({uuid: "1"}, secret, {}); | ||
return fetch(`${url}/2`, { | ||
method: "POST", | ||
headers: { | ||
"Accept": "application/json", | ||
"Content-Type": "application/json", | ||
"x-csrf-jwt": token, | ||
"Cookie": `x-csrf-jwt=${token}` | ||
}, | ||
body: JSON.stringify({message: "hello"}) | ||
}).then((res) => { | ||
expect(res.status).to.equal(500); | ||
return fetch(`${url}/2`, { | ||
method: "POST", | ||
headers: { | ||
"Accept": "application/json", | ||
"Content-Type": "application/json", | ||
"x-csrf-jwt": "invalid", | ||
"Cookie": `x-csrf-jwt=${token}` | ||
}, | ||
body: JSON.stringify({message: "hello"}) | ||
}).then((res) => { | ||
expect(res.status).to.equal(500); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); |