Redirect user when he has already validated some factors

Example 1: The user has validated first factor when accessing a service
protected by basic auth. When he tries to access another service protected
by second factor, he is redirected to second factor step to complete
authentication.

Example 2: The user has already validated second factor. When he access auth
service, he is redirected either to /loggedin page that displays an "already
logged in" page or to the URL provided in the "redirect" query parameter.
pull/131/head
Clement Michaud 2017-10-09 00:28:46 +02:00
parent c061dbfda4
commit 1cf4e57bb1
9 changed files with 110 additions and 9 deletions

View File

@ -29,6 +29,9 @@ import ResetPasswordRequestPost = require("./routes/password-reset/request/get")
import Error401Get = require("./routes/error/401/get"); import Error401Get = require("./routes/error/401/get");
import Error403Get = require("./routes/error/403/get"); import Error403Get = require("./routes/error/403/get");
import Error404Get = require("./routes/error/404/get"); import Error404Get = require("./routes/error/404/get");
import LoggedIn = require("./routes/loggedin/get");
import { ServerVariablesHandler } from "./ServerVariablesHandler"; import { ServerVariablesHandler } from "./ServerVariablesHandler";
import Endpoints = require("../../../shared/api"); import Endpoints = require("../../../shared/api");
@ -72,5 +75,6 @@ export class RestApi {
app.get(Endpoints.ERROR_401_GET, withLog(Error401Get.default)); app.get(Endpoints.ERROR_401_GET, withLog(Error401Get.default));
app.get(Endpoints.ERROR_403_GET, withLog(Error403Get.default)); app.get(Endpoints.ERROR_403_GET, withLog(Error403Get.default));
app.get(Endpoints.ERROR_404_GET, withLog(Error404Get.default)); app.get(Endpoints.ERROR_404_GET, withLog(Error404Get.default));
app.get(Endpoints.LOGGED_IN, withLog(LoggedIn.default));
} }
} }

View File

@ -6,11 +6,52 @@ import Endpoints = require("../../../../../shared/api");
import AuthenticationValidator = require("../../AuthenticationValidator"); import AuthenticationValidator = require("../../AuthenticationValidator");
import { ServerVariablesHandler } from "../../ServerVariablesHandler"; import { ServerVariablesHandler } from "../../ServerVariablesHandler";
import BluebirdPromise = require("bluebird"); import BluebirdPromise = require("bluebird");
import AuthenticationSession = require("../../AuthenticationSession");
import Constants = require("../../../../../shared/constants");
import Util = require("util");
export default function (req: express.Request, res: express.Response): BluebirdPromise<void> { function getRedirectParam(req: express.Request) {
return req.query[Constants.REDIRECT_QUERY_PARAM] != "undefined"
? req.query[Constants.REDIRECT_QUERY_PARAM]
: undefined;
}
function redirectToSecondFactorPage(req: express.Request, res: express.Response) {
const redirectUrl = getRedirectParam(req);
if (!redirectUrl)
res.redirect(Endpoints.SECOND_FACTOR_GET);
else
res.redirect(Util.format("%s?redirect=%s", Endpoints.SECOND_FACTOR_GET,
encodeURIComponent(redirectUrl)));
}
function redirectToService(req: express.Request, res: express.Response) {
const redirectUrl = getRedirectParam(req);
if (!redirectUrl)
res.redirect(Endpoints.LOGGED_IN);
else
res.redirect(redirectUrl);
}
function renderFirstFactor(res: express.Response) {
res.render("firstfactor", { res.render("firstfactor", {
first_factor_post_endpoint: Endpoints.FIRST_FACTOR_POST, first_factor_post_endpoint: Endpoints.FIRST_FACTOR_POST,
reset_password_request_endpoint: Endpoints.RESET_PASSWORD_REQUEST_GET reset_password_request_endpoint: Endpoints.RESET_PASSWORD_REQUEST_GET
}); });
return BluebirdPromise.resolve(); }
export default function (req: express.Request, res: express.Response): BluebirdPromise<void> {
return AuthenticationSession.get(req)
.then(function (authSession) {
if (authSession.first_factor) {
if (authSession.second_factor)
redirectToService(req, res);
else
redirectToSecondFactorPage(req, res);
return BluebirdPromise.resolve();
}
renderFirstFactor(res);
return BluebirdPromise.resolve();
});
} }

View File

@ -48,7 +48,10 @@ export default function (req: express.Request, res: express.Response): BluebirdP
JSON.stringify(groupsAndEmails)); JSON.stringify(groupsAndEmails));
authSession.userid = username; authSession.userid = username;
authSession.first_factor = true; authSession.first_factor = true;
const redirectUrl = req.query[Constants.REDIRECT_QUERY_PARAM]; const redirectUrl = req.query[Constants.REDIRECT_QUERY_PARAM] != "undefined"
// Fuck, don't know why it is a string!
? req.query[Constants.REDIRECT_QUERY_PARAM]
: undefined;
const emails: string[] = groupsAndEmails.emails; const emails: string[] = groupsAndEmails.emails;
const groups: string[] = groupsAndEmails.groups; const groups: string[] = groupsAndEmails.groups;

View File

@ -0,0 +1,8 @@
import Express = require("express");
import Endpoints = require("../../../../../shared/api");
export default function(req: Express.Request, res: Express.Response) {
res.render("already-logged-in", {
logout_endpoint: Endpoints.LOGOUT_GET
});
}

View File

@ -4,15 +4,24 @@ import Endpoints = require("../../../../../shared/api");
import FirstFactorBlocker = require("../FirstFactorBlocker"); import FirstFactorBlocker = require("../FirstFactorBlocker");
import BluebirdPromise = require("bluebird"); import BluebirdPromise = require("bluebird");
import { ServerVariablesHandler } from "../../ServerVariablesHandler"; import { ServerVariablesHandler } from "../../ServerVariablesHandler";
import AuthenticationSession = require("../../AuthenticationSession");
const TEMPLATE_NAME = "secondfactor"; const TEMPLATE_NAME = "secondfactor";
export default FirstFactorBlocker.default(handler); export default FirstFactorBlocker.default(handler);
function handler(req: Express.Request, res: Express.Response): BluebirdPromise<void> { function handler(req: Express.Request, res: Express.Response): BluebirdPromise<void> {
return AuthenticationSession.get(req)
.then(function (authSession) {
if (authSession.first_factor && authSession.second_factor) {
res.redirect(Endpoints.LOGGED_IN);
return BluebirdPromise.resolve();
}
res.render(TEMPLATE_NAME, { res.render(TEMPLATE_NAME, {
totp_identity_start_endpoint: Endpoints.SECOND_FACTOR_TOTP_IDENTITY_START_GET, totp_identity_start_endpoint: Endpoints.SECOND_FACTOR_TOTP_IDENTITY_START_GET,
u2f_identity_start_endpoint: Endpoints.SECOND_FACTOR_U2F_IDENTITY_START_GET u2f_identity_start_endpoint: Endpoints.SECOND_FACTOR_U2F_IDENTITY_START_GET
}); });
return BluebirdPromise.resolve(); return BluebirdPromise.resolve();
});
} }

View File

@ -1,9 +1,11 @@
export class DomainExtractor { export class DomainExtractor {
static fromUrl(url: string): string { static fromUrl(url: string): string {
if (!url) return "";
return url.match(/https?:\/\/([^\/:]+).*/)[1]; return url.match(/https?:\/\/([^\/:]+).*/)[1];
} }
static fromHostHeader(host: string): string { static fromHostHeader(host: string): string {
if (!host) return "";
return host.split(":")[0]; return host.split(":")[0];
} }
} }

View File

@ -297,3 +297,5 @@ export const LOGOUT_GET = "/logout";
export const ERROR_401_GET = "/error/401"; export const ERROR_401_GET = "/error/401";
export const ERROR_403_GET = "/error/403"; export const ERROR_403_GET = "/error/403";
export const ERROR_404_GET = "/error/404"; export const ERROR_404_GET = "/error/404";
export const LOGGED_IN = "/loggedin";

View File

@ -0,0 +1,32 @@
Feature: User is redirected when factors are already validated
@need-registered-user-john
Scenario: User has validated first factor and tries to access service protected by second factor. He is then redirect to second factor step.
When I visit "https://basicauth.test.local:8080/secret.html"
And I'm redirected to "https://auth.test.local:8080/?redirect=https%3A%2F%2Fbasicauth.test.local%3A8080%2Fsecret.html"
And I login with user "john" and password "password"
And I'm redirected to "https://basicauth.test.local:8080/secret.html"
And I visit "https://public.test.local:8080/secret.html"
Then I'm redirected to "https://auth.test.local:8080/secondfactor?redirect=https%3A%2F%2Fpublic.test.local%3A8080%2Fsecret.html"
@need-registered-user-john
Scenario: User who has validated second factor and access auth portal should be redirected to "Already logged in page"
When I visit "https://public.test.local:8080/secret.html"
And I'm redirected to "https://auth.test.local:8080/?redirect=https%3A%2F%2Fpublic.test.local%3A8080%2Fsecret.html"
And I login with user "john" and password "password"
And I use "REGISTERED" as TOTP token handle
And I click on "TOTP"
And I'm redirected to "https://public.test.local:8080/secret.html"
And I visit "https://auth.test.local:8080"
Then I'm redirected to "https://auth.test.local:8080/loggedin"
@need-registered-user-john
Scenario: User who has validated second factor and access auth portal with rediction param should be redirected to that URL
When I visit "https://public.test.local:8080/secret.html"
And I'm redirected to "https://auth.test.local:8080/?redirect=https%3A%2F%2Fpublic.test.local%3A8080%2Fsecret.html"
And I login with user "john" and password "password"
And I use "REGISTERED" as TOTP token handle
And I click on "TOTP"
And I'm redirected to "https://public.test.local:8080/secret.html"
And I visit "https://auth.test.local:8080?redirect=https://public.test.local:8080/secret.html"
Then I'm redirected to "https://public.test.local:8080/secret.html"

View File

@ -1,4 +1,4 @@
Feature: User validate first factor Feature: Authentication scenarii
Scenario: User succeeds first factor Scenario: User succeeds first factor
Given I visit "https://auth.test.local:8080/" Given I visit "https://auth.test.local:8080/"