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.pull/343/head
parent
e3b6410e79
commit
81207b49ad
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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 => {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
|
||||||
|
|
||||||
|
export default interface RedirectResponse {
|
||||||
|
redirect?: string;
|
||||||
|
error?: string;
|
||||||
|
}
|
|
@ -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'});
|
||||||
|
|
|
@ -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:
|
|
@ -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() {
|
|
@ -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");
|
||||||
|
});
|
||||||
|
}
|
|
@ -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);
|
||||||
});
|
});
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue