Add redirection URL as a query parameter during authentication
Before this fix, the redirection URL was stored in the user session, but this has a big drawback since user could open several pages in browser and thus override the redirection URL leading the user to be incorrectly redirected.pull/91/head
parent
83f5302615
commit
7128970a53
|
@ -60,12 +60,6 @@ http {
|
||||||
ssl_certificate /etc/ssl/server.crt;
|
ssl_certificate /etc/ssl/server.crt;
|
||||||
ssl_certificate_key /etc/ssl/server.key;
|
ssl_certificate_key /etc/ssl/server.key;
|
||||||
|
|
||||||
error_page 401 = @error401;
|
|
||||||
error_page 403 = https://auth.test.local:8080/error/403;
|
|
||||||
location @error401 {
|
|
||||||
return 302 https://auth.test.local:8080;
|
|
||||||
}
|
|
||||||
|
|
||||||
location /auth_verify {
|
location /auth_verify {
|
||||||
internal;
|
internal;
|
||||||
proxy_set_header X-Original-URI $request_uri;
|
proxy_set_header X-Original-URI $request_uri;
|
||||||
|
@ -78,12 +72,20 @@ http {
|
||||||
location = /secret.html {
|
location = /secret.html {
|
||||||
auth_request /auth_verify;
|
auth_request /auth_verify;
|
||||||
|
|
||||||
|
auth_request_set $redirect $upstream_http_redirect;
|
||||||
|
proxy_set_header Redirect $redirect;
|
||||||
|
|
||||||
auth_request_set $user $upstream_http_x_remote_user;
|
auth_request_set $user $upstream_http_x_remote_user;
|
||||||
proxy_set_header X-Forwarded-User $user;
|
proxy_set_header X-Forwarded-User $user;
|
||||||
|
|
||||||
auth_request_set $groups $upstream_http_remote_groups;
|
auth_request_set $groups $upstream_http_remote_groups;
|
||||||
proxy_set_header Remote-Groups $groups;
|
proxy_set_header Remote-Groups $groups;
|
||||||
|
|
||||||
auth_request_set $expiry $upstream_http_remote_expiry;
|
auth_request_set $expiry $upstream_http_remote_expiry;
|
||||||
proxy_set_header Remote-Expiry $expiry;
|
proxy_set_header Remote-Expiry $expiry;
|
||||||
|
|
||||||
|
error_page 401 =302 https://auth.test.local:8080?redirect=$redirect;
|
||||||
|
error_page 403 = https://auth.test.local:8080/error/403;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ start_services() {
|
||||||
}
|
}
|
||||||
|
|
||||||
shut_services() {
|
shut_services() {
|
||||||
$DC_SCRIPT down
|
$DC_SCRIPT down --remove-orphans
|
||||||
}
|
}
|
||||||
|
|
||||||
expect_services_count() {
|
expect_services_count() {
|
||||||
|
@ -66,4 +66,4 @@ run_integration_tests
|
||||||
run_other_tests
|
run_other_tests
|
||||||
|
|
||||||
# Test example with precompiled container
|
# Test example with precompiled container
|
||||||
run_other_tests_docker
|
run_other_tests_docker
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
|
||||||
|
export class QueryParametersRetriever {
|
||||||
|
static get(name: string, url?: string): string {
|
||||||
|
if (!url) url = window.location.href;
|
||||||
|
name = name.replace(/[\[\]]/g, "\\$&");
|
||||||
|
const regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
|
||||||
|
results = regex.exec(url);
|
||||||
|
if (!results) return undefined;
|
||||||
|
if (!results[2]) return "";
|
||||||
|
return decodeURIComponent(results[2].replace(/\+/g, " "));
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ import FirstFactorValidator = require("./FirstFactorValidator");
|
||||||
import JSLogger = require("js-logger");
|
import JSLogger = require("js-logger");
|
||||||
import UISelectors = require("./UISelectors");
|
import UISelectors = require("./UISelectors");
|
||||||
import { Notifier } from "../Notifier";
|
import { Notifier } from "../Notifier";
|
||||||
|
import { QueryParametersRetriever } from "../QueryParametersRetriever";
|
||||||
|
|
||||||
import Endpoints = require("../../../server/endpoints");
|
import Endpoints = require("../../../server/endpoints");
|
||||||
|
|
||||||
|
@ -22,8 +23,12 @@ export default function (window: Window, $: JQueryStatic,
|
||||||
|
|
||||||
function onFirstFactorSuccess() {
|
function onFirstFactorSuccess() {
|
||||||
jslogger.debug("First factor validated.");
|
jslogger.debug("First factor validated.");
|
||||||
// Redirect to second factor
|
const redirectUrl = QueryParametersRetriever.get("redirect");
|
||||||
window.location.href = Endpoints.SECOND_FACTOR_GET;
|
if (redirectUrl)
|
||||||
|
window.location.href = Endpoints.SECOND_FACTOR_GET + "?redirect="
|
||||||
|
+ encodeURIComponent(redirectUrl);
|
||||||
|
else
|
||||||
|
window.location.href = Endpoints.SECOND_FACTOR_GET;
|
||||||
}
|
}
|
||||||
|
|
||||||
function onFirstFactorFailure(err: Error) {
|
function onFirstFactorFailure(err: Error) {
|
||||||
|
|
|
@ -7,6 +7,7 @@ import U2FValidator = require("./U2FValidator");
|
||||||
import Endpoints = require("../../../server/endpoints");
|
import Endpoints = require("../../../server/endpoints");
|
||||||
import Constants = require("./constants");
|
import Constants = require("./constants");
|
||||||
import { Notifier } from "../Notifier";
|
import { Notifier } from "../Notifier";
|
||||||
|
import { QueryParametersRetriever } from "../QueryParametersRetriever";
|
||||||
|
|
||||||
|
|
||||||
export default function (window: Window, $: JQueryStatic, u2fApi: typeof U2fApi) {
|
export default function (window: Window, $: JQueryStatic, u2fApi: typeof U2fApi) {
|
||||||
|
@ -14,7 +15,11 @@ export default function (window: Window, $: JQueryStatic, u2fApi: typeof U2fApi)
|
||||||
const notifierU2f = new Notifier(".notification-u2f", $);
|
const notifierU2f = new Notifier(".notification-u2f", $);
|
||||||
|
|
||||||
function onAuthenticationSuccess(data: any) {
|
function onAuthenticationSuccess(data: any) {
|
||||||
window.location.href = data.redirection_url;
|
const redirectUrl = QueryParametersRetriever.get("redirect");
|
||||||
|
if (redirectUrl)
|
||||||
|
window.location.href = redirectUrl;
|
||||||
|
else
|
||||||
|
window.location.href = Endpoints.FIRST_FACTOR_GET;
|
||||||
}
|
}
|
||||||
|
|
||||||
function onSecondFactorTotpSuccess(data: any) {
|
function onSecondFactorTotpSuccess(data: any) {
|
||||||
|
|
|
@ -59,8 +59,8 @@ export default function (req: express.Request, res: express.Response): BluebirdP
|
||||||
logger.debug("1st factor: Mark successful authentication to regulator.");
|
logger.debug("1st factor: Mark successful authentication to regulator.");
|
||||||
regulator.mark(username, true);
|
regulator.mark(username, true);
|
||||||
|
|
||||||
logger.debug("1st factor: Redirect to %s", Endpoint.SECOND_FACTOR_GET);
|
res.status(204);
|
||||||
res.redirect(Endpoint.SECOND_FACTOR_GET);
|
res.send();
|
||||||
return BluebirdPromise.resolve();
|
return BluebirdPromise.resolve();
|
||||||
})
|
})
|
||||||
.catch(exceptions.LdapSearchError, ErrorReplies.replyWithError500(res, logger))
|
.catch(exceptions.LdapSearchError, ErrorReplies.replyWithError500(res, logger))
|
||||||
|
|
|
@ -8,7 +8,7 @@ import AuthenticationSession = require("../../AuthenticationSession");
|
||||||
|
|
||||||
export default function (req: express.Request, res: express.Response) {
|
export default function (req: express.Request, res: express.Response) {
|
||||||
const authSession = AuthenticationSession.get(req);
|
const authSession = AuthenticationSession.get(req);
|
||||||
const redirectUrl = authSession.redirect || Endpoints.FIRST_FACTOR_GET;
|
const redirectUrl = req.query.redirect || Endpoints.FIRST_FACTOR_GET;
|
||||||
res.json({
|
res.json({
|
||||||
redirection_url: redirectUrl
|
redirection_url: redirectUrl
|
||||||
});
|
});
|
||||||
|
|
|
@ -17,7 +17,7 @@ function verify_filter(req: express.Request, res: express.Response): BluebirdPro
|
||||||
const authSession = AuthenticationSession.get(req);
|
const authSession = AuthenticationSession.get(req);
|
||||||
|
|
||||||
logger.debug("Verify: headers are %s", JSON.stringify(req.headers));
|
logger.debug("Verify: headers are %s", JSON.stringify(req.headers));
|
||||||
authSession.redirect = "https://" + req.headers["host"] + req.headers["x-original-uri"];
|
res.set("Redirect", encodeURIComponent("https://" + req.headers["host"] + req.headers["x-original-uri"]));
|
||||||
|
|
||||||
return AuthenticationValidator.validate(req)
|
return AuthenticationValidator.validate(req)
|
||||||
.then(function () {
|
.then(function () {
|
||||||
|
|
|
@ -18,14 +18,14 @@ Feature: User validate first factor
|
||||||
Given I visit "https://auth.test.local:8080/"
|
Given I visit "https://auth.test.local:8080/"
|
||||||
And I login with user "john" and password "password"
|
And I login with user "john" and password "password"
|
||||||
And I register a TOTP secret called "Sec0"
|
And I register a TOTP secret called "Sec0"
|
||||||
When I visit "https://secret.test.local:8080/secret.html" and get redirected "https://auth.test.local:8080/"
|
When I visit "https://secret.test.local:8080/secret.html" and get redirected "https://auth.test.local:8080/?redirect=https%3A%2F%2Fsecret.test.local%3A8080%2Fsecret.html"
|
||||||
And I login with user "john" and password "password"
|
And I login with user "john" and password "password"
|
||||||
And I use "Sec0" as TOTP token handle
|
And I use "Sec0" as TOTP token handle
|
||||||
And I click on "TOTP"
|
And I click on "TOTP"
|
||||||
Then I'm redirected to "https://secret.test.local:8080/secret.html"
|
Then I'm redirected to "https://secret.test.local:8080/secret.html"
|
||||||
|
|
||||||
Scenario: User fails TOTP second factor
|
Scenario: User fails TOTP second factor
|
||||||
When I visit "https://secret.test.local:8080/secret.html" and get redirected "https://auth.test.local:8080/"
|
When I visit "https://secret.test.local:8080/secret.html" and get redirected "https://auth.test.local:8080/?redirect=https%3A%2F%2Fsecret.test.local%3A8080%2Fsecret.html"
|
||||||
And I login with user "john" and password "password"
|
And I login with user "john" and password "password"
|
||||||
And I use "BADTOKEN" as TOTP token
|
And I use "BADTOKEN" as TOTP token
|
||||||
And I click on "TOTP"
|
And I click on "TOTP"
|
||||||
|
@ -50,4 +50,4 @@ Feature: User validate first factor
|
||||||
And I login with user "john" and password "password"
|
And I login with user "john" and password "password"
|
||||||
And I use "Sec0" as TOTP token handle
|
And I use "Sec0" as TOTP token handle
|
||||||
When I visit "https://auth.test.local:8080/logout?redirect=https://www.google.fr"
|
When I visit "https://auth.test.local:8080/logout?redirect=https://www.google.fr"
|
||||||
Then I'm redirected to "https://www.google.fr"
|
Then I'm redirected to "https://www.google.fr"
|
||||||
|
|
|
@ -20,4 +20,28 @@ Feature: User is correctly redirected
|
||||||
Scenario: User Harry does not have access to https://secret.test.local:8080/secret.html and thus he must get an error 401
|
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"
|
When I register TOTP and login with user "harry" and password "password"
|
||||||
And I visit "https://secret.test.local:8080/secret.html"
|
And I visit "https://secret.test.local:8080/secret.html"
|
||||||
Then I get an error 403
|
Then I get an error 403
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Scenario: Redirection URL is propagated from restricted page to first factor
|
||||||
|
When I visit "https://public.test.local:8080/secret.html"
|
||||||
|
Then I'm redirected to "https://auth.test.local:8080/?redirect=https%3A%2F%2Fpublic.test.local%3A8080%2Fsecret.html"
|
||||||
|
|
||||||
|
Scenario: Redirection URL is propagated from first factor to second factor
|
||||||
|
Given I visit "https://auth.test.local:8080/"
|
||||||
|
And I login with user "john" and password "password"
|
||||||
|
And I register a TOTP secret called "Sec0"
|
||||||
|
When I visit "https://public.test.local:8080/secret.html"
|
||||||
|
And I login with user "john" and password "password"
|
||||||
|
Then I'm redirected to "https://auth.test.local:8080/secondfactor?redirect=https%3A%2F%2Fpublic.test.local%3A8080%2Fsecret.html"
|
||||||
|
|
||||||
|
Scenario: Redirection URL is used to send user from second factor to target page
|
||||||
|
Given I visit "https://auth.test.local:8080/"
|
||||||
|
And I login with user "john" and password "password"
|
||||||
|
And I register a TOTP secret called "Sec0"
|
||||||
|
When I visit "https://public.test.local:8080/secret.html"
|
||||||
|
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"
|
|
@ -12,7 +12,7 @@ Feature: Authelia keeps user sessions despite the application restart
|
||||||
And I login with user "john" and password "password"
|
And I login with user "john" and password "password"
|
||||||
And I register a TOTP secret called "Sec0"
|
And I register a TOTP secret called "Sec0"
|
||||||
When the application restarts
|
When the application restarts
|
||||||
And I visit "https://secret.test.local:8080/secret.html" and get redirected "https://auth.test.local:8080/"
|
And I visit "https://secret.test.local:8080/secret.html" and get redirected "https://auth.test.local:8080/?redirect=https%3A%2F%2Fsecret.test.local%3A8080%2Fsecret.html"
|
||||||
And I login with user "john" and password "password"
|
And I login with user "john" and password "password"
|
||||||
And I use "Sec0" as TOTP token handle
|
And I use "Sec0" as TOTP token handle
|
||||||
And I click on "TOTP"
|
And I click on "TOTP"
|
||||||
|
|
|
@ -99,7 +99,7 @@ function CustomWorld() {
|
||||||
};
|
};
|
||||||
|
|
||||||
this.useTotpToken = function (totpSecret: string) {
|
this.useTotpToken = function (totpSecret: string) {
|
||||||
return that.driver.wait(seleniumWebdriver.until.elementLocated(seleniumWebdriver.By.className("register-totp")), 5000)
|
return that.driver.wait(seleniumWebdriver.until.elementLocated(seleniumWebdriver.By.id("token")), 5000)
|
||||||
.then(function () {
|
.then(function () {
|
||||||
return that.driver.findElement(seleniumWebdriver.By.id("token"))
|
return that.driver.findElement(seleniumWebdriver.By.id("token"))
|
||||||
.sendKeys(totpSecret);
|
.sendKeys(totpSecret);
|
||||||
|
|
|
@ -71,7 +71,7 @@ describe("test the first factor validation route", function () {
|
||||||
res = ExpressMock.ResponseMock();
|
res = ExpressMock.ResponseMock();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should redirect client to second factor page", function () {
|
it("should reply with 204 if success", function () {
|
||||||
(serverVariables.ldapAuthenticator as any).authenticate.withArgs("username", "password")
|
(serverVariables.ldapAuthenticator as any).authenticate.withArgs("username", "password")
|
||||||
.returns(BluebirdPromise.resolve({
|
.returns(BluebirdPromise.resolve({
|
||||||
emails: emails,
|
emails: emails,
|
||||||
|
@ -81,7 +81,8 @@ describe("test the first factor validation route", function () {
|
||||||
return FirstFactorPost.default(req as any, res as any)
|
return FirstFactorPost.default(req as any, res as any)
|
||||||
.then(function () {
|
.then(function () {
|
||||||
assert.equal("username", authSession.userid);
|
assert.equal("username", authSession.userid);
|
||||||
assert.equal(Endpoints.SECOND_FACTOR_GET, res.redirect.getCall(0).args[0]);
|
assert(res.send.calledOnce);
|
||||||
|
assert(res.status.calledWith(204));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue