From e04db2c49cd82c61359accc64e4efa58a8b32246 Mon Sep 17 00:00:00 2001 From: Michael Jackson Date: Sat, 1 Sep 2018 09:36:48 -0700 Subject: [PATCH] Fix auth using header --- modules/__tests__/_auth-test.js | 65 ++++ modules/__tests__/_blacklist-test.js | 109 +++++++ modules/__tests__/_publicKey-test.js | 21 ++ modules/__tests__/api-auth-test.js | 89 ++++++ modules/__tests__/api-blacklist-test.js | 109 +++++++ modules/__tests__/api-publicKey-test.js | 21 ++ modules/__tests__/api-test.js | 342 ---------------------- modules/__tests__/utils/withAuthHeader.js | 13 + modules/middleware/userToken.js | 12 +- package.json | 1 - yarn.lock | 2 +- 11 files changed, 435 insertions(+), 349 deletions(-) create mode 100644 modules/__tests__/_auth-test.js create mode 100644 modules/__tests__/_blacklist-test.js create mode 100644 modules/__tests__/_publicKey-test.js create mode 100644 modules/__tests__/api-auth-test.js create mode 100644 modules/__tests__/api-blacklist-test.js create mode 100644 modules/__tests__/api-publicKey-test.js delete mode 100644 modules/__tests__/api-test.js create mode 100644 modules/__tests__/utils/withAuthHeader.js diff --git a/modules/__tests__/_auth-test.js b/modules/__tests__/_auth-test.js new file mode 100644 index 0000000..f90d2d1 --- /dev/null +++ b/modules/__tests__/_auth-test.js @@ -0,0 +1,65 @@ +const request = require("supertest"); + +const createServer = require("../createServer"); +const withRevokedToken = require("./utils/withRevokedToken"); +const withToken = require("./utils/withToken"); + +describe("The /_auth endpoint", () => { + let server; + beforeEach(() => { + server = createServer(); + }); + + describe("POST /_auth", () => { + it("creates a new auth token", done => { + request(server) + .post("/_auth") + .end((err, res) => { + expect(res.body).toHaveProperty("token"); + done(); + }); + }); + }); + + describe("GET /_auth", () => { + describe("with no auth", () => { + it("echoes back null", done => { + request(server) + .get("/_auth") + .end((err, res) => { + expect(res.body).toHaveProperty("auth"); + expect(res.body.auth).toBe(null); + done(); + }); + }); + }); + + describe("with a revoked auth token", () => { + it("echoes back null", done => { + withRevokedToken({ some: { scope: true } }, token => { + request(server) + .get("/_auth?token=" + token) + .end((err, res) => { + expect(res.body).toHaveProperty("auth"); + expect(res.body.auth).toBe(null); + done(); + }); + }); + }); + }); + + describe("with a valid auth token", () => { + it("echoes back the auth payload", done => { + withToken({ some: { scope: true } }, token => { + request(server) + .get("/_auth?token=" + token) + .end((err, res) => { + expect(res.body).toHaveProperty("auth"); + expect(typeof res.body.auth).toBe("object"); + done(); + }); + }); + }); + }); + }); +}); diff --git a/modules/__tests__/_blacklist-test.js b/modules/__tests__/_blacklist-test.js new file mode 100644 index 0000000..0e04c9a --- /dev/null +++ b/modules/__tests__/_blacklist-test.js @@ -0,0 +1,109 @@ +const request = require("supertest"); + +const createServer = require("../createServer"); +const clearBlacklist = require("./utils/clearBlacklist"); +const withToken = require("./utils/withToken"); + +describe("The /_blacklist endpoint", () => { + let server; + beforeEach(() => { + server = createServer(); + }); + + describe("POST /_blacklist", () => { + afterEach(clearBlacklist); + + describe("with no auth", () => { + it("is forbidden", done => { + request(server) + .post("/_blacklist") + .end((err, res) => { + expect(res.statusCode).toBe(403); + done(); + }); + }); + }); + + describe('with the "blacklist.add" scope', () => { + it("can add to the blacklist", done => { + withToken({ blacklist: { add: true } }, token => { + request(server) + .post("/_blacklist") + .send({ token, packageName: "bad-package" }) + .end((err, res) => { + expect(res.statusCode).toBe(200); + expect(res.body.ok).toBe(true); + done(); + }); + }); + }); + }); + }); + + describe("GET /_blacklist", () => { + describe("with no auth", () => { + it("is forbidden", done => { + request(server) + .get("/_blacklist") + .end((err, res) => { + expect(res.statusCode).toBe(403); + done(); + }); + }); + }); + + describe('with the "blacklist.read" scope', () => { + it("can read the blacklist", done => { + withToken({ blacklist: { read: true } }, token => { + request(server) + .get("/_blacklist?token=" + token) + .end((err, res) => { + expect(res.statusCode).toBe(200); + done(); + }); + }); + }); + }); + }); + + describe("DELETE /_blacklist/:packageName", () => { + describe("with no auth", () => { + it("is forbidden", done => { + request(server) + .delete("/_blacklist/bad-package") + .end((err, res) => { + expect(res.statusCode).toBe(403); + done(); + }); + }); + }); + + describe('with the "blacklist.remove" scope', () => { + it("can remove a package from the blacklist", done => { + withToken({ blacklist: { remove: true } }, token => { + request(server) + .delete("/_blacklist/bad-package") + .send({ token }) + .end((err, res) => { + expect(res.statusCode).toBe(200); + expect(res.body.ok).toBe(true); + done(); + }); + }); + }); + + it("can remove a scoped package from the blacklist", done => { + withToken({ blacklist: { remove: true } }, token => { + request(server) + .delete("/_blacklist/@scope/bad-package") + .send({ token }) + .end((err, res) => { + expect(res.statusCode).toBe(200); + expect(res.body.ok).toBe(true); + done(); + }); + }); + }); + }); + }); +}); diff --git a/modules/__tests__/_publicKey-test.js b/modules/__tests__/_publicKey-test.js new file mode 100644 index 0000000..fd65fd5 --- /dev/null +++ b/modules/__tests__/_publicKey-test.js @@ -0,0 +1,21 @@ +const request = require("supertest"); + +const createServer = require("../createServer"); + +describe("The /_publicKey endpoint", () => { + let server; + beforeEach(() => { + server = createServer(); + }); + + describe("GET /_publicKey", () => { + it("echoes the public key", done => { + request(server) + .get("/_publicKey") + .end((err, res) => { + expect(res.text).toMatch(/PUBLIC KEY/); + done(); + }); + }); + }); +}); diff --git a/modules/__tests__/api-auth-test.js b/modules/__tests__/api-auth-test.js new file mode 100644 index 0000000..4f621b9 --- /dev/null +++ b/modules/__tests__/api-auth-test.js @@ -0,0 +1,89 @@ +const request = require("supertest"); + +const createServer = require("../createServer"); +const withAuthHeader = require("./utils/withAuthHeader"); +const withRevokedToken = require("./utils/withRevokedToken"); +const withToken = require("./utils/withToken"); + +describe("The /api/auth endpoint", () => { + let server; + beforeEach(() => { + server = createServer(); + }); + + describe("POST /api/auth", () => { + it("creates a new auth token", done => { + request(server) + .post("/api/auth") + .end((err, res) => { + expect(res.body).toHaveProperty("token"); + done(); + }); + }); + }); + + describe("GET /api/auth", () => { + describe("with no auth", () => { + it("echoes back null", done => { + request(server) + .get("/api/auth") + .end((err, res) => { + expect(res.body).toHaveProperty("auth"); + expect(res.body.auth).toBe(null); + done(); + }); + }); + }); + + describe("with a revoked auth token", () => { + it("echoes back null", done => { + withRevokedToken({ some: { scope: true } }, token => { + request(server) + .get("/api/auth?token=" + token) + .end((err, res) => { + expect(res.body).toHaveProperty("auth"); + expect(res.body.auth).toBe(null); + done(); + }); + }); + }); + }); + + describe("with a valid auth token", () => { + describe("in the query string", () => { + it("echoes back the auth payload", done => { + const scopes = { some: { scope: true } }; + + withToken(scopes, token => { + request(server) + .get("/api/auth?token=" + token) + .end((err, res) => { + expect(res.body).toHaveProperty("auth"); + expect(res.body.auth).toBeDefined(); + expect(res.body.auth.scopes).toMatchObject(scopes); + done(); + }); + }); + }); + }); + + describe("in the Authorization header", () => { + it.only("echoes back the auth payload", done => { + const scopes = { some: { scope: true } }; + + withAuthHeader(scopes, header => { + request(server) + .get("/api/auth") + .set({ Authorization: header }) + .end((err, res) => { + expect(res.body).toHaveProperty("auth"); + expect(res.body.auth).toBeDefined(); + expect(res.body.auth.scopes).toMatchObject(scopes); + done(); + }); + }); + }); + }); + }); + }); +}); diff --git a/modules/__tests__/api-blacklist-test.js b/modules/__tests__/api-blacklist-test.js new file mode 100644 index 0000000..77092fc --- /dev/null +++ b/modules/__tests__/api-blacklist-test.js @@ -0,0 +1,109 @@ +const request = require("supertest"); + +const createServer = require("../createServer"); +const clearBlacklist = require("./utils/clearBlacklist"); +const withToken = require("./utils/withToken"); + +describe("The /api/blacklist endpoint", () => { + let server; + beforeEach(() => { + server = createServer(); + }); + + describe("POST /api/blacklist", () => { + afterEach(clearBlacklist); + + describe("with no auth", () => { + it("is forbidden", done => { + request(server) + .post("/api/blacklist") + .end((err, res) => { + expect(res.statusCode).toBe(403); + done(); + }); + }); + }); + + describe('with the "blacklist.add" scope', () => { + it("can add to the blacklist", done => { + withToken({ blacklist: { add: true } }, token => { + request(server) + .post("/api/blacklist") + .send({ token, packageName: "bad-package" }) + .end((err, res) => { + expect(res.statusCode).toBe(200); + expect(res.body.ok).toBe(true); + done(); + }); + }); + }); + }); + }); + + describe("GET /api/blacklist", () => { + describe("with no auth", () => { + it("is forbidden", done => { + request(server) + .get("/api/blacklist") + .end((err, res) => { + expect(res.statusCode).toBe(403); + done(); + }); + }); + }); + + describe('with the "blacklist.read" scope', () => { + it("can read the blacklist", done => { + withToken({ blacklist: { read: true } }, token => { + request(server) + .get("/api/blacklist?token=" + token) + .end((err, res) => { + expect(res.statusCode).toBe(200); + done(); + }); + }); + }); + }); + }); + + describe("DELETE /api/blacklist", () => { + describe("with no auth", () => { + it("is forbidden", done => { + request(server) + .delete("/api/blacklist") + .end((err, res) => { + expect(res.statusCode).toBe(403); + done(); + }); + }); + }); + + describe('with the "blacklist.remove" scope', () => { + it("can remove a package from the blacklist", done => { + withToken({ blacklist: { remove: true } }, token => { + request(server) + .delete("/api/blacklist") + .send({ token, packageName: "bad-package" }) + .end((err, res) => { + expect(res.statusCode).toBe(200); + expect(res.body.ok).toBe(true); + done(); + }); + }); + }); + + it("can remove a scoped package from the blacklist", done => { + withToken({ blacklist: { remove: true } }, token => { + request(server) + .delete("/api/blacklist") + .send({ token, packageName: "@scope/bad-package" }) + .end((err, res) => { + expect(res.statusCode).toBe(200); + expect(res.body.ok).toBe(true); + done(); + }); + }); + }); + }); + }); +}); diff --git a/modules/__tests__/api-publicKey-test.js b/modules/__tests__/api-publicKey-test.js new file mode 100644 index 0000000..971fa62 --- /dev/null +++ b/modules/__tests__/api-publicKey-test.js @@ -0,0 +1,21 @@ +const request = require("supertest"); + +const createServer = require("../createServer"); + +describe("The /api/publicKey endpoint", () => { + let server; + beforeEach(() => { + server = createServer(); + }); + + describe("GET /api/publicKey", () => { + it("echoes the public key", done => { + request(server) + .get("/api/publicKey") + .end((err, res) => { + expect(res.text).toMatch(/PUBLIC KEY/); + done(); + }); + }); + }); +}); diff --git a/modules/__tests__/api-test.js b/modules/__tests__/api-test.js deleted file mode 100644 index 35eb080..0000000 --- a/modules/__tests__/api-test.js +++ /dev/null @@ -1,342 +0,0 @@ -const request = require("supertest"); - -const createServer = require("../createServer"); - -const clearBlacklist = require("./utils/clearBlacklist"); -const withRevokedToken = require("./utils/withRevokedToken"); -const withToken = require("./utils/withToken"); - -describe("The API server", () => { - let server; - beforeEach(() => { - server = createServer(); - }); - - describe("GET /api/publicKey", () => { - it("echoes the public key", done => { - request(server) - .get("/api/publicKey") - .end((err, res) => { - expect(res.text).toMatch(/PUBLIC KEY/); - done(); - }); - }); - }); - - // TODO: Remove - describe("GET /_publicKey", () => { - it("echoes the public key", done => { - request(server) - .get("/_publicKey") - .end((err, res) => { - expect(res.text).toMatch(/PUBLIC KEY/); - done(); - }); - }); - }); - - describe("POST /api/auth", () => { - it("creates a new auth token", done => { - request(server) - .post("/api/auth") - .end((err, res) => { - expect(res.body).toHaveProperty("token"); - done(); - }); - }); - }); - - // TODO: Remove - describe("POST /_auth", () => { - it("creates a new auth token", done => { - request(server) - .post("/_auth") - .end((err, res) => { - expect(res.body).toHaveProperty("token"); - done(); - }); - }); - }); - - describe("GET /api/auth", () => { - describe("with no auth", () => { - it("echoes back null", done => { - request(server) - .get("/api/auth") - .end((err, res) => { - expect(res.body).toHaveProperty("auth"); - expect(res.body.auth).toBe(null); - done(); - }); - }); - }); - - describe("with a revoked auth token", () => { - it("echoes back null", done => { - withRevokedToken({ some: { scope: true } }, token => { - request(server) - .get("/api/auth?token=" + token) - .end((err, res) => { - expect(res.body).toHaveProperty("auth"); - expect(res.body.auth).toBe(null); - done(); - }); - }); - }); - }); - - describe("with a valid auth token", () => { - it("echoes back the auth payload", done => { - withToken({ some: { scope: true } }, token => { - request(server) - .get("/api/auth?token=" + token) - .end((err, res) => { - expect(res.body).toHaveProperty("auth"); - expect(typeof res.body.auth).toBe("object"); - done(); - }); - }); - }); - }); - }); - - // TODO: Remove - describe("GET /_auth", () => { - describe("with no auth", () => { - it("echoes back null", done => { - request(server) - .get("/_auth") - .end((err, res) => { - expect(res.body).toHaveProperty("auth"); - expect(res.body.auth).toBe(null); - done(); - }); - }); - }); - - describe("with a revoked auth token", () => { - it("echoes back null", done => { - withRevokedToken({ some: { scope: true } }, token => { - request(server) - .get("/_auth?token=" + token) - .end((err, res) => { - expect(res.body).toHaveProperty("auth"); - expect(res.body.auth).toBe(null); - done(); - }); - }); - }); - }); - - describe("with a valid auth token", () => { - it("echoes back the auth payload", done => { - withToken({ some: { scope: true } }, token => { - request(server) - .get("/_auth?token=" + token) - .end((err, res) => { - expect(res.body).toHaveProperty("auth"); - expect(typeof res.body.auth).toBe("object"); - done(); - }); - }); - }); - }); - }); - - describe("POST /api/blacklist", () => { - afterEach(clearBlacklist); - - describe("with no auth", () => { - it("is forbidden", done => { - request(server) - .post("/api/blacklist") - .end((err, res) => { - expect(res.statusCode).toBe(403); - done(); - }); - }); - }); - - describe('with the "blacklist.add" scope', () => { - it("can add to the blacklist", done => { - withToken({ blacklist: { add: true } }, token => { - request(server) - .post("/api/blacklist") - .send({ token, packageName: "bad-package" }) - .end((err, res) => { - expect(res.statusCode).toBe(200); - expect(res.body.ok).toBe(true); - done(); - }); - }); - }); - }); - }); - - // TODO: Remove - describe("POST /_blacklist", () => { - afterEach(clearBlacklist); - - describe("with no auth", () => { - it("is forbidden", done => { - request(server) - .post("/_blacklist") - .end((err, res) => { - expect(res.statusCode).toBe(403); - done(); - }); - }); - }); - - describe('with the "blacklist.add" scope', () => { - it("can add to the blacklist", done => { - withToken({ blacklist: { add: true } }, token => { - request(server) - .post("/_blacklist") - .send({ token, packageName: "bad-package" }) - .end((err, res) => { - expect(res.statusCode).toBe(200); - expect(res.body.ok).toBe(true); - done(); - }); - }); - }); - }); - }); - - describe("GET /api/blacklist", () => { - describe("with no auth", () => { - it("is forbidden", done => { - request(server) - .get("/api/blacklist") - .end((err, res) => { - expect(res.statusCode).toBe(403); - done(); - }); - }); - }); - - describe('with the "blacklist.read" scope', () => { - it("can read the blacklist", done => { - withToken({ blacklist: { read: true } }, token => { - request(server) - .get("/api/blacklist?token=" + token) - .end((err, res) => { - expect(res.statusCode).toBe(200); - done(); - }); - }); - }); - }); - }); - - // TODO: Remove - describe("GET /_blacklist", () => { - describe("with no auth", () => { - it("is forbidden", done => { - request(server) - .get("/_blacklist") - .end((err, res) => { - expect(res.statusCode).toBe(403); - done(); - }); - }); - }); - - describe('with the "blacklist.read" scope', () => { - it("can read the blacklist", done => { - withToken({ blacklist: { read: true } }, token => { - request(server) - .get("/_blacklist?token=" + token) - .end((err, res) => { - expect(res.statusCode).toBe(200); - done(); - }); - }); - }); - }); - }); - - describe("DELETE /api/blacklist", () => { - describe("with no auth", () => { - it("is forbidden", done => { - request(server) - .delete("/api/blacklist") - .end((err, res) => { - expect(res.statusCode).toBe(403); - done(); - }); - }); - }); - - describe('with the "blacklist.remove" scope', () => { - it("can remove a package from the blacklist", done => { - withToken({ blacklist: { remove: true } }, token => { - request(server) - .delete("/api/blacklist") - .send({ token, packageName: "bad-package" }) - .end((err, res) => { - expect(res.statusCode).toBe(200); - expect(res.body.ok).toBe(true); - done(); - }); - }); - }); - - it("can remove a scoped package from the blacklist", done => { - withToken({ blacklist: { remove: true } }, token => { - request(server) - .delete("/api/blacklist") - .send({ token, packageName: "@scope/bad-package" }) - .end((err, res) => { - expect(res.statusCode).toBe(200); - expect(res.body.ok).toBe(true); - done(); - }); - }); - }); - }); - }); - - // TODO: Remove - describe("DELETE /_blacklist/:packageName", () => { - describe("with no auth", () => { - it("is forbidden", done => { - request(server) - .delete("/_blacklist/bad-package") - .end((err, res) => { - expect(res.statusCode).toBe(403); - done(); - }); - }); - }); - - describe('with the "blacklist.remove" scope', () => { - it("can remove a package from the blacklist", done => { - withToken({ blacklist: { remove: true } }, token => { - request(server) - .delete("/_blacklist/bad-package") - .send({ token }) - .end((err, res) => { - expect(res.statusCode).toBe(200); - expect(res.body.ok).toBe(true); - done(); - }); - }); - }); - - it("can remove a scoped package from the blacklist", done => { - withToken({ blacklist: { remove: true } }, token => { - request(server) - .delete("/_blacklist/@scope/bad-package") - .send({ token }) - .end((err, res) => { - expect(res.statusCode).toBe(200); - expect(res.body.ok).toBe(true); - done(); - }); - }); - }); - }); - }); -}); diff --git a/modules/__tests__/utils/withAuthHeader.js b/modules/__tests__/utils/withAuthHeader.js new file mode 100644 index 0000000..5c3f884 --- /dev/null +++ b/modules/__tests__/utils/withAuthHeader.js @@ -0,0 +1,13 @@ +const withToken = require("./withToken"); + +function encodeBase64(token) { + return Buffer.from(token).toString("base64"); +} + +function withAuthHeader(scopes, done) { + withToken(scopes, token => { + done(encodeBase64(token)); + }); +} + +module.exports = withAuthHeader; diff --git a/modules/middleware/userToken.js b/modules/middleware/userToken.js index e78c45e..afd8bdd 100644 --- a/modules/middleware/userToken.js +++ b/modules/middleware/userToken.js @@ -1,9 +1,11 @@ -const basicAuth = require("basic-auth"); - const AuthAPI = require("../AuthAPI"); const ReadMethods = { GET: true, HEAD: true }; +function decodeBase64(string) { + return Buffer.from(string, "base64").toString(); +} + /** * Sets req.user from the payload in the auth token in the request. */ @@ -12,9 +14,9 @@ function userToken(req, res, next) { return next(); } - const credentials = basicAuth(req); - const token = credentials - ? credentials.pass + const auth = req.get("Authorization"); + const token = auth + ? decodeBase64(auth) : (ReadMethods[req.method] ? req.query : req.body).token; if (!token) { diff --git a/package.json b/package.json index 580ecc9..7f679a7 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,6 @@ "babel-preset-react": "^6.24.1", "babel-preset-stage-2": "^6.24.1", "babel-register": "^6.26.0", - "basic-auth": "^2.0.0", "body-parser": "^1.18.2", "cors": "^2.8.1", "countries-list": "^1.3.2", diff --git a/yarn.lock b/yarn.lock index 8acd05d..22ca323 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1144,7 +1144,7 @@ base@^0.11.1: mixin-deep "^1.2.0" pascalcase "^0.1.1" -basic-auth@^2.0.0, basic-auth@~2.0.0: +basic-auth@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.0.tgz#015db3f353e02e56377755f962742e8981e7bbba" dependencies: