btrz-auth-api-key
Version:
Betterez api authorization library
1,170 lines (1,060 loc) • 53.7 kB
JavaScript
const assert = require("node:assert/strict");
const { describe, it, beforeEach, afterEach } = require("node:test");
const context = describe;
describe("Express integration", function () {
let request = require("supertest"),
Chance = require("chance").Chance,
chance = new Chance(),
sinon = require("sinon"),
express = require("express"),
bodyParser = require("body-parser"),
jwt = require("jsonwebtoken"),
SimpleDao = require("btrz-simple-dao").SimpleDao,
mockLogger = { info() { }, error() { } },
constants = require("../constants"),
{ Authenticator, InternalAuthTokenProvider, audiences } = require("../"),
app,
testKey = "test-api-key",
validKey = "72ed8526-24a6-497f-8949-ec7ed6766aaf",
validKeyWithNoUser = "10967537-7ea4-46f3-a723-9822db056646",
validKeyWithDeletedUser = "10967537-7ea4-46f3-a723-9822db055757",
privateKey = "492a97f3-597f-4b54-84f5-f8ad3eb6ee36",
internalAuthTokenSigningSecrets = {
main: chance.hash(),
secondary: chance.hash()
},
internalAuthTokenProvider = null,
testUser = { _id: chance.hash(), name: "Test", last: "User" },
testFullUser = { _id: SimpleDao.objectId(), name: "Test", last: "User", display: "Testing", password: chance.hash(), deleted: false },
deletedUser = { _id: SimpleDao.objectId(), deleted: true },
userTokenSigningOptions = { algorithm: "HS512", expiresIn: "2 days", issuer: "btrz-api-accounts", subject: "account_user_sign_in"},
internalTokenSigningOptions = {
algorithm: "HS512", expiresIn: "2 minutes",
issuer: constants.INTERNAL_AUTH_TOKEN_ISSUER,
audience: "betterez-app"
},
validToken = jwt.sign({ user: testFullUser, aud: "betterez-app"}, privateKey, userTokenSigningOptions),
validBackofficeToken = jwt.sign({ user: testFullUser, aud: "betterez-app" }, privateKey, userTokenSigningOptions),
validBackofficeTokenForOtherApp = jwt.sign({ user: testFullUser, aud: "other-app" }, privateKey, userTokenSigningOptions),
validInternalToken = jwt.sign({}, internalAuthTokenSigningSecrets.main, internalTokenSigningOptions),
validCustomerToken = jwt.sign({ customer: { _id: 1, customerNumber: "111-222-333" }, aud: "customer" }, privateKey, userTokenSigningOptions),
testToken = "test-token",
options,
clock,
simpleDao;
const keyWithoutChannels = chance.guid();
const privateKeyWithoutChannels = chance.guid();
const application = {
"_id": SimpleDao.objectId("608808b2481ef95330d4b98e"),
"accountId": "595f9c7007ee12686d000032",
"userId": "608808b2481ef95330d4b98e",
"key": "6e720915-e3ff-4dba-8a10-bc1ae4406fd4",
"privateKey": "123ac3f1-619f-4f33-b903-97241c4a79b6",
"name": "signon",
"description": "",
"internal": false,
"premium": [],
channels: ["backoffice", "agency-backoffice"]
};
const validApplicationToken = jwt.sign(application, application.privateKey, userTokenSigningOptions);
const validApplicationTokenWithoutChannels = jwt.sign({}, privateKeyWithoutChannels, userTokenSigningOptions);
const apiKeys = [
{accountId: chance.hash(), key: validKey, privateKey: privateKey, userId: testFullUser._id.toString()},
{accountId: chance.hash(), key: validKeyWithNoUser, privateKey: chance.guid(), userId: SimpleDao.objectId().toString()},
{accountId: chance.hash(), key: validKeyWithDeletedUser, privateKey: chance.guid(), userId: deletedUser._id.toString()},
{...application},
{accountId: chance.hash(), key: keyWithoutChannels, privateKey: privateKeyWithoutChannels, userId: deletedUser._id.toString(), channles: []}
];
beforeEach(async () => {
options = {
"testKey": testKey,
"testUser": testUser,
"testToken": testToken,
"authKeyFields" : {
request: "apiKey"
},
"ignoredRoutes": [
"^/api-docs",
"^/ignoredsecure",
"^/ignored-and-secure",
"^/say-no$",
"^/route/for/internal/use/only",
{route: "^/ignored-get-put", methods: ["GET", "PUT"]}
],
"collection": {
"name": "apikeys",
"property": "key"
},
"db": {
"options": {
"database": "btrzAuthApiKeyTest",
"username": "",
"password": ""
},
"uris": [
"127.0.0.1:27017"
]
},
internalAuthTokenSigningSecrets,
};
let auth = new Authenticator(options, mockLogger);
internalAuthTokenProvider = new InternalAuthTokenProvider(options);
app = express();
app.use(auth.initialize({userProperty: "account"}));
app.use(auth.authenticate());
app.use(bodyParser.json());
app.get("/api-docs", function (req, res) {
res.status(200).json({docs: "documents"});
});
app.get("/api-docs/pets", function (req, res) {
res.status(200).json({docs: "documents"});
});
app.get("/hello-world", function (req, res) {
res.status(200).json(req.account);
});
app.put("/ignored-get-put", function (req, res) {
res.status(200).json(req.account);
});
app.get("/ignored-get-put", function (req, res) {
res.status(200).json(req.account);
});
app.post("/ignored-get-put", function (req, res) {
res.status(200).json(req.account);
});
app.get("/secured", auth.tokenSecuredForAudiences([audiences.BETTEREZ_APP]), function (req, res) {
res.status(200).json(req.user);
});
app.get("/ignoredsecure", auth.tokenSecuredForAudiences([audiences.BETTEREZ_APP]), function (req, res) {
res.status(200).json(req.account);
});
app.get("/ignored-and-secure", auth.optionalTokenSecured, function (req, res) {
res.status(200).json(req.account);
});
app.get("/backoffice", auth.tokenSecuredForBackoffice, function (req, res) {
res.status(200).json(req.user || {message: "no token"});
});
app.post("/backoffice", auth.tokenSecuredForBackoffice, function (req, res) {
res.status(200).json(req.user || {message: "no token"});
});
app.get("/customer", auth.customerTokenSecured, function (req, res) {
res.status(200).json(req.user || {});
});
app.get("/route/for/internal/use/only", auth.tokenSecuredForInternal, function (req, res) {
res.status(200).json(req.user || {});
});
app.get("/allowOnlyCustomerOrBackoffice", auth.tokenSecuredForAudiences(["betterez-app", "customer"]), function (req, res) {
res.status(200).json(req.user || {});
});
app.get("/validate-jwt-if-given", auth.validateJwtIfGiven, function (req, res) {
res.status(200).json(req.user || {});
});
app.get("/gimmeTokens", function (req, res) {
res.status(200).json(req.tokens);
});
app.get("/gimmeTokensSecured", auth.tokenSecuredForAudiences([audiences.BETTEREZ_APP]), function (req, res) {
res.status(200).json(req.tokens);
});
app.get("/unsecureWithUser", function (req, res) {
res.status(200).json(req.user);
});
simpleDao = new SimpleDao(options);
const db = await simpleDao.connect();
await db.collection(options.collection.name)
.insertMany(apiKeys);
await db.collection("users")
.insertMany([testFullUser, deletedUser]);
});
afterEach(async () => {
if (clock) {
clock.restore();
clock = null;
}
const db = await simpleDao.connect();
await db.dropCollection("apikeys");
await db.dropCollection("users");
});
it("should return 200 ok if no X-API-KEY is present but route should not be secured and use internal token", async function () {
await request(app)
.get("/api-docs")
.set("Accept", "application/json")
.set("Authorization", `Bearer ${validInternalToken}`)
.then((response) => { assert.strictEqual(response.status, 200); return response; })
;
});
it("should return 401 if no X-API-KEY is present and route should not be secured (strict regexp)", async function () {
await request(app)
.get("/say-no/more")
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 401); return response; })
;
});
it("should return 200 ok if no X-API-KEY is present but route should not be secured and use internal token (ignore method GET)", async function () {
await request(app)
.get("/ignored-get-put")
.set("Accept", "application/json")
.set("Authorization", `Bearer ${validInternalToken}`)
.then((response) => { assert.strictEqual(response.status, 200); return response; })
;
});
it("should return 200 ok if no X-API-KEY is present but route should not be secured and use internal token (ignore method PUT)", async function () {
await request(app)
.put("/ignored-get-put")
.set("Accept", "application/json")
.set("Authorization", `Bearer ${validInternalToken}`)
.then((response) => { assert.strictEqual(response.status, 200); return response; })
;
});
it("should return 200 ok if no X-API-KEY is present but route should not be secured (ignore method PUT)", async function () {
await request(app)
.put("/ignored-get-put")
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 200); return response; })
;
});
it("should return 401 if no X-API-KEY is present and method POST for route is secured", async function () {
await request(app)
.post("/ignored-get-put")
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 401); return response; })
;
});
it("should return 200 ok if no X-API-KEY is present but route should not be secured and use internal token", async function () {
await request(app)
.get("/api-docs/pets")
.set("Accept", "application/json")
.set("Authorization", `Bearer ${validInternalToken}`)
.then((response) => { assert.strictEqual(response.status, 200); return response; })
;
});
it("should return 401 unauthorized if no X-API-KEY header is present", async function () {
await request(app)
.get("/hello-world")
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 401); return response; })
;
});
it("should return 401 unauthorized if X-API-KEY is not valid", async function () {
await request(app)
.get("/hello-world")
.set("X-API-KEY", chance.hash())
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 401); return response; })
;
});
it("should authenticate the user if HEADER X-API-KEY is valid", async function () {
await request(app)
.get("/hello-world")
.set("X-API-KEY", validKey)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 200); return response; })
;
});
it("should authenticate the user if QS X-API-KEY is valid despite it is an ignored route", async function () {
await request(app)
.get(`/ignored-get-put?apiKey=${validKey}`)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 200); return response; })
;
});
it("should authenticate the user if QS X-API-KEY is valid", async function () {
await request(app)
.get(`/hello-world?apiKey=${validKey}`)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 200); return response; })
;
});
it("should authenticate the user if X-API-KEY is the testKey", async function () {
await request(app)
.get("/hello-world")
.set("X-API-KEY", testKey)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 200); return response; })
;
});
it("should add the testUser into the request X-API-KEY is the testKey", async function () {
await request(app)
.get("/hello-world")
.set("X-API-KEY", testKey)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 200); return response; })
.then((response) => { assert.deepStrictEqual(response.body, testUser); return response; })
;
});
it("should read the user from a custom key on request", async function () {
await request(app)
.get("/hello-world")
.set("X-API-KEY", testKey)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 200); return response; })
.then((response) => { assert.deepStrictEqual(response.body, testUser); return response; })
;
});
it("should require api key header for token secured route", async function () {
await request(app)
.get("/secured")
.set("Authorization", `Bearer ${validToken}`)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 401); return response; })
;
});
it("should require token in token secured route", async function () {
await request(app)
.get("/secured")
.set("X-API-KEY", validKey)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 401); return response; })
;
});
it("should require api key header on ignoredRoutes for token secured route", async function () {
await request(app)
.get("/ignoredsecure")
.set("Authorization", `Bearer ${validToken}`)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 401); return response; })
;
});
describe("#ignored-and-secure", () => {
it("should return 200 if xapikey and jwttoken are sent for an optional token secured route", async function () {
await request(app)
.get("/ignored-and-secure")
.set("X-API-KEY", validKey)
.set("Authorization", `Bearer ${validToken}`)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 200); return response; })
;
});
it("should return 401 if xapikey is invalid for an optional token secured route", async function () {
await request(app)
.get("/ignored-and-secure")
.set("X-API-KEY", "invalid key")
.set("Authorization", `Bearer ${validToken}`)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 401); return response; })
;
});
it("should return 401 if jwttoken is invalid for an optional token secured route", async function () {
await request(app)
.get("/ignored-and-secure")
.set("X-API-KEY", validKey)
.set("Authorization", `Bearer invalidtoken`)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 401); return response; })
;
});
it("should return 401 if jwttoken is ommited for an optional token secured route", async function () {
await request(app)
.get("/ignored-and-secure")
.set("X-API-KEY", validKey)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 401); return response; })
;
});
it("should return 401 if xapikey is ommited for an optional token secured route", async function () {
await request(app)
.get("/ignored-and-secure")
.set("Authorization", `Bearer ${validToken}`)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 401); return response; })
;
});
it("should return 200 if no auth is attempted, the route is ignored and an optional token secured setup is used", async function () {
await request(app)
.get("/ignored-and-secure")
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 200); return response; })
;
});
})
describe("#validate-jwt-if-given", () => {
it("should return 200 if a valid jwttoken is sent", async function () {
await request(app)
.get("/validate-jwt-if-given")
.set("X-API-KEY", validKey)
.set("Authorization", `Bearer ${validToken}`)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 200); return response; })
.then(function (response) {
});
});
it("should authenticate with token and set req.user to the token payload if a valid jwttoken is sent", async function () {
await request(app)
.get("/validate-jwt-if-given")
.set("X-API-KEY", validKey)
.set("Authorization", `Bearer ${validToken}`)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 200); return response; })
.then(function (response) {
let user = JSON.parse(response.text).user;
assert.deepStrictEqual(user, Object.assign({}, testFullUser, {_id: testFullUser._id.toString()}));
});
});
it("should return 200 if not jwttoken is sent", async function () {
await request(app)
.get("/validate-jwt-if-given")
.set("X-API-KEY", validKey)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 200); return response; })
;
});
it("should return 401 if an invalid jwttoken is sent", async function () {
await request(app)
.get("/validate-jwt-if-given")
.set("X-API-KEY", validKey)
.set("Authorization", `Bearer invalid-token`)
.then((response) => { assert.strictEqual(response.status, 401); return response; })
;
});
})
it("should authenticate the user with api key and token", async function () {
await request(app)
.get("/secured")
.set("X-API-KEY", validKey)
.set("Authorization", `Bearer ${validToken}`)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 200); return response; })
;
});
it("should authenticate the user with api key and token when 'Bearer' isn't specified", async function () {
await request(app)
.get("/secured")
.set("X-API-KEY", validKey)
.set("Authorization", `${validToken}`)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 200); return response; })
;
});
it("should authenticate the user with api key and a test token", async function () {
await request(app)
.get("/secured")
.set("X-API-KEY", validKey)
.set("Authorization", `Bearer ${testToken}`)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 200); return response; })
;
});
it("should authenticate with token and set req.user to the token payload", async function () {
await request(app)
.get("/secured")
.set("X-API-KEY", validKey)
.set("Authorization", `Bearer ${validToken}`)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 200); return response; })
.then(function (response) {
let user = JSON.parse(response.text).user;
assert.deepStrictEqual(user, Object.assign({}, testFullUser, {_id: testFullUser._id.toString()}));
});
});
it("should not authenticate when the token issuer is not specified", () => {
const tokenSigningOptions = {...userTokenSigningOptions};
delete tokenSigningOptions.issuer;
const tokenWithNoIssuer = jwt.sign({user: testFullUser}, privateKey, tokenSigningOptions);
return request(app)
.get("/secured")
.set("X-API-KEY", validKey)
.set("Authorization", `Bearer ${tokenWithNoIssuer}`)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 401); return response; });
});
it("should not authenticate when the token is malformed", () => {
const malformedToken = chance.hash();
return request(app)
.get("/secured")
.set("X-API-KEY", validKey)
.set("Authorization", `Bearer ${malformedToken}`)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 401); return response; });
});
context("internal auth tokens", () => {
it("should authenticate with an api key and internal token", () => {
return request(app)
.get("/secured")
.set("X-API-KEY", validKey)
.set("Authorization", `Bearer ${validInternalToken}`)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 200); return response; });
});
it("should authenticate with an api key and internal token signed with the secondary signing secret", () => {
const anotherValidInternalToken = jwt.sign({}, internalAuthTokenSigningSecrets.secondary, internalTokenSigningOptions);
return request(app)
.get("/secured")
.set("X-API-KEY", validKey)
.set("Authorization", `Bearer ${anotherValidInternalToken}`)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 200); return response; });
});
it("should authenticate with an internal token, fetch the user from the database, " +
"and assign properties of the user to req.user (excluding their hashed password)", () => {
return request(app)
.get("/secured")
.set("X-API-KEY", validKey)
.set("Authorization", `Bearer ${validInternalToken}`)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 200); return response; })
.then(({body}) => {
const expectedUserProperties = Object.keys(testFullUser).filter((prop) => prop !== "password");
expectedUserProperties.forEach((prop) => {
assert.deepStrictEqual(body[prop], Object.assign({}, testFullUser, {_id: testFullUser._id.toString()})[prop]);
});
});
});
it("should authenticate with an internal token and assign properties of the token payload to req.user ", () => {
return request(app)
.get("/secured")
.set("X-API-KEY", validKey)
.set("Authorization", `Bearer ${validInternalToken}`)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 200); return response; })
.then(({body}) => {
const tokenPayload = jwt.decode(validInternalToken),
expectedTokenProperties = Object.keys(tokenPayload);
expectedTokenProperties.forEach((prop) => {
assert.deepStrictEqual(body[prop], tokenPayload[prop]);
});
});
});
it("should require that the user exists in the database", () => {
return request(app)
.get("/secured")
.set("X-API-KEY", validKeyWithNoUser)
.set("Authorization", `Bearer ${validInternalToken}`)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 401); return response; });
});
describe("verify internal token only (issuer)", () => {
it("should authenticate with an internal token", () => {
return request(app)
.get("/route/for/internal/use/only")
.set("Authorization", `Bearer ${validInternalToken}`)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 200); return response; });
});
it("should authenticate with an internal token when 'Bearer' isn't specified", () => {
return request(app)
.get("/route/for/internal/use/only")
.set("Authorization", `${validInternalToken}`)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 200); return response; });
});
it("should return unauthorized if internal token is not given", () => {
return request(app)
.get("/route/for/internal/use/only")
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 401); return response; });
});
it("should return unauthorized if internal token is not valid", () => {
return request(app)
.get("/route/for/internal/use/only")
.set("Authorization", "Bearer not_valid_token")
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 401); return response; });
});
});
it("should fail because there's no administrator user enabled to impersonate", () => {
return request(app)
.get("/secured")
.set("X-API-KEY", validKeyWithDeletedUser)
.set("Authorization", `Bearer ${validInternalToken}`)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 401); return response; });
});
describe("with a fallback user to impersonate", () => {
const fallbackAdministrator = {
email: "laststanding@administrator.com",
accountId: apiKeys[2].accountId,
deleted: false,
roles: {administrator: 1},
locked: {status: false},
};
beforeEach(async () => {
const db = await simpleDao.connect();
await db.collection("users")
.insertMany([fallbackAdministrator]);
});
it("should use another administrator user to impersonate", () => {
return request(app)
.get("/secured")
.set("X-API-KEY", validKeyWithDeletedUser)
.set("Authorization", `Bearer ${validInternalToken}`)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 200); return response; })
.then(({body}) => {
assert.strictEqual(body.email, fallbackAdministrator.email);
});
});
});
});
describe("tokenSecuredForBackoffice middleware", function () {
it("should not check the token if querystring does not reference channel but fill user if a validToken is provided", async function () {
await request(app)
.get("/backoffice")
.set("X-API-KEY", validKey)
.set("Authorization", `Bearer ${validToken}`)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 200); return response; })
.then(function (response) {
const body = JSON.parse(response.text);
assert.deepStrictEqual(body.user, Object.assign({}, testFullUser, {_id: testFullUser._id.toString()}));
});
});
it("should not check the token if querystring does not reference channel and no valid token is provided", async function () {
await request(app)
.get("/backoffice")
.set("X-API-KEY", validKey)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 200); return response; })
.then(function (response) {
let message = JSON.parse(response.text).message;
assert.strictEqual(message, "no token");
});
});
it("should not authorize if querystring requests channel=backoffice and token is not for the internal app", async function () {
await request(app)
.get("/backoffice?channel=backoffice")
.set("X-API-KEY", validKey)
.set("Authorization", `Bearer ${validCustomerToken}`)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 401); return response; })
;
});
it("should not authorize if querystring requests channel=agency-backoffice and token is not for the internal app", async function () {
await request(app)
.get("/backoffice?channel=agency-backoffice")
.set("X-API-KEY", validKey)
.set("Authorization", `Bearer ${validCustomerToken}`)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 401); return response; })
;
});
it("should not authorize if querystring requests channels contain backoffice and token is not for the internal app", async function () {
await request(app)
.get("/backoffice?channels=websales,backoffice")
.set("X-API-KEY", validKey)
.set("Authorization", `Bearer ${validCustomerToken}`)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 401); return response; })
;
});
it("should not authorize if querystring requests channels contain agency-backoffice and token is not for the internal app", async function () {
await request(app)
.get("/backoffice?channels=websales,agency-backoffice")
.set("X-API-KEY", validKey)
.set("Authorization", `Bearer ${validCustomerToken}`)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 401); return response; })
;
});
it("should not check the token if querystring references another channel", async function () {
await request(app)
.get("/backoffice?channel=websales")
.set("X-API-KEY", validKey)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 200); return response; })
.then(function (response) {
let message = JSON.parse(response.text).message;
assert.strictEqual(message, "no token");
});
});
it("should authorize if querystring requests channel=backoffice and token is for the internal app", async function () {
await request(app)
.get("/backoffice?channel=backoffice")
.set("X-API-KEY", validKey)
.set("Authorization", `Bearer ${validBackofficeToken}`)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 200); return response; })
.then(function (response) {
let user = JSON.parse(response.text).user;
assert.deepStrictEqual(user, Object.assign({}, testFullUser, {_id: testFullUser._id.toString()}));
});
});
it("should authorize if querystring requests channel=agency-backoffice and token is for the internal app", async function () {
await request(app)
.get("/backoffice?channel=agency-backoffice")
.set("X-API-KEY", validKey)
.set("Authorization", `Bearer ${validBackofficeToken}`)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 200); return response; })
.then(function (response) {
let user = JSON.parse(response.text).user;
assert.deepStrictEqual(user, Object.assign({}, testFullUser, {_id: testFullUser._id.toString()}));
});
});
it("should authorize if querystring requests channels cointain agency-backoffice and token is for the internal app", async function () {
await request(app)
.get("/backoffice?channels=any,agency-backoffice")
.set("X-API-KEY", validKey)
.set("Authorization", `Bearer ${validBackofficeToken}`)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 200); return response; })
.then(function (response) {
let user = JSON.parse(response.text).user;
assert.deepStrictEqual(user, Object.assign({}, testFullUser, {_id: testFullUser._id.toString()}));
});
});
it("should authorize if querystring requests channels cointain backoffice and token is for the internal app", async function () {
await request(app)
.get("/backoffice?channels=any,backoffice")
.set("X-API-KEY", validKey)
.set("Authorization", `Bearer ${validBackofficeToken}`)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 200); return response; })
.then(function (response) {
let user = JSON.parse(response.text).user;
assert.deepStrictEqual(user, Object.assign({}, testFullUser, {_id: testFullUser._id.toString()}));
});
});
it("should not check the token if body does not reference channel", async function () {
await request(app)
.post("/backoffice")
.send({})
.set("X-API-KEY", validKey)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 200); return response; })
.then(function (response) {
let message = JSON.parse(response.text).message;
assert.strictEqual(message, "no token");
});
});
it("should not authorize if body requests channel=backoffice and token is not for the internal app", async function () {
await request(app)
.post("/backoffice")
.send({channel: "backoffice"})
.set("X-API-KEY", validKey)
.set("Authorization", `Bearer ${validCustomerToken}`)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 401); return response; })
;
});
it("should not authorize if body requests channel=agency-backoffice and token is not for the internal app", async function () {
await request(app)
.post("/backoffice")
.send({channel: "agency-Backoffice"})
.set("X-API-KEY", validKey)
.set("Authorization", `Bearer ${validCustomerToken}`)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 401); return response; })
;
});
it("should not authorize if body requests channels contain backoffice and token is not for the internal app", async function () {
await request(app)
.post("/backoffice")
.send({channels: ["any", "backOffice"]})
.set("X-API-KEY", validKey)
.set("Authorization", `Bearer ${validCustomerToken}`)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 401); return response; })
;
});
it("should not check the token if body references another channel", async function () {
await request(app)
.post("/backoffice")
.send({channel: "websales"})
.set("X-API-KEY", validKey)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 200); return response; })
.then(function (response) {
let message = JSON.parse(response.text).message;
assert.strictEqual(message, "no token");
});
});
it("should authorize if body requests channel=backoffice and token is for the internal app", async function () {
await request(app)
.post("/backoffice")
.send({channel: "backoffice"})
.set("X-API-KEY", validKey)
.set("Authorization", `Bearer ${validBackofficeToken}`)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 200); return response; })
.then(function (response) {
let user = JSON.parse(response.text).user;
assert.deepStrictEqual(user, Object.assign({}, testFullUser, {_id: testFullUser._id.toString()}));
});
});
it("should authorize if body requests channel=agency-backoffice and token is for the internal app", async function () {
await request(app)
.post("/backoffice")
.send({channel: "agency-backoffice"})
.set("X-API-KEY", validKey)
.set("Authorization", `Bearer ${validBackofficeToken}`)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 200); return response; })
.then(function (response) {
let user = JSON.parse(response.text).user;
assert.deepStrictEqual(user, Object.assign({}, testFullUser, {_id: testFullUser._id.toString()}));
});
});
it("should authorize if body requests channel=agency-backoffice and token is from application that support the channel", async function () {
await request(app)
.post("/backoffice")
.send({channel: "agency-backoffice"})
.set("X-API-KEY", application.key)
.set("Authorization", `Bearer ${validApplicationToken}`)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 200); return response; })
.then(function (response) {
let app = JSON.parse(response.text);
assert.strictEqual(app._id, application._id.toString());
});
});
it("should authorize if body requests channel=backoffice and token is from application that support the channel", async function () {
await request(app)
.post("/backoffice")
.send({channel: "backoffice"})
.set("X-API-KEY", application.key)
.set("Authorization", `Bearer ${validApplicationToken}`)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 200); return response; })
.then(function (response) {
let app = JSON.parse(response.text);
assert.strictEqual(app._id, application._id.toString());
});
});
it("should not authorize if body requests channels backoffice and the app does not support the channel", async function () {
await request(app)
.post("/backoffice")
.send({channels: ["backoffice"]})
.set("X-API-KEY", keyWithoutChannels)
.set("Authorization", `Bearer ${validApplicationTokenWithoutChannels}`)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 401); return response; })
;
});
it("should authorize if body requests channels contain backoffice and token is for the internal app", async function () {
await request(app)
.post("/backoffice")
.send({channels: ["any", "backoffice"]})
.set("X-API-KEY", validKey)
.set("Authorization", `Bearer ${validBackofficeToken}`)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 200); return response; })
.then(function (response) {
let user = JSON.parse(response.text).user;
assert.deepStrictEqual(user, Object.assign({}, testFullUser, {_id: testFullUser._id.toString()}));
});
});
it("should authorize when 'Bearer' isn't specified in token", async function () {
await request(app)
.post("/backoffice")
.send({channel: "backoffice"})
.set("X-API-KEY", validKey)
.set("Authorization", `${validBackofficeToken}`)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 200); return response; })
.then(function (response) {
let user = JSON.parse(response.text).user;
assert.deepStrictEqual(user, Object.assign({}, testFullUser, {_id: testFullUser._id.toString()}));
});
});
it("should authorize the configured test token", async function () {
await request(app)
.post("/backoffice")
.send({channel: "websales"})
.set("X-API-KEY", validKey)
.set("Authorization", `Bearer ${testToken}`)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 200); return response; })
.then(function (response) {
let message = JSON.parse(response.text).message;
assert.strictEqual(message, "no token");
});
});
describe("testing options audiences array", function () {
beforeEach(function () {
options.audiences = ["betterez-app", "btrz-mobile-scanner"];
});
it("should not authorize if querystring requests channel=backoffice and token is not for the internal app", async function () {
await request(app)
.get("/backoffice?channel=backoffice")
.set("X-API-KEY", validKey)
.set("Authorization", `Bearer ${validCustomerToken}`)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 401); return response; })
;
});
it("should not authorize if querystring requests channel=backoffice and token is for other-app", async function () {
await request(app)
.get("/backoffice?channel=backoffice")
.set("X-API-KEY", validKey)
.set("Authorization", `Bearer ${validBackofficeTokenForOtherApp}`)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 401); return response; })
;
});
it("should authorize if querystring requests channel=backoffice and token is for btrz-mobile-scanner", async function () {
let validBackofficeTokenForMobileApp = jwt.sign({user: testFullUser, aud: "btrz-mobile-scanner"},
privateKey, userTokenSigningOptions);
await request(app)
.get("/backoffice?channel=backoffice")
.set("X-API-KEY", validKey)
.set("Authorization", `Bearer ${validBackofficeTokenForMobileApp}`)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 200); return response; })
.then(function (response) {
let user = JSON.parse(response.text).user;
assert.deepStrictEqual(user, Object.assign({}, testFullUser, {_id: testFullUser._id.toString()}));
});
});
it("should authorize if querystring requests channel=backoffice and token is for betterez-app", async function () {
let validBackofficeTokenForBetterezApp = jwt.sign({user: testFullUser, aud: "betterez-app"}, privateKey, userTokenSigningOptions);
await request(app)
.get("/backoffice?channel=backoffice")
.set("X-API-KEY", validKey)
.set("Authorization", `Bearer ${validBackofficeTokenForBetterezApp}`)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 200); return response; })
.then(function (response) {
let user = JSON.parse(response.text).user;
assert.deepStrictEqual(user, Object.assign({}, testFullUser, {_id: testFullUser._id.toString()}));
});
});
});
});
describe("customerTokenSecured middleware", function () {
it("should fail to authenticate customer with user token", async function () {
await request(app)
.get("/customer")
.set("X-API-KEY", validKey)
.set("Authorization", `Bearer ${validToken}`)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 401); return response; })
;
});
it("should require API key header for customer token secured route", async function () {
await request(app)
.get("/customer")
.set("Authorization", `Bearer ${validCustomerToken}`)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 401); return response; })
;
});
it("should authenticate customer with token and set customer on request", async function () {
await request(app)
.get("/customer")
.set("X-API-KEY", validKey)
.set("Authorization", `Bearer ${validCustomerToken}`)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 200); return response; })
.then(function (response) {
let customer = JSON.parse(response.text).customer;
assert.strictEqual(customer.customerNumber, "111-222-333");
});
});
it("should authenticate customer with token when 'Bearer' isn't specified and set customer on request", async function () {
await request(app)
.get("/customer")
.set("X-API-KEY", validKey)
.set("Authorization", `${validCustomerToken}`)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 200); return response; })
.then(function (response) {
let customer = JSON.parse(response.text).customer;
assert.strictEqual(customer.customerNumber, "111-222-333");
});
});
});
describe("tokenSecuredForAudiences", () => {
it("should authorize for the internal app", async function () {
const response = await request(app)
.get("/allowOnlyCustomerOrBackoffice")
.set("X-API-KEY", validKey)
.set("Authorization", `Bearer ${validBackofficeToken}`)
.set("Accept", "application/json");
assert.strictEqual(response.status, 200);
assert.deepStrictEqual(JSON.parse(response.text).user, Object.assign({}, testFullUser, {_id: testFullUser._id.toString()}));
});
it("should authorize for a customer", async function () {
const response = await request(app)
.get("/allowOnlyCustomerOrBackoffice")
.set("X-API-KEY", validKey)
.set("Authorization", `Bearer ${validCustomerToken}`)
.set("Accept", "application/json");
assert.strictEqual(response.status, 200);
assert.strictEqual(JSON.parse(response.text).customer.customerNumber, "111-222-333");
});
it("should not authorize for other app", async function () {
const response = await request(app)
.get("/allowOnlyCustomerOrBackoffice")
.set("X-API-KEY", validKey)
.set("Authorization", `Bearer ${validBackofficeTokenForOtherApp}`)
.set("Accept", "application/json");
assert.strictEqual(response.status, 401);
assert.strictEqual(response.text, "Unauthorized");
});
it("should authorize for the internal app", async function () {
const response = await request(app)
.get("/allowOnlyCustomerOrBackoffice")
.set("X-API-KEY", validKey)
.set("Authorization", `${validBackofficeToken}`)
.set("Accept", "application/json");
assert.strictEqual(response.status, 200);
assert.deepStrictEqual(JSON.parse(response.text).user, Object.assign({}, testFullUser, {_id: testFullUser._id.toString()}));
});
it("should authorize for internal app that has channel backoffice as accepted", async function () {
await request(app)
.get("/allowOnlyCustomerOrBackoffice")
.set("X-API-KEY", application.key)
.set("Authorization", `Bearer ${validApplicationToken}`)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 200); return response; })
.then(function (response) {
let app = JSON.parse(response.text);
assert.strictEqual(app._id, application._id.toString());
});
})
});
describe("internalAuthTokenProvider", () => {
it("should generate an auth token that is accepted by token-secured endpoints", () => {
const internalToken = internalAuthTokenProvider.getToken();
return request(app)
.get("/secured")
.set("X-API-KEY", validKey)
.set("Authorization", `Bearer ${internalToken}`)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 200); return response; });
});
it("should cache the generated token for a period of time", () => {
const currentTimestamp = new Date().getTime(),
futureTimestamp = currentTimestamp + 60*12*1000 + 1000, // 12 hours and one second
internalToken1 = internalAuthTokenProvider.getToken();
assert.ok(internalToken1);
// Confirm that the first token is cached
const internalToken2 = internalAuthTokenProvider.getToken();
assert.strictEqual(internalToken2, internalToken1);
// Confirm that a new token will be generated after some time has elapsed
clock = sinon.useFakeTimers({now: currentTimestamp, shouldAdvanceTime: false});
clock.setSystemTime(futureTimestamp);
const internalToken3 = internalAuthTokenProvider.getToken();
assert.ok(internalToken3);
assert.notStrictEqual(internalToken3, internalToken2);
});
});
describe("req.tokens & req.user", () => {
it("should add tokens to request on non-secure endpoint", () => {
const internalToken = internalAuthTokenProvider.getToken();
return request(app)
.get("/gimmeTokens")
.set("X-API-KEY", validKey)
.set("Authorization", `Bearer ${internalToken}`)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 200); return response; })
.then(({body}) => {
assert.deepStrictEqual(body.token, validKey);
assert.deepStrictEqual(body.jwtToken, internalToken);
});
});
it("should add tokens to request on secure endpoint", () => {
const internalToken = internalAuthTokenProvider.getToken();
return request(app)
.get("/gimmeTokensSecured")
.set("X-API-KEY", validKey)
.set("Authorization", `Bearer ${internalToken}`)
.set("Accept", "application/json")
.then((response) => { assert.strictEqual(response.status, 200); return response; })
.then(({body}) => {
assert.deepStrictEqual(body.token, validKey);
assert.deepStrictEqual(body.jwtToken, internalToken);
});
});
it("should add only x-api-key token if Authorization header is not present", () => {
return request(app)
.get("/gimmeTokens")
.set("X-API-KEY", validKey)