Fix redirection after authentication and error page when accessing restricted pages
parent
785182236c
commit
928209dc98
|
@ -61,6 +61,7 @@ http {
|
||||||
ssl_certificate_key /etc/ssl/server.key;
|
ssl_certificate_key /etc/ssl/server.key;
|
||||||
|
|
||||||
error_page 401 = @error401;
|
error_page 401 = @error401;
|
||||||
|
error_page 403 = https://auth.test.local:8080/error/403;
|
||||||
location @error401 {
|
location @error401 {
|
||||||
return 302 https://auth.test.local:8080;
|
return 302 https://auth.test.local:8080;
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,13 +39,15 @@ function verify_filter(req: express.Request, res: express.Response): BluebirdPro
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function (req: express.Request, res: express.Response) {
|
export default function (req: express.Request, res: express.Response): BluebirdPromise<void> {
|
||||||
const logger = ServerVariablesHandler.getLogger(req.app);
|
const logger = ServerVariablesHandler.getLogger(req.app);
|
||||||
verify_filter(req, res)
|
return verify_filter(req, res)
|
||||||
.then(function () {
|
.then(function () {
|
||||||
res.status(204);
|
res.status(204);
|
||||||
res.send();
|
res.send();
|
||||||
|
return BluebirdPromise.resolve();
|
||||||
})
|
})
|
||||||
|
.catch(exceptions.DomainAccessDenied, ErrorReplies.replyWithError403(res, logger))
|
||||||
.catch(ErrorReplies.replyWithError401(res, logger));
|
.catch(ErrorReplies.replyWithError401(res, logger));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,4 +8,4 @@ block form-header
|
||||||
<img class="header-img" src="/img/warning.png" alt="">
|
<img class="header-img" src="/img/warning.png" alt="">
|
||||||
|
|
||||||
block content
|
block content
|
||||||
<p>You are either not authorized or not <a href="/">logged in</a>.</p>
|
<p>Please <a href="/">log in</a> to access this resource.</p>
|
||||||
|
|
|
@ -8,4 +8,4 @@ block form-header
|
||||||
<img class="header-img" src="/img/warning.png" alt="">
|
<img class="header-img" src="/img/warning.png" alt="">
|
||||||
|
|
||||||
block content
|
block content
|
||||||
<p>You are either not authorized or not <a href="/">logged in</a>.</p>
|
<p>You are not authorized to access this resource.</p>
|
||||||
|
|
|
@ -1,7 +1,23 @@
|
||||||
Feature: User is redirected to authelia when he is not authenticated
|
Feature: User is correctly redirected correctly
|
||||||
|
|
||||||
Scenario: User is redirected to authelia
|
Scenario: User is redirected to authelia when he is not authenticated
|
||||||
Given I'm on https://home.test.local:8080
|
Given I'm on https://home.test.local:8080
|
||||||
When I click on the link to secret.test.local
|
When I click on the link to secret.test.local
|
||||||
Then I'm redirected to "https://auth.test.local:8080/"
|
Then I'm redirected to "https://auth.test.local:8080/"
|
||||||
|
|
||||||
|
Scenario: User is redirected to home page after several authentication tries
|
||||||
|
Given I'm on https://auth.test.local:8080/
|
||||||
|
And I login with user "john" and password "password"
|
||||||
|
And I register a TOTP secret called "Sec0"
|
||||||
|
And I visit "https://public.test.local:8080/secret.html"
|
||||||
|
When I login with user "john" and password "badpassword"
|
||||||
|
And I clear field "username"
|
||||||
|
And I login with user "john" and password "password"
|
||||||
|
And I use "Sec0" as TOTP token handle
|
||||||
|
And I click on "TOTP"
|
||||||
|
Then I'm redirected to "https://public.test.local:8080/secret.html"
|
||||||
|
|
||||||
|
Scenario: User Harry does not have access to https://secret.test.local:8080/secret.html and thus he must get an error 401
|
||||||
|
When I register TOTP and login with user "harry" and password "password"
|
||||||
|
And I visit "https://secret.test.local:8080/secret.html"
|
||||||
|
Then I get an error 403
|
|
@ -14,6 +14,10 @@ Cucumber.defineSupportCode(function ({ Given, When, Then }) {
|
||||||
return this.setFieldTo(fieldName, content);
|
return this.setFieldTo(fieldName, content);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
When("I clear field {stringInDoubleQuotes}", function (fieldName: string) {
|
||||||
|
return this.clearField(fieldName);
|
||||||
|
});
|
||||||
|
|
||||||
When("I click on {stringInDoubleQuotes}", function (text: string) {
|
When("I click on {stringInDoubleQuotes}", function (text: string) {
|
||||||
return this.clickOnButton(text);
|
return this.clickOnButton(text);
|
||||||
});
|
});
|
||||||
|
@ -55,20 +59,20 @@ Cucumber.defineSupportCode(function ({ Given, When, Then }) {
|
||||||
return this.registerTotpAndSignin(username, password);
|
return this.registerTotpAndSignin(username, password);
|
||||||
});
|
});
|
||||||
|
|
||||||
function hasAccessToSecret(link: string, driver: any) {
|
function hasAccessToSecret(link: string, that: any) {
|
||||||
return driver.get(link)
|
return that.driver.get(link)
|
||||||
.then(function () {
|
.then(function () {
|
||||||
return driver.findElement(seleniumWebdriver.By.tagName("body")).getText()
|
return that.driver.findElement(seleniumWebdriver.By.tagName("body")).getText()
|
||||||
.then(function (body: string) {
|
.then(function (body: string) {
|
||||||
Assert(body.indexOf("This is a very important secret!") > -1);
|
Assert(body.indexOf("This is a very important secret!") > -1, body);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasNoAccessToSecret(link: string, driver: any) {
|
function hasNoAccessToSecret(link: string, that: any) {
|
||||||
return driver.get(link)
|
return that.driver.get(link)
|
||||||
.then(function () {
|
.then(function () {
|
||||||
return driver.wait(seleniumWebdriver.until.urlIs("https://auth.test.local:8080/"));
|
return that.getErrorPage(403);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,7 +80,7 @@ Cucumber.defineSupportCode(function ({ Given, When, Then }) {
|
||||||
const promises = [];
|
const promises = [];
|
||||||
for (let i = 0; i < dataTable.rows().length; i++) {
|
for (let i = 0; i < dataTable.rows().length; i++) {
|
||||||
const url = (dataTable.hashes() as any)[i].url;
|
const url = (dataTable.hashes() as any)[i].url;
|
||||||
promises.push(hasAccessToSecret(url, this.driver));
|
promises.push(hasAccessToSecret(url, this));
|
||||||
}
|
}
|
||||||
return Promise.all(promises);
|
return Promise.all(promises);
|
||||||
});
|
});
|
||||||
|
@ -85,7 +89,7 @@ Cucumber.defineSupportCode(function ({ Given, When, Then }) {
|
||||||
const promises = [];
|
const promises = [];
|
||||||
for (let i = 0; i < dataTable.rows().length; i++) {
|
for (let i = 0; i < dataTable.rows().length; i++) {
|
||||||
const url = (dataTable.hashes() as any)[i].url;
|
const url = (dataTable.hashes() as any)[i].url;
|
||||||
promises.push(hasNoAccessToSecret(url, this.driver));
|
promises.push(hasNoAccessToSecret(url, this));
|
||||||
}
|
}
|
||||||
return Promise.all(promises);
|
return Promise.all(promises);
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,8 +4,6 @@ import Assert = require("assert");
|
||||||
|
|
||||||
Cucumber.defineSupportCode(function ({ Given, When, Then }) {
|
Cucumber.defineSupportCode(function ({ Given, When, Then }) {
|
||||||
Then("I get an error {number}", function (code: number) {
|
Then("I get an error {number}", function (code: number) {
|
||||||
return this.driver
|
return this.getErrorPage(code);
|
||||||
.findElement(seleniumWebdriver.By.tagName("h1"))
|
|
||||||
.findElement(seleniumWebdriver.By.xpath("//h1[contains(.,'Error " + code + "')]"));
|
|
||||||
});
|
});
|
||||||
});
|
});
|
|
@ -21,6 +21,16 @@ function CustomWorld() {
|
||||||
.sendKeys(content);
|
.sendKeys(content);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.clearField = function (fieldName: string) {
|
||||||
|
return this.driver.findElement(seleniumWebdriver.By.id(fieldName)).clear();
|
||||||
|
};
|
||||||
|
|
||||||
|
this.getErrorPage = function (code: number) {
|
||||||
|
return this.driver
|
||||||
|
.findElement(seleniumWebdriver.By.tagName("h1"))
|
||||||
|
.findElement(seleniumWebdriver.By.xpath("//h1[contains(.,'Error " + code + "')]"));
|
||||||
|
};
|
||||||
|
|
||||||
this.clickOnButton = function (buttonText: string) {
|
this.clickOnButton = function (buttonText: string) {
|
||||||
return this.driver
|
return this.driver
|
||||||
.findElement(seleniumWebdriver.By.tagName("button"))
|
.findElement(seleniumWebdriver.By.tagName("button"))
|
||||||
|
@ -29,9 +39,11 @@ function CustomWorld() {
|
||||||
};
|
};
|
||||||
|
|
||||||
this.loginWithUserPassword = function (username: string, password: string) {
|
this.loginWithUserPassword = function (username: string, password: string) {
|
||||||
return this.driver
|
return that.driver.wait(seleniumWebdriver.until.elementLocated(seleniumWebdriver.By.id("username")), 4000)
|
||||||
.findElement(seleniumWebdriver.By.id("username"))
|
.then(function () {
|
||||||
.sendKeys(username)
|
return that.driver.findElement(seleniumWebdriver.By.id("username"))
|
||||||
|
.sendKeys(username);
|
||||||
|
})
|
||||||
.then(function () {
|
.then(function () {
|
||||||
return that.driver.findElement(seleniumWebdriver.By.id("password"))
|
return that.driver.findElement(seleniumWebdriver.By.id("password"))
|
||||||
.sendKeys(password);
|
.sendKeys(password);
|
||||||
|
@ -39,14 +51,14 @@ function CustomWorld() {
|
||||||
.then(function () {
|
.then(function () {
|
||||||
return that.driver.findElement(seleniumWebdriver.By.tagName("button"))
|
return that.driver.findElement(seleniumWebdriver.By.tagName("button"))
|
||||||
.click();
|
.click();
|
||||||
})
|
|
||||||
.then(function () {
|
|
||||||
return that.driver.wait(seleniumWebdriver.until.elementLocated(seleniumWebdriver.By.className("register-totp")), 4000);
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
this.registerTotpSecret = function (totpSecretHandle: string) {
|
this.registerTotpSecret = function (totpSecretHandle: string) {
|
||||||
return this.driver.findElement(seleniumWebdriver.By.className("register-totp")).click()
|
return that.driver.wait(seleniumWebdriver.until.elementLocated(seleniumWebdriver.By.className("register-totp")), 4000)
|
||||||
|
.then(function () {
|
||||||
|
return that.driver.findElement(seleniumWebdriver.By.className("register-totp")).click();
|
||||||
|
})
|
||||||
.then(function () {
|
.then(function () {
|
||||||
const notif = Fs.readFileSync("./notifications/notification.txt").toString();
|
const notif = Fs.readFileSync("./notifications/notification.txt").toString();
|
||||||
const regexp = new RegExp(/Link: (.+)/);
|
const regexp = new RegExp(/Link: (.+)/);
|
||||||
|
@ -75,8 +87,11 @@ function CustomWorld() {
|
||||||
};
|
};
|
||||||
|
|
||||||
this.useTotpToken = function (totpSecret: string) {
|
this.useTotpToken = function (totpSecret: string) {
|
||||||
return this.driver.findElement(seleniumWebdriver.By.id("token"))
|
return that.driver.wait(seleniumWebdriver.until.elementLocated(seleniumWebdriver.By.className("register-totp")), 4000)
|
||||||
|
.then(function () {
|
||||||
|
return that.driver.findElement(seleniumWebdriver.By.id("token"))
|
||||||
.sendKeys(totpSecret);
|
.sendKeys(totpSecret);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
this.registerTotpAndSignin = function (username: string, password: string) {
|
this.registerTotpAndSignin = function (username: string, password: string) {
|
||||||
|
|
|
@ -24,6 +24,8 @@ describe("test authentication token verification", function () {
|
||||||
|
|
||||||
req = ExpressMock.RequestMock();
|
req = ExpressMock.RequestMock();
|
||||||
res = ExpressMock.ResponseMock();
|
res = ExpressMock.ResponseMock();
|
||||||
|
req.session = {};
|
||||||
|
AuthenticationSession.reset(req as any);
|
||||||
req.headers = {};
|
req.headers = {};
|
||||||
req.headers.host = "secret.example.com";
|
req.headers.host = "secret.example.com";
|
||||||
const mocks = ServerVariablesMock.mock(req.app);
|
const mocks = ServerVariablesMock.mock(req.app);
|
||||||
|
@ -32,7 +34,7 @@ describe("test authentication token verification", function () {
|
||||||
mocks.accessController = accessController as any;
|
mocks.accessController = accessController as any;
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should be already authenticated", function (done) {
|
it("should be already authenticated", function () {
|
||||||
req.session = {};
|
req.session = {};
|
||||||
AuthenticationSession.reset(req as any);
|
AuthenticationSession.reset(req as any);
|
||||||
const authSession = AuthenticationSession.get(req as any);
|
const authSession = AuthenticationSession.get(req as any);
|
||||||
|
@ -40,39 +42,34 @@ describe("test authentication token verification", function () {
|
||||||
authSession.second_factor = true;
|
authSession.second_factor = true;
|
||||||
authSession.userid = "myuser";
|
authSession.userid = "myuser";
|
||||||
|
|
||||||
res.send = sinon.spy(function () {
|
return VerifyGet.default(req as express.Request, res as any)
|
||||||
|
.then(function () {
|
||||||
assert.equal(204, res.status.getCall(0).args[0]);
|
assert.equal(204, res.status.getCall(0).args[0]);
|
||||||
done();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
VerifyGet.default(req as express.Request, res as any);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("given different cases of session", function () {
|
describe("given different cases of session", function () {
|
||||||
function test_session(auth_session: AuthenticationSession.AuthenticationSession, status_code: number) {
|
function test_session(auth_session: AuthenticationSession.AuthenticationSession, status_code: number) {
|
||||||
return new BluebirdPromise(function (resolve, reject) {
|
return VerifyGet.default(req as express.Request, res as any)
|
||||||
req.session = {};
|
.then(function () {
|
||||||
req.session.auth_session = auth_session;
|
|
||||||
|
|
||||||
res.send = sinon.spy(function () {
|
|
||||||
assert.equal(status_code, res.status.getCall(0).args[0]);
|
assert.equal(status_code, res.status.getCall(0).args[0]);
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
|
|
||||||
VerifyGet.default(req as express.Request, res as any);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function test_unauthorized(auth_session: AuthenticationSession.AuthenticationSession) {
|
function test_non_authenticated_401(auth_session: AuthenticationSession.AuthenticationSession) {
|
||||||
return test_session(auth_session, 401);
|
return test_session(auth_session, 401);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function test_unauthorized_403(auth_session: AuthenticationSession.AuthenticationSession) {
|
||||||
|
return test_session(auth_session, 403);
|
||||||
|
}
|
||||||
|
|
||||||
function test_authorized(auth_session: AuthenticationSession.AuthenticationSession) {
|
function test_authorized(auth_session: AuthenticationSession.AuthenticationSession) {
|
||||||
return test_session(auth_session, 204);
|
return test_session(auth_session, 204);
|
||||||
}
|
}
|
||||||
|
|
||||||
it("should not be authenticated when second factor is missing", function () {
|
it("should not be authenticated when second factor is missing", function () {
|
||||||
return test_unauthorized({
|
return test_non_authenticated_401({
|
||||||
userid: "user",
|
userid: "user",
|
||||||
first_factor: true,
|
first_factor: true,
|
||||||
second_factor: false,
|
second_factor: false,
|
||||||
|
@ -82,7 +79,7 @@ describe("test authentication token verification", function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not be authenticated when first factor is missing", function () {
|
it("should not be authenticated when first factor is missing", function () {
|
||||||
return test_unauthorized({
|
return test_non_authenticated_401({
|
||||||
userid: "user",
|
userid: "user",
|
||||||
first_factor: false,
|
first_factor: false,
|
||||||
second_factor: true,
|
second_factor: true,
|
||||||
|
@ -92,7 +89,7 @@ describe("test authentication token verification", function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not be authenticated when userid is missing", function () {
|
it("should not be authenticated when userid is missing", function () {
|
||||||
return test_unauthorized({
|
return test_non_authenticated_401({
|
||||||
userid: undefined,
|
userid: undefined,
|
||||||
first_factor: true,
|
first_factor: true,
|
||||||
second_factor: false,
|
second_factor: false,
|
||||||
|
@ -102,7 +99,7 @@ describe("test authentication token verification", function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not be authenticated when first and second factor are missing", function () {
|
it("should not be authenticated when first and second factor are missing", function () {
|
||||||
return test_unauthorized({
|
return test_non_authenticated_401({
|
||||||
userid: "user",
|
userid: "user",
|
||||||
first_factor: false,
|
first_factor: false,
|
||||||
second_factor: false,
|
second_factor: false,
|
||||||
|
@ -112,16 +109,21 @@ describe("test authentication token verification", function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not be authenticated when session has not be initiated", function () {
|
it("should not be authenticated when session has not be initiated", function () {
|
||||||
return test_unauthorized(undefined);
|
return test_non_authenticated_401(undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should not be authenticated when domain is not allowed for user", function () {
|
it("should not be authenticated when domain is not allowed for user", function () {
|
||||||
|
const authSession = AuthenticationSession.get(req as any);
|
||||||
|
authSession.first_factor = true;
|
||||||
|
authSession.second_factor = true;
|
||||||
|
authSession.userid = "myuser";
|
||||||
|
|
||||||
req.headers.host = "test.example.com";
|
req.headers.host = "test.example.com";
|
||||||
|
|
||||||
accessController.isDomainAllowedForUser.returns(false);
|
accessController.isDomainAllowedForUser.returns(false);
|
||||||
accessController.isDomainAllowedForUser.withArgs("test.example.com", "user", ["group1", "group2"]).returns(true);
|
accessController.isDomainAllowedForUser.withArgs("test.example.com", "user", ["group1", "group2"]).returns(true);
|
||||||
|
|
||||||
return test_unauthorized({
|
return test_unauthorized_403({
|
||||||
first_factor: true,
|
first_factor: true,
|
||||||
second_factor: true,
|
second_factor: true,
|
||||||
userid: "user",
|
userid: "user",
|
||||||
|
|
Loading…
Reference in New Issue