authelia/server/test/IdentityCheckMiddleware.test.ts
Clement Michaud 56fdc40290 Every public endpoints return 200 with harmonized error messages or 401
Now, /verify can return 401 or 403 depending on the user authentication.
Every public API endpoints and pages return 200 with error message in
JSON body or 401 if the user is not authorized.

This policy makes it complicated for an attacker to know what is the source of
the failure and hide server-side bugs (not returning 500), bugs being potential
threats.
2017-10-14 11:57:38 +02:00

174 lines
6.8 KiB
TypeScript

import sinon = require("sinon");
import IdentityValidator = require("../src/lib/IdentityCheckMiddleware");
import AuthenticationSession = require("../src/lib/AuthenticationSession");
import { UserDataStore } from "../src/lib/storage/UserDataStore";
import exceptions = require("../src/lib/Exceptions");
import Assert = require("assert");
import Promise = require("bluebird");
import express = require("express");
import BluebirdPromise = require("bluebird");
import ExpressMock = require("./mocks/express");
import NotifierMock = require("./mocks/Notifier");
import IdentityValidatorMock = require("./mocks/IdentityValidator");
import ServerVariablesMock = require("./mocks/ServerVariablesMock");
describe("test identity check process", function () {
let mocks: ServerVariablesMock.ServerVariablesMock;
let req: ExpressMock.RequestMock;
let res: ExpressMock.ResponseMock;
let notifier: NotifierMock.NotifierMock;
let app: express.Application;
let app_get: sinon.SinonStub;
let app_post: sinon.SinonStub;
let identityValidable: IdentityValidatorMock.IdentityValidableMock;
beforeEach(function () {
req = ExpressMock.RequestMock();
res = ExpressMock.ResponseMock();
identityValidable = IdentityValidatorMock.IdentityValidableMock();
notifier = NotifierMock.NotifierMock();
notifier.notify = sinon.stub().returns(Promise.resolve());
req.headers = {};
req.session = {};
req.session = {};
req.query = {};
req.app = {};
mocks = ServerVariablesMock.mock(req.app);
mocks.notifier = notifier;
mocks.userDataStore.produceIdentityValidationTokenStub.returns(Promise.resolve());
mocks.userDataStore.consumeIdentityValidationTokenStub.returns(Promise.resolve({ userId: "user" }));
app = express();
app_get = sinon.stub(app, "get");
app_post = sinon.stub(app, "post");
});
afterEach(function () {
app_get.restore();
app_post.restore();
});
describe("test start GET", test_start_get_handler);
describe("test finish GET", test_finish_get_handler);
function test_start_get_handler() {
it("should send 401 if pre validation initialization throws a first factor error", function () {
identityValidable.preValidationInit.returns(BluebirdPromise.reject(new exceptions.FirstFactorValidationError("Error during prevalidation")));
const callback = IdentityValidator.get_start_validation(identityValidable, "/endpoint");
return callback(req as any, res as any, undefined)
.then(function () { return BluebirdPromise.reject("Should fail"); })
.catch(function () {
Assert.equal(res.status.getCall(0).args[0], 401);
});
});
it("should send 401 if email is missing in provided identity", function () {
const identity = { userid: "abc" };
identityValidable.preValidationInit.returns(BluebirdPromise.resolve(identity));
const callback = IdentityValidator.get_start_validation(identityValidable, "/endpoint");
return callback(req as any, res as any, undefined)
.then(function () { return BluebirdPromise.reject("Should fail"); })
.catch(function () {
Assert.equal(res.status.getCall(0).args[0], 401);
});
});
it("should send 401 if userid is missing in provided identity", function () {
const endpoint = "/protected";
const identity = { email: "abc@example.com" };
identityValidable.preValidationInit.returns(BluebirdPromise.resolve(identity));
const callback = IdentityValidator.get_start_validation(identityValidable, "/endpoint");
return callback(req as any, res as any, undefined)
.then(function () { return BluebirdPromise.reject(new Error("It should fail")); })
.catch(function (err: Error) {
Assert.equal(res.status.getCall(0).args[0], 401);
return BluebirdPromise.resolve();
});
});
it("should issue a token, send an email and return 204", function () {
const endpoint = "/protected";
const identity = { userid: "user", email: "abc@example.com" };
req.get = sinon.stub().withArgs("Host").returns("localhost");
identityValidable.preValidationInit.returns(BluebirdPromise.resolve(identity));
const callback = IdentityValidator.get_start_validation(identityValidable, "/finish_endpoint");
return callback(req as any, res as any, undefined)
.then(function () {
Assert(notifier.notify.calledOnce);
Assert(mocks.userDataStore.produceIdentityValidationTokenStub.calledOnce);
Assert.equal(mocks.userDataStore.produceIdentityValidationTokenStub.getCall(0).args[0], "user");
Assert.equal(mocks.userDataStore.produceIdentityValidationTokenStub.getCall(0).args[3], 240000);
});
});
}
function test_finish_get_handler() {
it("should send 401 if no identity_token is provided", function () {
const callback = IdentityValidator.get_finish_validation(identityValidable);
return callback(req as any, res as any, undefined)
.then(function () { return BluebirdPromise.reject("Should fail"); })
.catch(function () {
Assert.equal(res.status.getCall(0).args[0], 401);
});
});
it("should call postValidation if identity_token is provided and still valid", function () {
req.query.identity_token = "token";
const callback = IdentityValidator.get_finish_validation(identityValidable);
return callback(req as any, res as any, undefined);
});
it("should return 401 if identity_token is provided but invalid", function () {
req.query.identity_token = "token";
mocks.userDataStore.consumeIdentityValidationTokenStub.returns(BluebirdPromise.reject(new Error("Invalid token")));
const callback = IdentityValidator.get_finish_validation(identityValidable);
return callback(req as any, res as any, undefined)
.then(function () { return BluebirdPromise.reject("Should fail"); })
.catch(function () {
Assert.equal(res.status.getCall(0).args[0], 401);
});
});
it("should set the identity_check session object even if session does not exist yet", function () {
req.query.identity_token = "token";
req.session = {};
let authSession: AuthenticationSession.AuthenticationSession;
const callback = IdentityValidator.get_finish_validation(identityValidable);
return AuthenticationSession.get(req as any)
.then(function (_authSession: AuthenticationSession.AuthenticationSession) {
authSession = _authSession;
return callback(req as any, res as any, undefined);
})
.then(function () { return BluebirdPromise.reject("Should fail"); })
.catch(function () {
Assert.equal(authSession.identity_check.userid, "user");
return BluebirdPromise.resolve();
});
});
}
});