Fix endpoints redirection on errors
From this commit on, api endpoints reply with a 401 error code and non api endpoints redirect to /error/40X. This commit also fixes missing restrictions on /loggedin (the "already logged in page). This was not a security issue, though. The change also makes error pages automatically redirect the user after few seconds based on the referrer or the default_redirection_url if provided in the configuration. Warning: The old /verify endpoint of the REST API has moved to /api/verify. You will need to update your nginx configuration to take this change into account.pull/187/head
parent
837884ef0d
commit
6b78240d39
|
@ -86,7 +86,7 @@ http {
|
|||
proxy_set_header Host $http_host;
|
||||
proxy_set_header Content-Length "";
|
||||
|
||||
proxy_pass http://authelia/verify;
|
||||
proxy_pass http://authelia/api/verify;
|
||||
}
|
||||
|
||||
location / {
|
||||
|
@ -143,7 +143,7 @@ http {
|
|||
proxy_set_header Host $http_host;
|
||||
proxy_set_header Content-Length "";
|
||||
|
||||
proxy_pass http://authelia/verify;
|
||||
proxy_pass http://authelia/api/verify;
|
||||
}
|
||||
|
||||
location / {
|
||||
|
@ -183,7 +183,7 @@ http {
|
|||
proxy_set_header Host $http_host;
|
||||
proxy_set_header Content-Length "";
|
||||
|
||||
proxy_pass http://authelia/verify;
|
||||
proxy_pass http://authelia/api/verify;
|
||||
}
|
||||
|
||||
location / {
|
||||
|
@ -223,7 +223,7 @@ http {
|
|||
proxy_set_header Host $http_host;
|
||||
proxy_set_header Content-Length "";
|
||||
|
||||
proxy_pass http://authelia/verify;
|
||||
proxy_pass http://authelia/api/verify;
|
||||
}
|
||||
|
||||
location / {
|
||||
|
@ -263,7 +263,7 @@ http {
|
|||
proxy_set_header Host $http_host;
|
||||
proxy_set_header Content-Length "";
|
||||
|
||||
proxy_pass http://authelia/verify;
|
||||
proxy_pass http://authelia/api/verify;
|
||||
}
|
||||
|
||||
location / {
|
||||
|
|
|
@ -5,10 +5,17 @@ import { IRequestLogger } from "./logging/IRequestLogger";
|
|||
function replyWithError(req: express.Request, res: express.Response,
|
||||
code: number, logger: IRequestLogger, body?: Object): (err: Error) => void {
|
||||
return function (err: Error): void {
|
||||
logger.error(req, "Reply with error %d: %s", code, err.message);
|
||||
logger.debug(req, "%s", err.stack);
|
||||
res.status(code);
|
||||
res.send(body);
|
||||
if (req.originalUrl.startsWith("/api/") || code == 200) {
|
||||
logger.error(req, "Reply with error %d: %s", code, err.message);
|
||||
logger.debug(req, "%s", err.stack);
|
||||
res.status(code);
|
||||
res.send(body);
|
||||
}
|
||||
else {
|
||||
logger.error(req, "Redirect to error %d: %s", code, err.message);
|
||||
logger.debug(req, "%s", err.stack);
|
||||
res.redirect("/error/" + code);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -7,69 +7,84 @@ import Exceptions = require("./Exceptions");
|
|||
import fs = require("fs");
|
||||
import ejs = require("ejs");
|
||||
import { IUserDataStore } from "./storage/IUserDataStore";
|
||||
import express = require("express");
|
||||
import Express = require("express");
|
||||
import ErrorReplies = require("./ErrorReplies");
|
||||
import { AuthenticationSessionHandler } from "./AuthenticationSessionHandler";
|
||||
import { AuthenticationSession } from "../../types/AuthenticationSession";
|
||||
import { ServerVariables } from "./ServerVariables";
|
||||
|
||||
import Identity = require("../../types/Identity");
|
||||
import { IdentityValidationDocument } from "./storage/IdentityValidationDocument";
|
||||
import { IdentityValidationDocument }
|
||||
from "./storage/IdentityValidationDocument";
|
||||
|
||||
const filePath = __dirname + "/../resources/email-template.ejs";
|
||||
const email_template = fs.readFileSync(filePath, "utf8");
|
||||
|
||||
// IdentityValidator allows user to go through a identity validation process in two steps:
|
||||
// IdentityValidator allows user to go through a identity validation process
|
||||
// in two steps:
|
||||
// - Request an operation to be performed (password reset, registration).
|
||||
// - Confirm operation with email.
|
||||
|
||||
export interface IdentityValidable {
|
||||
challenge(): string;
|
||||
preValidationInit(req: express.Request): BluebirdPromise<Identity.Identity>;
|
||||
postValidationInit(req: express.Request): BluebirdPromise<void>;
|
||||
preValidationInit(req: Express.Request): BluebirdPromise<Identity.Identity>;
|
||||
postValidationInit(req: Express.Request): BluebirdPromise<void>;
|
||||
|
||||
// Serves a page after identity check request
|
||||
preValidationResponse(req: express.Request, res: express.Response): void;
|
||||
preValidationResponse(req: Express.Request, res: Express.Response): void;
|
||||
// Serves the page if identity validated
|
||||
postValidationResponse(req: express.Request, res: express.Response): void;
|
||||
postValidationResponse(req: Express.Request, res: Express.Response): void;
|
||||
mailSubject(): string;
|
||||
}
|
||||
|
||||
function createAndSaveToken(userid: string, challenge: string, userDataStore: IUserDataStore)
|
||||
function createAndSaveToken(userid: string, challenge: string,
|
||||
userDataStore: IUserDataStore)
|
||||
: BluebirdPromise<string> {
|
||||
const five_minutes = 4 * 60 * 1000;
|
||||
const token = randomstring.generate({ length: 64 });
|
||||
const that = this;
|
||||
|
||||
return userDataStore.produceIdentityValidationToken(userid, token, challenge, five_minutes)
|
||||
return userDataStore.produceIdentityValidationToken(userid, token, challenge,
|
||||
five_minutes)
|
||||
.then(function () {
|
||||
return BluebirdPromise.resolve(token);
|
||||
});
|
||||
}
|
||||
|
||||
function consumeToken(token: string, challenge: string, userDataStore: IUserDataStore)
|
||||
function consumeToken(token: string, challenge: string,
|
||||
userDataStore: IUserDataStore)
|
||||
: BluebirdPromise<IdentityValidationDocument> {
|
||||
return userDataStore.consumeIdentityValidationToken(token, challenge);
|
||||
}
|
||||
|
||||
export function register(app: express.Application, pre_validation_endpoint: string,
|
||||
post_validation_endpoint: string, handler: IdentityValidable, vars: ServerVariables) {
|
||||
app.get(pre_validation_endpoint, get_start_validation(handler, post_validation_endpoint, vars));
|
||||
app.get(post_validation_endpoint, get_finish_validation(handler, vars));
|
||||
export function register(app: Express.Application,
|
||||
pre_validation_endpoint: string,
|
||||
post_validation_endpoint: string,
|
||||
handler: IdentityValidable,
|
||||
vars: ServerVariables) {
|
||||
|
||||
app.get(pre_validation_endpoint,
|
||||
get_start_validation(handler, post_validation_endpoint, vars));
|
||||
app.get(post_validation_endpoint,
|
||||
get_finish_validation(handler, vars));
|
||||
}
|
||||
|
||||
function checkIdentityToken(req: express.Request, identityToken: string): BluebirdPromise<void> {
|
||||
function checkIdentityToken(req: Express.Request, identityToken: string)
|
||||
: BluebirdPromise<void> {
|
||||
if (!identityToken)
|
||||
return BluebirdPromise.reject(new Exceptions.AccessDeniedError("No identity token provided"));
|
||||
return BluebirdPromise.reject(
|
||||
new Exceptions.AccessDeniedError("No identity token provided"));
|
||||
return BluebirdPromise.resolve();
|
||||
}
|
||||
|
||||
export function get_finish_validation(handler: IdentityValidable,
|
||||
vars: ServerVariables)
|
||||
: express.RequestHandler {
|
||||
return function (req: express.Request, res: express.Response): BluebirdPromise<void> {
|
||||
: Express.RequestHandler {
|
||||
return function (req: Express.Request, res: Express.Response)
|
||||
: BluebirdPromise<void> {
|
||||
let authSession: AuthenticationSession;
|
||||
const identityToken = objectPath.get<express.Request, string>(req, "query.identity_token");
|
||||
const identityToken = objectPath.get<Express.Request, string>(
|
||||
req, "query.identity_token");
|
||||
vars.logger.debug(req, "Identity token provided is %s", identityToken);
|
||||
|
||||
return checkIdentityToken(req, identityToken)
|
||||
|
@ -78,7 +93,8 @@ export function get_finish_validation(handler: IdentityValidable,
|
|||
return handler.postValidationInit(req);
|
||||
})
|
||||
.then(function () {
|
||||
return consumeToken(identityToken, handler.challenge(), vars.userDataStore);
|
||||
return consumeToken(identityToken, handler.challenge(),
|
||||
vars.userDataStore);
|
||||
})
|
||||
.then(function (doc: IdentityValidationDocument) {
|
||||
authSession.identity_check = {
|
||||
|
@ -95,8 +111,9 @@ export function get_finish_validation(handler: IdentityValidable,
|
|||
export function get_start_validation(handler: IdentityValidable,
|
||||
postValidationEndpoint: string,
|
||||
vars: ServerVariables)
|
||||
: express.RequestHandler {
|
||||
return function (req: express.Request, res: express.Response): BluebirdPromise<void> {
|
||||
: Express.RequestHandler {
|
||||
return function (req: Express.Request, res: Express.Response)
|
||||
: BluebirdPromise<void> {
|
||||
let identity: Identity.Identity;
|
||||
|
||||
return handler.preValidationInit(req)
|
||||
|
@ -104,20 +121,24 @@ export function get_start_validation(handler: IdentityValidable,
|
|||
identity = id;
|
||||
const email = identity.email;
|
||||
const userid = identity.userid;
|
||||
vars.logger.info(req, "Start identity validation of user \"%s\"", userid);
|
||||
vars.logger.info(req, "Start identity validation of user \"%s\"",
|
||||
userid);
|
||||
|
||||
if (!(email && userid))
|
||||
return BluebirdPromise.reject(new Exceptions.IdentityError(
|
||||
"Missing user id or email address"));
|
||||
|
||||
return createAndSaveToken(userid, handler.challenge(), vars.userDataStore);
|
||||
return createAndSaveToken(userid, handler.challenge(),
|
||||
vars.userDataStore);
|
||||
})
|
||||
.then(function (token: string) {
|
||||
const host = req.get("Host");
|
||||
const link_url = util.format("https://%s%s?identity_token=%s", host,
|
||||
postValidationEndpoint, token);
|
||||
vars.logger.info(req, "Notification sent to user \"%s\"", identity.userid);
|
||||
return vars.notifier.notify(identity.email, handler.mailSubject(), link_url);
|
||||
vars.logger.info(req, "Notification sent to user \"%s\"",
|
||||
identity.userid);
|
||||
return vars.notifier.notify(identity.email, handler.mailSubject(),
|
||||
link_url);
|
||||
})
|
||||
.then(function () {
|
||||
handler.preValidationResponse(req, res);
|
||||
|
|
|
@ -1,8 +1,15 @@
|
|||
|
||||
import BluebirdPromise = require("bluebird");
|
||||
import express = require("express");
|
||||
import redirector from "../redirector";
|
||||
import { ServerVariables } from "../../../ServerVariables";
|
||||
|
||||
export default function (req: express.Request, res: express.Response): BluebirdPromise<void> {
|
||||
res.render("errors/401");
|
||||
export default function (vars: ServerVariables) {
|
||||
return function (req: express.Request, res: express.Response): BluebirdPromise<void> {
|
||||
const redirectionUrl = redirector(req, vars);
|
||||
res.render("errors/401", {
|
||||
redirection_url: redirectionUrl
|
||||
});
|
||||
return BluebirdPromise.resolve();
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,8 +1,15 @@
|
|||
|
||||
import BluebirdPromise = require("bluebird");
|
||||
import express = require("express");
|
||||
import redirector from "../redirector";
|
||||
import { ServerVariables } from "../../../ServerVariables";
|
||||
|
||||
export default function (req: express.Request, res: express.Response): BluebirdPromise<void> {
|
||||
res.render("errors/403");
|
||||
export default function (vars: ServerVariables) {
|
||||
return function (req: express.Request, res: express.Response): BluebirdPromise<void> {
|
||||
const redirectionUrl = redirector(req, vars);
|
||||
res.render("errors/403", {
|
||||
redirection_url: redirectionUrl
|
||||
});
|
||||
return BluebirdPromise.resolve();
|
||||
};
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
import Express = require("express");
|
||||
import { ServerVariables } from "../../ServerVariables";
|
||||
|
||||
export default function (req: Express.Request, vars: ServerVariables): string {
|
||||
let redirectionUrl: string;
|
||||
|
||||
if (req.headers && req.headers["referer"])
|
||||
redirectionUrl = "" + req.headers["referer"];
|
||||
else if (vars.config.default_redirection_url)
|
||||
redirectionUrl = vars.config.default_redirection_url;
|
||||
|
||||
return redirectionUrl;
|
||||
}
|
|
@ -101,17 +101,21 @@ function setupU2f(app: Express.Application, vars: ServerVariables) {
|
|||
}
|
||||
|
||||
function setupResetPassword(app: Express.Application, vars: ServerVariables) {
|
||||
IdentityCheckMiddleware.register(app, Endpoints.RESET_PASSWORD_IDENTITY_START_GET,
|
||||
IdentityCheckMiddleware.register(app,
|
||||
Endpoints.RESET_PASSWORD_IDENTITY_START_GET,
|
||||
Endpoints.RESET_PASSWORD_IDENTITY_FINISH_GET,
|
||||
new ResetPasswordIdentityHandler(vars.logger, vars.ldapEmailsRetriever), vars);
|
||||
new ResetPasswordIdentityHandler(vars.logger, vars.ldapEmailsRetriever),
|
||||
vars);
|
||||
|
||||
app.get(Endpoints.RESET_PASSWORD_REQUEST_GET, ResetPasswordRequestPost.default);
|
||||
app.post(Endpoints.RESET_PASSWORD_FORM_POST, ResetPasswordFormPost.default(vars));
|
||||
app.get(Endpoints.RESET_PASSWORD_REQUEST_GET,
|
||||
ResetPasswordRequestPost.default);
|
||||
app.post(Endpoints.RESET_PASSWORD_FORM_POST,
|
||||
ResetPasswordFormPost.default(vars));
|
||||
}
|
||||
|
||||
function setupErrors(app: Express.Application) {
|
||||
app.get(Endpoints.ERROR_401_GET, Error401Get.default);
|
||||
app.get(Endpoints.ERROR_403_GET, Error403Get.default);
|
||||
function setupErrors(app: Express.Application, vars: ServerVariables) {
|
||||
app.get(Endpoints.ERROR_401_GET, Error401Get.default(vars));
|
||||
app.get(Endpoints.ERROR_403_GET, Error403Get.default(vars));
|
||||
app.get(Endpoints.ERROR_404_GET, Error404Get.default);
|
||||
}
|
||||
|
||||
|
@ -124,6 +128,7 @@ export class RestApi {
|
|||
vars.config.authentication_methods),
|
||||
RequireValidatedFirstFactor.middleware(vars.logger),
|
||||
SecondFactorGet.default(vars));
|
||||
|
||||
app.get(Endpoints.LOGOUT_GET, LogoutGet.default);
|
||||
|
||||
app.get(Endpoints.VERIFY_GET, VerifyGet.default(vars));
|
||||
|
@ -132,8 +137,10 @@ export class RestApi {
|
|||
setupTotp(app, vars);
|
||||
setupU2f(app, vars);
|
||||
setupResetPassword(app, vars);
|
||||
setupErrors(app);
|
||||
setupErrors(app, vars);
|
||||
|
||||
app.get(Endpoints.LOGGED_IN, LoggedIn.default(vars));
|
||||
app.get(Endpoints.LOGGED_IN,
|
||||
RequireValidatedFirstFactor.middleware(vars.logger),
|
||||
LoggedIn.default(vars));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,4 +8,9 @@ block form-header
|
|||
|
||||
block content
|
||||
img(class="header-img" src="/img/warning.png" alt="warning")
|
||||
p Please <a href="/">log in</a> to access this resource.
|
||||
if redirection_url
|
||||
p You are not authorized to access this resource.<br/><br/>
|
||||
| Please click <a href=#{redirection_url}>here</a> if you are not
|
||||
| redirected in few seconds.
|
||||
else
|
||||
p You are not authorized to access this resource.
|
|
@ -8,4 +8,9 @@ block form-header
|
|||
|
||||
block content
|
||||
img(class="header-img" src="/img/warning.png" alt="warning")
|
||||
p You are not authorized to access this resource.
|
||||
if redirection_url
|
||||
p You don't have enough privileges to access this resource.<br/><br/>
|
||||
| Please click <a href=#{redirection_url}>here</a> if you are not
|
||||
| redirected in few seconds.
|
||||
else
|
||||
p You don't have enough privileges to access this resource.
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
|
||||
import sinon = require("sinon");
|
||||
import IdentityValidator = require("../src/lib/IdentityCheckMiddleware");
|
||||
import { AuthenticationSessionHandler } from "../src/lib/AuthenticationSessionHandler";
|
||||
import { AuthenticationSessionHandler }
|
||||
from "../src/lib/AuthenticationSessionHandler";
|
||||
import { AuthenticationSession } from "../types/AuthenticationSession";
|
||||
import { UserDataStore } from "../src/lib/storage/UserDataStore";
|
||||
import exceptions = require("../src/lib/Exceptions");
|
||||
|
@ -13,7 +14,8 @@ import ExpressMock = require("./mocks/express");
|
|||
import NotifierMock = require("./mocks/Notifier");
|
||||
import IdentityValidatorMock = require("./mocks/IdentityValidator");
|
||||
import { RequestLoggerStub } from "./mocks/RequestLoggerStub";
|
||||
import { ServerVariablesMock, ServerVariablesMockBuilder } from "./mocks/ServerVariablesMockBuilder";
|
||||
import { ServerVariablesMock, ServerVariablesMockBuilder }
|
||||
from "./mocks/ServerVariablesMockBuilder";
|
||||
|
||||
|
||||
describe("test identity check process", function () {
|
||||
|
@ -36,17 +38,18 @@ describe("test identity check process", function () {
|
|||
|
||||
identityValidable = IdentityValidatorMock.IdentityValidableMock();
|
||||
|
||||
|
||||
req.headers = {};
|
||||
req.session = {};
|
||||
req.originalUrl = "/non-api/xxx";
|
||||
req.session = {};
|
||||
|
||||
req.query = {};
|
||||
req.app = {};
|
||||
|
||||
mocks.notifier.notifyStub.returns(BluebirdPromise.resolve());
|
||||
mocks.userDataStore.produceIdentityValidationTokenStub.returns(Promise.resolve());
|
||||
mocks.userDataStore.consumeIdentityValidationTokenStub.returns(Promise.resolve({ userId: "user" }));
|
||||
mocks.userDataStore.produceIdentityValidationTokenStub
|
||||
.returns(Promise.resolve());
|
||||
mocks.userDataStore.consumeIdentityValidationTokenStub
|
||||
.returns(Promise.resolve({ userId: "user" }));
|
||||
|
||||
app = express();
|
||||
app_get = sinon.stub(app, "get");
|
||||
|
@ -58,112 +61,142 @@ describe("test identity check process", function () {
|
|||
app_post.restore();
|
||||
});
|
||||
|
||||
describe("test start GET", test_start_get_handler);
|
||||
describe("test finish GET", test_finish_get_handler);
|
||||
describe("test start GET", function () {
|
||||
it("should redirect to error 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", vars);
|
||||
|
||||
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", vars);
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
return callback(req as any, res as any, undefined)
|
||||
.then(function () { return BluebirdPromise.reject("Should fail"); })
|
||||
.catch(function () {
|
||||
Assert(res.redirect.calledWith("/error/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", vars);
|
||||
identityValidable.preValidationInit
|
||||
.returns(BluebirdPromise.resolve(identity));
|
||||
const callback = IdentityValidator
|
||||
.get_start_validation(identityValidable, "/endpoint", vars);
|
||||
|
||||
return callback(req as any, res as any, undefined)
|
||||
.then(function () { return BluebirdPromise.reject("Should fail"); })
|
||||
.then(function () {
|
||||
return BluebirdPromise.reject("Should fail");
|
||||
})
|
||||
.catch(function () {
|
||||
Assert.equal(res.status.getCall(0).args[0], 401);
|
||||
Assert(res.redirect.calledWith("/error/401"));
|
||||
});
|
||||
});
|
||||
|
||||
it("should send 401 if userid is missing in provided identity", function () {
|
||||
const endpoint = "/protected";
|
||||
const identity = { email: "abc@example.com" };
|
||||
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", vars);
|
||||
identityValidable.preValidationInit
|
||||
.returns(BluebirdPromise.resolve(identity));
|
||||
const callback = IdentityValidator
|
||||
.get_start_validation(identityValidable, "/endpoint", vars);
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
return callback(req as any, res as any, undefined)
|
||||
.then(function () {
|
||||
return BluebirdPromise.reject(new Error("It should fail"));
|
||||
})
|
||||
.catch(function (err: Error) {
|
||||
Assert(res.redirect.calledWith("/error/401"));
|
||||
});
|
||||
});
|
||||
|
||||
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", vars);
|
||||
identityValidable.preValidationInit
|
||||
.returns(BluebirdPromise.resolve(identity));
|
||||
const callback = IdentityValidator
|
||||
.get_start_validation(identityValidable, "/finish_endpoint", vars);
|
||||
|
||||
return callback(req as any, res as any, undefined)
|
||||
.then(function () {
|
||||
Assert(mocks.notifier.notifyStub.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);
|
||||
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() {
|
||||
|
||||
|
||||
describe("test finish GET", function () {
|
||||
it("should send 401 if no identity_token is provided", function () {
|
||||
|
||||
const callback = IdentityValidator.get_finish_validation(identityValidable, vars);
|
||||
const callback = IdentityValidator
|
||||
.get_finish_validation(identityValidable, vars);
|
||||
|
||||
return callback(req as any, res as any, undefined)
|
||||
.then(function () { return BluebirdPromise.reject("Should fail"); })
|
||||
.then(function () {
|
||||
return BluebirdPromise.reject("Should fail");
|
||||
})
|
||||
.catch(function () {
|
||||
Assert.equal(res.status.getCall(0).args[0], 401);
|
||||
Assert(res.redirect.calledWith("/error/401"));
|
||||
});
|
||||
});
|
||||
|
||||
it("should call postValidation if identity_token is provided and still valid", function () {
|
||||
req.query.identity_token = "token";
|
||||
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, vars);
|
||||
return callback(req as any, res as any, undefined);
|
||||
});
|
||||
const callback = IdentityValidator
|
||||
.get_finish_validation(identityValidable, vars);
|
||||
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";
|
||||
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")));
|
||||
mocks.userDataStore.consumeIdentityValidationTokenStub
|
||||
.returns(BluebirdPromise.reject(new Error("Invalid token")));
|
||||
|
||||
const callback = IdentityValidator.get_finish_validation(identityValidable, vars);
|
||||
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);
|
||||
});
|
||||
});
|
||||
const callback = IdentityValidator
|
||||
.get_finish_validation(identityValidable, vars);
|
||||
return callback(req as any, res as any, undefined)
|
||||
.then(function () {
|
||||
return BluebirdPromise.reject("Should fail");
|
||||
})
|
||||
.catch(function () {
|
||||
Assert(res.redirect.calledWith("/error/401"));
|
||||
});
|
||||
});
|
||||
|
||||
it("should set the identity_check session object even if session does not exist yet", function () {
|
||||
req.query.identity_token = "token";
|
||||
it("should set the identity_check session object even if session does \
|
||||
not exist yet", function () {
|
||||
req.query.identity_token = "token";
|
||||
|
||||
req.session = {};
|
||||
const authSession: AuthenticationSession = AuthenticationSessionHandler.get(req as any, vars.logger);
|
||||
const callback = IdentityValidator.get_finish_validation(identityValidable, vars);
|
||||
req.session = {};
|
||||
const authSession =
|
||||
AuthenticationSessionHandler.get(req as any, vars.logger);
|
||||
const callback = IdentityValidator
|
||||
.get_finish_validation(identityValidable, vars);
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
}
|
||||
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");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -9,6 +9,7 @@ export interface RequestMock {
|
|||
headers?: any;
|
||||
get?: any;
|
||||
query?: any;
|
||||
originalUrl: string;
|
||||
}
|
||||
|
||||
export interface ResponseMock {
|
||||
|
@ -51,6 +52,7 @@ export interface ResponseMock {
|
|||
|
||||
export function RequestMock(): RequestMock {
|
||||
return {
|
||||
originalUrl: "/non-api/xxx",
|
||||
app: {
|
||||
get: sinon.stub()
|
||||
},
|
||||
|
|
|
@ -2,18 +2,60 @@ import Sinon = require("sinon");
|
|||
import Express = require("express");
|
||||
import Assert = require("assert");
|
||||
import Get401 from "../../../../src/lib/routes/error/401/get";
|
||||
import { ServerVariables } from "../../../../src/lib/ServerVariables";
|
||||
import { ServerVariablesMockBuilder, ServerVariablesMock }
|
||||
from "../../../mocks/ServerVariablesMockBuilder";
|
||||
|
||||
describe("Server error 401", function () {
|
||||
it("should render the page", function () {
|
||||
const req = {} as Express.Request;
|
||||
const res = {
|
||||
render: Sinon.stub()
|
||||
};
|
||||
let vars: ServerVariables;
|
||||
let mocks: ServerVariablesMock;
|
||||
let req: any;
|
||||
let res: any;
|
||||
let renderSpy: Sinon.SinonSpy;
|
||||
|
||||
return Get401(req, res as any)
|
||||
beforeEach(function () {
|
||||
const s = ServerVariablesMockBuilder.build();
|
||||
vars = s.variables;
|
||||
mocks = s.mocks;
|
||||
|
||||
renderSpy = Sinon.spy();
|
||||
req = {
|
||||
headers: {}
|
||||
};
|
||||
res = {
|
||||
render: renderSpy
|
||||
};
|
||||
});
|
||||
|
||||
it("should set redirection url to the default redirection url", function () {
|
||||
vars.config.default_redirection_url = "http://default-redirection";
|
||||
return Get401(vars)(req, res as any)
|
||||
.then(function () {
|
||||
Assert(res.render.calledOnce);
|
||||
Assert(res.render.calledWith("errors/401"));
|
||||
Assert(renderSpy.calledOnce);
|
||||
Assert(renderSpy.calledWithExactly("errors/401", {
|
||||
redirection_url: "http://default-redirection"
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
it("should set redirection url to the referer", function () {
|
||||
req.headers["referer"] = "http://redirection";
|
||||
return Get401(vars)(req, res as any)
|
||||
.then(function () {
|
||||
Assert(renderSpy.calledOnce);
|
||||
Assert(renderSpy.calledWithExactly("errors/401", {
|
||||
redirection_url: "http://redirection"
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
it("should render without redirecting the user", function () {
|
||||
return Get401(vars)(req, res as any)
|
||||
.then(function () {
|
||||
Assert(renderSpy.calledOnce);
|
||||
Assert(renderSpy.calledWithExactly("errors/401", {
|
||||
redirection_url: undefined
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
|
@ -2,18 +2,60 @@ import Sinon = require("sinon");
|
|||
import Express = require("express");
|
||||
import Assert = require("assert");
|
||||
import Get403 from "../../../../src/lib/routes/error/403/get";
|
||||
import { ServerVariables } from "../../../../src/lib/ServerVariables";
|
||||
import { ServerVariablesMockBuilder, ServerVariablesMock }
|
||||
from "../../../mocks/ServerVariablesMockBuilder";
|
||||
|
||||
describe("Server error 403", function () {
|
||||
it("should render the page", function () {
|
||||
const req = {} as Express.Request;
|
||||
const res = {
|
||||
render: Sinon.stub()
|
||||
};
|
||||
let vars: ServerVariables;
|
||||
let mocks: ServerVariablesMock;
|
||||
let req: any;
|
||||
let res: any;
|
||||
let renderSpy: Sinon.SinonSpy;
|
||||
|
||||
return Get403(req, res as any)
|
||||
beforeEach(function () {
|
||||
const s = ServerVariablesMockBuilder.build();
|
||||
vars = s.variables;
|
||||
mocks = s.mocks;
|
||||
|
||||
renderSpy = Sinon.spy();
|
||||
req = {
|
||||
headers: {}
|
||||
};
|
||||
res = {
|
||||
render: renderSpy
|
||||
};
|
||||
});
|
||||
|
||||
it("should set redirection url to the default redirection url", function () {
|
||||
vars.config.default_redirection_url = "http://default-redirection";
|
||||
return Get403(vars)(req, res as any)
|
||||
.then(function () {
|
||||
Assert(res.render.calledOnce);
|
||||
Assert(res.render.calledWith("errors/403"));
|
||||
Assert(renderSpy.calledOnce);
|
||||
Assert(renderSpy.calledWithExactly("errors/403", {
|
||||
redirection_url: "http://default-redirection"
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
it("should set redirection url to the referer", function () {
|
||||
req.headers["referer"] = "http://redirection";
|
||||
return Get403(vars)(req, res as any)
|
||||
.then(function () {
|
||||
Assert(renderSpy.calledOnce);
|
||||
Assert(renderSpy.calledWithExactly("errors/403", {
|
||||
redirection_url: "http://redirection"
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
it("should render without redirecting the user", function () {
|
||||
return Get403(vars)(req, res as any)
|
||||
.then(function () {
|
||||
Assert(renderSpy.calledOnce);
|
||||
Assert(renderSpy.calledWithExactly("errors/403", {
|
||||
redirection_url: undefined
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
|
@ -34,6 +34,7 @@ describe("test the first factor validation route", function () {
|
|||
mocks.regulator.markStub.returns(BluebirdPromise.resolve());
|
||||
|
||||
req = {
|
||||
originalUrl: "/api/firstfactor",
|
||||
body: {
|
||||
username: "username",
|
||||
password: "password"
|
||||
|
|
|
@ -18,6 +18,7 @@ describe("test reset password identity check", function () {
|
|||
|
||||
beforeEach(function () {
|
||||
req = {
|
||||
originalUrl: "/non-api/xxx",
|
||||
query: {
|
||||
userid: "user"
|
||||
},
|
||||
|
|
|
@ -20,6 +20,7 @@ describe("test reset password route", function () {
|
|||
|
||||
beforeEach(function () {
|
||||
req = {
|
||||
originalUrl: "/api/password-reset",
|
||||
body: {
|
||||
userid: "user"
|
||||
},
|
||||
|
|
|
@ -25,6 +25,7 @@ describe("test totp route", function () {
|
|||
mocks = s.mocks;
|
||||
const app_get = Sinon.stub();
|
||||
req = {
|
||||
originalUrl: "/api/totp-register",
|
||||
app: {},
|
||||
body: {
|
||||
token: "abc"
|
||||
|
|
|
@ -20,6 +20,7 @@ describe("test u2f routes: register", function () {
|
|||
|
||||
beforeEach(function () {
|
||||
req = ExpressMock.RequestMock();
|
||||
req.originalUrl = "/api/xxxx";
|
||||
req.app = {};
|
||||
req.session = {
|
||||
auth: {
|
||||
|
|
|
@ -16,6 +16,7 @@ describe("test u2f routes: register_request", function () {
|
|||
|
||||
beforeEach(function () {
|
||||
req = ExpressMock.RequestMock();
|
||||
req.originalUrl = "/api/xxxx";
|
||||
req.app = {};
|
||||
req.session = {
|
||||
auth: {
|
||||
|
|
|
@ -20,6 +20,7 @@ describe("test u2f routes: sign", function () {
|
|||
beforeEach(function () {
|
||||
req = ExpressMock.RequestMock();
|
||||
req.app = {};
|
||||
req.originalUrl = "/api/xxxx";
|
||||
|
||||
const s = ServerVariablesMockBuilder.build();
|
||||
mocks = s.mocks;
|
||||
|
|
|
@ -20,6 +20,7 @@ describe("test u2f routes: sign_request", function () {
|
|||
|
||||
beforeEach(function () {
|
||||
req = ExpressMock.RequestMock();
|
||||
req.originalUrl = "/api/xxxx";
|
||||
req.app = {};
|
||||
req.session = {
|
||||
auth: {
|
||||
|
|
|
@ -12,7 +12,7 @@ import ExpressMock = require("../../mocks/express");
|
|||
import { ServerVariables } from "../../../src/lib/ServerVariables";
|
||||
import { ServerVariablesMockBuilder, ServerVariablesMock } from "../../mocks/ServerVariablesMockBuilder";
|
||||
|
||||
describe("test /verify endpoint", function () {
|
||||
describe("test /api/verify endpoint", function () {
|
||||
let req: ExpressMock.RequestMock;
|
||||
let res: ExpressMock.ResponseMock;
|
||||
let mocks: ServerVariablesMock;
|
||||
|
@ -22,6 +22,7 @@ describe("test /verify endpoint", function () {
|
|||
beforeEach(function () {
|
||||
req = ExpressMock.RequestMock();
|
||||
res = ExpressMock.ResponseMock();
|
||||
req.originalUrl = "/api/xxxx";
|
||||
req.query = {
|
||||
redirect: "http://redirect.url"
|
||||
};
|
||||
|
@ -174,7 +175,7 @@ describe("test /verify endpoint", function () {
|
|||
});
|
||||
|
||||
describe("inactivity period", function () {
|
||||
it("should update last inactivity period on requests on /verify", function () {
|
||||
it("should update last inactivity period on requests on /api/verify", function () {
|
||||
mocks.config.session.inactivity = 200000;
|
||||
mocks.accessController.isAccessAllowedMock.returns(true);
|
||||
const currentTime = new Date().getTime() - 1000;
|
||||
|
|
|
@ -264,7 +264,7 @@ export const FIRST_FACTOR_GET = "/";
|
|||
export const SECOND_FACTOR_GET = "/secondfactor";
|
||||
|
||||
/**
|
||||
* @api {get} /verify Verify user authentication
|
||||
* @api {get} /api/verify Verify user authentication
|
||||
* @apiName VerifyAuthentication
|
||||
* @apiGroup Verification
|
||||
* @apiVersion 1.0.0
|
||||
|
@ -279,7 +279,7 @@ export const SECOND_FACTOR_GET = "/secondfactor";
|
|||
* are set. Remote-User contains the user id of the currently logged in user and Remote-Groups
|
||||
* a comma separated list of assigned groups.
|
||||
*/
|
||||
export const VERIFY_GET = "/verify";
|
||||
export const VERIFY_GET = "/api/verify";
|
||||
|
||||
/**
|
||||
* @api {get} /logout Serves logout page
|
||||
|
|
|
@ -49,3 +49,22 @@ Feature: User is correctly redirected
|
|||
And I use "REGISTERED" as TOTP token handle
|
||||
And I click on "Sign in"
|
||||
Then I'm redirected to "https://home.test.local:8080/"
|
||||
|
||||
|
||||
Scenario: User is redirected when hitting an error 401
|
||||
When I visit "https://auth.test.local:8080/secondfactor/u2f/identity/finish"
|
||||
Then I'm redirected to "https://auth.test.local:8080/error/401"
|
||||
And I sleep for 5 seconds
|
||||
And I'm redirected to "https://home.test.local:8080/"
|
||||
|
||||
@need-registered-user-harry
|
||||
Scenario: User is redirected when hitting an error 403
|
||||
When I visit "https://auth.test.local:8080"
|
||||
And I login with user "harry" and password "password"
|
||||
And I use "REGISTERED" as TOTP token handle
|
||||
And I click on "Sign in"
|
||||
And I'm redirected to "https://home.test.local:8080/"
|
||||
When I visit "https://admin.test.local:8080/secret.html"
|
||||
Then I'm redirected to "https://auth.test.local:8080/error/403"
|
||||
And I sleep for 5 seconds
|
||||
And I'm redirected to "https://home.test.local:8080/"
|
|
@ -8,6 +8,7 @@ Feature: Non authenticated users have no access to certain pages
|
|||
| https://auth.test.local:8080/secondfactor/u2f/identity/finish | 401 | GET |
|
||||
| https://auth.test.local:8080/secondfactor/totp/identity/start | 401 | GET |
|
||||
| https://auth.test.local:8080/secondfactor/totp/identity/finish | 401 | GET |
|
||||
| https://auth.test.local:8080/loggedin | 401 | GET |
|
||||
| https://auth.test.local:8080/api/totp | 401 | POST |
|
||||
| https://auth.test.local:8080/api/u2f/sign_request | 401 | GET |
|
||||
| https://auth.test.local:8080/api/u2f/sign | 401 | POST |
|
||||
|
|
Loading…
Reference in New Issue