Fix failing second factor when no default redirection url set.

When no default redirection url was set, Duo push second factor was shown as
failing even if authentication was successful.
This commit is contained in:
Clement Michaud 2019-03-27 21:57:16 +01:00 committed by Clément Michaud
parent e3b6410e79
commit 81207b49ad
14 changed files with 78 additions and 43 deletions

View File

@ -5,14 +5,11 @@ import { triggerDuoPushAuth, triggerDuoPushAuthSuccess, triggerDuoPushAuthFailur
export default async function(dispatch: Dispatch, redirectionUrl: string | null) { export default async function(dispatch: Dispatch, redirectionUrl: string | null) {
dispatch(triggerDuoPushAuth()); dispatch(triggerDuoPushAuth());
try { try {
const res = await AutheliaService.triggerDuoPush(redirectionUrl); const body = await AutheliaService.triggerDuoPush(redirectionUrl);
const body = await res.json();
if ('error' in body) {
throw new Error(body['error']);
}
dispatch(triggerDuoPushAuthSuccess()); dispatch(triggerDuoPushAuthSuccess());
return body; return body;
} catch (err) { } catch (err) {
console.error(err);
dispatch(triggerDuoPushAuthFailure(err.message)) dispatch(triggerDuoPushAuthFailure(err.message))
} }
} }

View File

@ -4,6 +4,7 @@ import { Dispatch } from 'redux';
import SecondFactorDuoPush, { StateProps, OwnProps, DispatchProps } from '../../../components/SecondFactorDuoPush/SecondFactorDuoPush'; import SecondFactorDuoPush, { StateProps, OwnProps, DispatchProps } from '../../../components/SecondFactorDuoPush/SecondFactorDuoPush';
import FetchStateBehavior from '../../../behaviors/FetchStateBehavior'; import FetchStateBehavior from '../../../behaviors/FetchStateBehavior';
import TriggerDuoPushAuth from '../../../behaviors/TriggerDuoPushAuth'; import TriggerDuoPushAuth from '../../../behaviors/TriggerDuoPushAuth';
import RedirectionResponse from '../../../services/RedirectResponse';
const mapStateToProps = (state: RootState): StateProps => ({ const mapStateToProps = (state: RootState): StateProps => ({
@ -12,16 +13,16 @@ const mapStateToProps = (state: RootState): StateProps => ({
}); });
async function redirectIfPossible(body: any) { async function redirectIfPossible(body: any) {
if ('redirect' in body) { if (body && 'redirect' in body) {
window.location.href = body['redirect']; window.location.href = body['redirect'];
return true; return true;
} }
return false; return false;
} }
async function handleSuccess(dispatch: Dispatch, res: Response, duration?: number) { async function handleSuccess(dispatch: Dispatch, body: RedirectionResponse | undefined, duration?: number) {
async function handle() { async function handle() {
const redirected = await redirectIfPossible(res); const redirected = await redirectIfPossible(body);
if (!redirected) { if (!redirected) {
await FetchStateBehavior(dispatch); await FetchStateBehavior(dispatch);
} }
@ -35,9 +36,8 @@ async function handleSuccess(dispatch: Dispatch, res: Response, duration?: numbe
} }
async function triggerDuoPushAuth(dispatch: Dispatch, redirectionUrl: string | null) { async function triggerDuoPushAuth(dispatch: Dispatch, redirectionUrl: string | null) {
const res = await TriggerDuoPushAuth(dispatch, redirectionUrl); const body = await TriggerDuoPushAuth(dispatch, redirectionUrl);
if (!res) return; await handleSuccess(dispatch, body, 1000);
await handleSuccess(dispatch, res, 2000);
} }
const mapDispatchToProps = (dispatch: Dispatch, ownProps: OwnProps): DispatchProps => { const mapDispatchToProps = (dispatch: Dispatch, ownProps: OwnProps): DispatchProps => {

View File

@ -1,6 +1,7 @@
import RemoteState from "../views/AuthenticationView/RemoteState"; import RemoteState from "../views/AuthenticationView/RemoteState";
import u2fApi, { SignRequest } from "u2f-api"; import u2fApi, { SignRequest } from "u2f-api";
import Method2FA from "../types/Method2FA"; import Method2FA from "../types/Method2FA";
import RedirectResponse from "./RedirectResponse";
class AutheliaService { class AutheliaService {
static async fetchSafe(url: string, options?: RequestInit): Promise<Response> { static async fetchSafe(url: string, options?: RequestInit): Promise<Response> {
@ -113,8 +114,7 @@ class AutheliaService {
}) })
} }
static async triggerDuoPush(redirectionUrl: string | null): Promise<any> { static async triggerDuoPush(redirectionUrl: string | null): Promise<RedirectResponse | undefined> {
const headers: Record<string, string> = { const headers: Record<string, string> = {
'Accept': 'application/json', 'Accept': 'application/json',
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@ -122,10 +122,20 @@ class AutheliaService {
if (redirectionUrl) { if (redirectionUrl) {
headers['X-Target-Url'] = redirectionUrl; headers['X-Target-Url'] = redirectionUrl;
} }
return this.fetchSafe('/api/duo-push', { const res = await this.fetchSafe('/api/duo-push', {
method: 'POST', method: 'POST',
headers: headers, headers: headers,
}) });
if (res.status === 204) {
return;
}
const body = await res.json();
if ('error' in body) {
throw new Error(body['error']);
}
return body;
} }
static async initiatePasswordResetIdentityValidation(username: string) { static async initiatePasswordResetIdentityValidation(username: string) {

View File

@ -0,0 +1,6 @@
export default interface RedirectResponse {
redirect?: string;
error?: string;
}

View File

@ -15,7 +15,8 @@ class AutheliaServerFromDist implements AutheliaServerInterface {
async start() { async start() {
this.serverProcess = ChildProcess.spawn('./scripts/authelia-scripts serve ' + this.configPath, { this.serverProcess = ChildProcess.spawn('./scripts/authelia-scripts serve ' + this.configPath, {
shell: true shell: true,
env: process.env,
} as any); } as any);
if (this.logInFile) { if (this.logInFile) {
var logStream = fs.createWriteStream('/tmp/authelia-server.log', {flags: 'a'}); var logStream = fs.createWriteStream('/tmp/authelia-server.log', {flags: 'a'});

View File

@ -20,11 +20,19 @@ storage:
local: local:
path: /tmp/authelia/db path: /tmp/authelia/db
# The Duo Push Notification API configuration
duo_api:
hostname: duo.example.com
integration_key: ABCDEFGHIJKL
secret_key: abcdefghijklmnopqrstuvwxyz123456789
access_control: access_control:
default_policy: bypass default_policy: bypass
rules: rules:
- domain: 'public.example.com' - domain: 'public.example.com'
policy: bypass policy: bypass
- domain: 'secure.example.com'
policy: two_factor
notifier: notifier:
smtp: smtp:

View File

@ -3,12 +3,16 @@ import { exec } from "../../helpers/utils/exec";
import AutheliaServer from "../../helpers/context/AutheliaServer"; import AutheliaServer from "../../helpers/context/AutheliaServer";
import DockerEnvironment from "../../helpers/context/DockerEnvironment"; import DockerEnvironment from "../../helpers/context/DockerEnvironment";
// required to query duo-api over https
process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = 0 as any;
const autheliaServer = new AutheliaServer(__dirname + '/config.yml'); const autheliaServer = new AutheliaServer(__dirname + '/config.yml');
const dockerEnv = new DockerEnvironment([ const dockerEnv = new DockerEnvironment([
'docker-compose.yml', 'docker-compose.yml',
'example/compose/nginx/backend/docker-compose.yml', 'example/compose/nginx/backend/docker-compose.yml',
'example/compose/nginx/portal/docker-compose.yml', 'example/compose/nginx/portal/docker-compose.yml',
'example/compose/smtp/docker-compose.yml', 'example/compose/smtp/docker-compose.yml',
'example/compose/duo-api/docker-compose.yml',
]) ])
async function setup() { async function setup() {

View File

@ -0,0 +1,33 @@
import { StartDriver, StopDriver } from "../../../helpers/context/WithDriver";
import LoginAs from "../../../helpers/LoginAs";
import VerifyIsSecondFactorStage from "../../../helpers/assertions/VerifyIsSecondFactorStage";
import ClickOnLink from "../../../helpers/ClickOnLink";
import VerifyIsUseAnotherMethodView from "../../../helpers/assertions/VerifyIsUseAnotherMethodView";
import ClickOnButton from "../../../helpers/behaviors/ClickOnButton";
import Request from 'request-promise';
import VerifyIsAlreadyAuthenticatedStage from "../../../helpers/assertions/VerifyIsAlreadyAuthenticatedStage";
export default function() {
before(async function() {
this.driver = await StartDriver();
// Configure the fake API to return allowing response.
await Request('https://duo.example.com/allow', {method: 'POST'});
});
after(async function () {
await StopDriver(this.driver);
});
it('should send user to already authenticated page', async function() {
await LoginAs(this.driver, "john", "password");
await VerifyIsSecondFactorStage(this.driver);
await ClickOnLink(this.driver, 'Use another method');
await VerifyIsUseAnotherMethodView(this.driver);
await ClickOnButton(this.driver, 'Duo Push Notification');
await VerifyIsAlreadyAuthenticatedStage(this.driver, 10000);
await ClickOnButton(this.driver, "Logout");
});
}

View File

@ -1,6 +1,7 @@
import AutheliaSuite from "../../helpers/context/AutheliaSuite"; import AutheliaSuite from "../../helpers/context/AutheliaSuite";
import { exec } from '../../helpers/utils/exec'; import { exec } from '../../helpers/utils/exec';
import BypassPolicy from "./scenarii/BypassPolicy"; import BypassPolicy from "./scenarii/BypassPolicy";
import NoDefaultRedirectionUrl from "./scenarii/NoDefaultRedirectionUrl";
AutheliaSuite(__dirname, function() { AutheliaSuite(__dirname, function() {
this.timeout(10000); this.timeout(10000);
@ -10,4 +11,5 @@ AutheliaSuite(__dirname, function() {
}); });
describe('Bypass policy', BypassPolicy); describe('Bypass policy', BypassPolicy);
describe("No default redirection", NoDefaultRedirectionUrl);
}); });

View File

@ -87,18 +87,6 @@ regulation:
ban_time: 900 ban_time: 900
notifier: notifier:
# For testing purpose, notifications can be sent in a file
# filesystem:
# filename: /tmp/authelia/notification.txt
# Use your email account to send the notifications. You can use an app password.
# List of valid services can be found here: https://nodemailer.com/smtp/well-known/
## email:
## username: user@example.com
## password: yourpassword
## sender: admin@example.com
## service: gmail
# Use a SMTP server for sending notifications # Use a SMTP server for sending notifications
smtp: smtp:
username: test username: test

View File

@ -6,8 +6,6 @@ port: 9091
logs_level: debug logs_level: debug
default_redirection_url: https://home.example.com:8080/
authentication_backend: authentication_backend:
file: file:
path: ./test/suites/basic/users_database.test.yml path: ./test/suites/basic/users_database.test.yml
@ -93,18 +91,6 @@ regulation:
ban_time: 900 ban_time: 900
notifier: notifier:
# For testing purpose, notifications can be sent in a file
# filesystem:
# filename: /tmp/authelia/notification.txt
# Use your email account to send the notifications. You can use an app password.
# List of valid services can be found here: https://nodemailer.com/smtp/well-known/
## email:
## username: user@example.com
## password: yourpassword
## sender: admin@example.com
## service: gmail
# Use a SMTP server for sending notifications # Use a SMTP server for sending notifications
smtp: smtp:
username: test username: test