Display only available 2FA methods.
For instance Duo Push Notification method is not displayed if the API is not configured.pull/342/head
parent
d09a307ff8
commit
a717b965c1
|
@ -0,0 +1,13 @@
|
|||
import { Dispatch } from "redux";
|
||||
import AutheliaService from "../services/AutheliaService";
|
||||
import { getAvailbleMethods, getAvailbleMethodsSuccess, getAvailbleMethodsFailure } from "../reducers/Portal/SecondFactor/actions";
|
||||
|
||||
export default async function(dispatch: Dispatch) {
|
||||
dispatch(getAvailbleMethods());
|
||||
try {
|
||||
const methods = await AutheliaService.getAvailable2faMethods();
|
||||
dispatch(getAvailbleMethodsSuccess(methods));
|
||||
} catch (err) {
|
||||
dispatch(getAvailbleMethodsFailure(err.message))
|
||||
}
|
||||
}
|
|
@ -3,9 +3,9 @@ import styles from '../../assets/scss/components/SecondFactorForm/SecondFactorFo
|
|||
import Method2FA from '../../types/Method2FA';
|
||||
import SecondFactorTOTP from '../../containers/components/SecondFactorTOTP/SecondFactorTOTP';
|
||||
import SecondFactorU2F from '../../containers/components/SecondFactorU2F/SecondFactorU2F';
|
||||
import { Button } from '@material/react-button';
|
||||
import classnames from 'classnames';
|
||||
import SecondFactorDuoPush from '../../containers/components/SecondFactorDuoPush/SecondFactorDuoPush';
|
||||
import UseAnotherMethod from '../../containers/components/UseAnotherMethod/UseAnotherMethod';
|
||||
|
||||
export interface OwnProps {
|
||||
username: string;
|
||||
|
@ -20,9 +20,6 @@ export interface StateProps {
|
|||
export interface DispatchProps {
|
||||
onInit: () => void;
|
||||
onLogoutClicked: () => void;
|
||||
onOneTimePasswordMethodClicked: () => void;
|
||||
onSecurityKeyMethodClicked: () => void;
|
||||
onDuoPushMethodClicked: () => void;
|
||||
onUseAnotherMethodClicked: () => void;
|
||||
}
|
||||
|
||||
|
@ -55,19 +52,6 @@ class SecondFactorForm extends Component<Props> {
|
|||
);
|
||||
}
|
||||
|
||||
private renderUseAnotherMethod() {
|
||||
return (
|
||||
<div className={classnames('use-another-method-view')}>
|
||||
<div>Choose a method</div>
|
||||
<div className={styles.buttonsContainer}>
|
||||
<Button raised onClick={this.props.onOneTimePasswordMethodClicked}>One-Time Password</Button>
|
||||
<Button raised onClick={this.props.onSecurityKeyMethodClicked}>Security Key (U2F)</Button>
|
||||
<Button raised onClick={this.props.onDuoPushMethodClicked}>Duo Push Notification</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private renderUseAnotherMethodLink() {
|
||||
return (
|
||||
<div className={styles.anotherMethodLink}>
|
||||
|
@ -88,7 +72,7 @@ class SecondFactorForm extends Component<Props> {
|
|||
</div>
|
||||
</div>
|
||||
<div className={styles.body}>
|
||||
{(this.props.useAnotherMethod) ? this.renderUseAnotherMethod() : this.renderMethod()}
|
||||
{(this.props.useAnotherMethod) ? <UseAnotherMethod/> : this.renderMethod()}
|
||||
</div>
|
||||
{(this.props.useAnotherMethod) ? null : this.renderUseAnotherMethodLink()}
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
import React, { Component } from 'react';
|
||||
import styles from '../../assets/scss/components/SecondFactorForm/SecondFactorForm.module.scss';
|
||||
import Method2FA from '../../types/Method2FA';
|
||||
import { Button } from '@material/react-button';
|
||||
import classnames from 'classnames';
|
||||
|
||||
export interface OwnProps {}
|
||||
|
||||
export interface StateProps {
|
||||
availableMethods: Method2FA[] | null;
|
||||
}
|
||||
|
||||
export interface DispatchProps {
|
||||
onInit: () => void;
|
||||
onOneTimePasswordMethodClicked: () => void;
|
||||
onSecurityKeyMethodClicked: () => void;
|
||||
onDuoPushMethodClicked: () => void;
|
||||
}
|
||||
|
||||
export type Props = OwnProps & StateProps & DispatchProps;
|
||||
|
||||
interface MethodDescription {
|
||||
name: string;
|
||||
onClicked: () => void;
|
||||
key: Method2FA;
|
||||
}
|
||||
|
||||
class UseAnotherMethod extends Component<Props> {
|
||||
componentDidMount() {
|
||||
this.props.onInit();
|
||||
}
|
||||
|
||||
render() {
|
||||
const methods: MethodDescription[] = [
|
||||
{
|
||||
name: "One-Time Password",
|
||||
onClicked: this.props.onOneTimePasswordMethodClicked,
|
||||
key: "totp"
|
||||
},
|
||||
{
|
||||
name: "Security Key (U2F)",
|
||||
onClicked: this.props.onSecurityKeyMethodClicked,
|
||||
key: "u2f"
|
||||
},
|
||||
{
|
||||
name: "Duo Push Notification",
|
||||
onClicked: this.props.onDuoPushMethodClicked,
|
||||
key: "duo_push"
|
||||
}
|
||||
];
|
||||
const methodsComponents = methods
|
||||
.filter(m => this.props.availableMethods && this.props.availableMethods.includes(m.key))
|
||||
.map(m => <Button raised onClick={m.onClicked} key={m.key}>{m.name}</Button>);
|
||||
|
||||
return (
|
||||
<div className={classnames('use-another-method-view')}>
|
||||
<div>Choose a method</div>
|
||||
<div className={styles.buttonsContainer}>
|
||||
{methodsComponents}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default UseAnotherMethod;
|
|
@ -29,9 +29,6 @@ const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => {
|
|||
return {
|
||||
onInit: () => FetchPrefered2faMethod(dispatch),
|
||||
onLogoutClicked: () => LogoutBehavior(dispatch),
|
||||
onOneTimePasswordMethodClicked: () => storeMethod(dispatch, 'totp'),
|
||||
onSecurityKeyMethodClicked: () => storeMethod(dispatch, 'u2f'),
|
||||
onDuoPushMethodClicked: () => storeMethod(dispatch, "duo_push"),
|
||||
onUseAnotherMethodClicked: () => dispatch(setUseAnotherMethod(true)),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { Dispatch } from 'redux';
|
||||
import { RootState } from '../../../reducers';
|
||||
import SetPrefered2faMethod from '../../../behaviors/SetPrefered2faMethod';
|
||||
import { getPreferedMethodSuccess, setUseAnotherMethod } from '../../../reducers/Portal/SecondFactor/actions';
|
||||
import Method2FA from '../../../types/Method2FA';
|
||||
import UseAnotherMethod, {StateProps, DispatchProps} from '../../../components/UseAnotherMethod/UseAnotherMethod';
|
||||
import GetAvailable2faMethods from '../../../behaviors/GetAvailable2faMethods';
|
||||
|
||||
const mapStateToProps = (state: RootState): StateProps => ({
|
||||
availableMethods: state.secondFactor.getAvailableMethodResponse,
|
||||
})
|
||||
|
||||
async function storeMethod(dispatch: Dispatch, method: Method2FA) {
|
||||
// display the new option
|
||||
dispatch(getPreferedMethodSuccess(method));
|
||||
dispatch(setUseAnotherMethod(false));
|
||||
|
||||
// And save the method for next time.
|
||||
await SetPrefered2faMethod(dispatch, method);
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => {
|
||||
return {
|
||||
onInit: () => GetAvailable2faMethods(dispatch),
|
||||
onOneTimePasswordMethodClicked: () => storeMethod(dispatch, 'totp'),
|
||||
onSecurityKeyMethodClicked: () => storeMethod(dispatch, 'u2f'),
|
||||
onDuoPushMethodClicked: () => storeMethod(dispatch, "duo_push"),
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(UseAnotherMethod);
|
|
@ -19,7 +19,10 @@ import {
|
|||
SET_USE_ANOTHER_METHOD,
|
||||
TRIGGER_DUO_PUSH_AUTH,
|
||||
TRIGGER_DUO_PUSH_AUTH_SUCCESS,
|
||||
TRIGGER_DUO_PUSH_AUTH_FAILURE
|
||||
TRIGGER_DUO_PUSH_AUTH_FAILURE,
|
||||
GET_AVAILABLE_METHODS,
|
||||
GET_AVAILABLE_METHODS_SUCCESS,
|
||||
GET_AVAILABLE_METHODS_FAILURE
|
||||
} from "../../constants";
|
||||
import Method2FA from "../../../types/Method2FA";
|
||||
|
||||
|
@ -31,6 +34,16 @@ export const setUseAnotherMethod = createAction(SET_USE_ANOTHER_METHOD, resolve
|
|||
return (useAnotherMethod: boolean) => resolve(useAnotherMethod);
|
||||
});
|
||||
|
||||
|
||||
export const getAvailbleMethods = createAction(GET_AVAILABLE_METHODS);
|
||||
export const getAvailbleMethodsSuccess = createAction(GET_AVAILABLE_METHODS_SUCCESS, resolve => {
|
||||
return (methods: Method2FA[]) => resolve(methods);
|
||||
});
|
||||
export const getAvailbleMethodsFailure = createAction(GET_AVAILABLE_METHODS_FAILURE, resolve => {
|
||||
return (err: string) => resolve(err);
|
||||
});
|
||||
|
||||
|
||||
export const getPreferedMethod = createAction(GET_PREFERED_METHOD);
|
||||
export const getPreferedMethodSuccess = createAction(GET_PREFERED_METHOD_SUCCESS, resolve => {
|
||||
return (method: Method2FA) => resolve(method);
|
||||
|
@ -39,30 +52,35 @@ export const getPreferedMethodFailure = createAction(GET_PREFERED_METHOD_FAILURE
|
|||
return (err: string) => resolve(err);
|
||||
});
|
||||
|
||||
|
||||
export const setPreferedMethod = createAction(SET_PREFERED_METHOD);
|
||||
export const setPreferedMethodSuccess = createAction(SET_PREFERED_METHOD_SUCCESS);
|
||||
export const setPreferedMethodFailure = createAction(SET_PREFERED_METHOD_FAILURE, resolve => {
|
||||
return (err: string) => resolve(err);
|
||||
})
|
||||
|
||||
|
||||
export const securityKeySign = createAction(SECURITY_KEY_SIGN);
|
||||
export const securityKeySignSuccess = createAction(SECURITY_KEY_SIGN_SUCCESS);
|
||||
export const securityKeySignFailure = createAction(SECURITY_KEY_SIGN_FAILURE, resolve => {
|
||||
return (error: string) => resolve(error);
|
||||
});
|
||||
|
||||
|
||||
export const oneTimePasswordVerification = createAction(ONE_TIME_PASSWORD_VERIFICATION_REQUEST);
|
||||
export const oneTimePasswordVerificationSuccess = createAction(ONE_TIME_PASSWORD_VERIFICATION_SUCCESS);
|
||||
export const oneTimePasswordVerificationFailure = createAction(ONE_TIME_PASSWORD_VERIFICATION_FAILURE, resolve => {
|
||||
return (err: string) => resolve(err);
|
||||
});
|
||||
|
||||
|
||||
export const triggerDuoPushAuth = createAction(TRIGGER_DUO_PUSH_AUTH);
|
||||
export const triggerDuoPushAuthSuccess = createAction(TRIGGER_DUO_PUSH_AUTH_SUCCESS);
|
||||
export const triggerDuoPushAuthFailure = createAction(TRIGGER_DUO_PUSH_AUTH_FAILURE, resolve => {
|
||||
return (err: string) => resolve(err);
|
||||
});
|
||||
|
||||
|
||||
export const logout = createAction(LOGOUT_REQUEST);
|
||||
export const logoutSuccess = createAction(LOGOUT_SUCCESS);
|
||||
export const logoutFailure = createAction(LOGOUT_FAILURE, resolve => {
|
||||
|
|
|
@ -12,6 +12,10 @@ interface SecondFactorState {
|
|||
|
||||
userAnotherMethod: boolean;
|
||||
|
||||
getAvailableMethodsLoading: boolean;
|
||||
getAvailableMethodResponse: Method2FA[] | null;
|
||||
getAvailableMethodError: string | null;
|
||||
|
||||
preferedMethodLoading: boolean;
|
||||
preferedMethodError: string | null;
|
||||
preferedMethod: Method2FA | null;
|
||||
|
@ -40,6 +44,10 @@ const secondFactorInitialState: SecondFactorState = {
|
|||
|
||||
userAnotherMethod: false,
|
||||
|
||||
getAvailableMethodsLoading: false,
|
||||
getAvailableMethodResponse: null,
|
||||
getAvailableMethodError: null,
|
||||
|
||||
preferedMethod: null,
|
||||
preferedMethodError: null,
|
||||
preferedMethodLoading: false,
|
||||
|
@ -190,6 +198,26 @@ export default (state = secondFactorInitialState, action: SecondFactorAction): S
|
|||
duoPushVerificationLoading: false,
|
||||
duoPushVerificationError: action.payload,
|
||||
}
|
||||
|
||||
case getType(Actions.getPreferedMethod):
|
||||
return {
|
||||
...state,
|
||||
getAvailableMethodsLoading: true,
|
||||
getAvailableMethodResponse: null,
|
||||
getAvailableMethodError: null,
|
||||
}
|
||||
case getType(Actions.getAvailbleMethodsSuccess):
|
||||
return {
|
||||
...state,
|
||||
getAvailableMethodsLoading: false,
|
||||
getAvailableMethodResponse: action.payload,
|
||||
}
|
||||
case getType(Actions.getAvailbleMethodsFailure):
|
||||
return {
|
||||
...state,
|
||||
getAvailableMethodsLoading: false,
|
||||
getAvailableMethodError: action.payload,
|
||||
}
|
||||
}
|
||||
return state;
|
||||
}
|
|
@ -12,6 +12,10 @@ export const AUTHENTICATE_FAILURE = '@portal/authenticate_failure';
|
|||
export const SET_SECURITY_KEY_SUPPORTED = '@portal/second_factor/set_security_key_supported';
|
||||
export const SET_USE_ANOTHER_METHOD = '@portal/second_factor/set_use_another_method';
|
||||
|
||||
export const GET_AVAILABLE_METHODS = '@portal/second_factor/get_available_methods';
|
||||
export const GET_AVAILABLE_METHODS_SUCCESS = '@portal/second_factor/get_available_methods_success';
|
||||
export const GET_AVAILABLE_METHODS_FAILURE = '@portal/second_factor/get_available_methods_failure';
|
||||
|
||||
export const GET_PREFERED_METHOD = '@portal/second_factor/get_prefered_method';
|
||||
export const GET_PREFERED_METHOD_SUCCESS = '@portal/second_factor/get_prefered_method_success';
|
||||
export const GET_PREFERED_METHOD_FAILURE = '@portal/second_factor/get_prefered_method_failure';
|
||||
|
|
|
@ -171,6 +171,10 @@ class AutheliaService {
|
|||
body: JSON.stringify({method})
|
||||
});
|
||||
}
|
||||
|
||||
static async getAvailable2faMethods(): Promise<Method2FA[]> {
|
||||
return await this.fetchSafeJson('/api/secondfactor/available');
|
||||
}
|
||||
}
|
||||
|
||||
export default AutheliaService;
|
|
@ -0,0 +1,36 @@
|
|||
import * as Express from "express";
|
||||
import { ServerVariables } from "../../../ServerVariables";
|
||||
import { ServerVariablesMockBuilder } from "../../../ServerVariablesMockBuilder.spec";
|
||||
import * as ExpressMock from "../../../stubs/express.spec";
|
||||
import Get from "./Get";
|
||||
import * as Assert from "assert";
|
||||
|
||||
|
||||
describe("routes/secondfactor/duo-push/Post", function() {
|
||||
let vars: ServerVariables;
|
||||
let req: Express.Request;
|
||||
let res: ExpressMock.ResponseMock;
|
||||
|
||||
beforeEach(function() {
|
||||
const sv = ServerVariablesMockBuilder.build();
|
||||
vars = sv.variables;
|
||||
|
||||
req = ExpressMock.RequestMock();
|
||||
res = ExpressMock.ResponseMock();
|
||||
})
|
||||
|
||||
it("should return default available methods", async function() {
|
||||
await Get(vars)(req, res as any);
|
||||
Assert(res.json.calledWith(["u2f", "totp"]));
|
||||
});
|
||||
|
||||
it("should return duo as an available method", async function() {
|
||||
vars.config.duo_api = {
|
||||
hostname: "example.com",
|
||||
integration_key: "ABCDEFG",
|
||||
secret_key: "ekjfzelfjz",
|
||||
}
|
||||
await Get(vars)(req, res as any);
|
||||
Assert(res.json.calledWith(["u2f", "totp", "duo_push"]));
|
||||
});
|
||||
});
|
|
@ -0,0 +1,14 @@
|
|||
import * as Express from "express";
|
||||
import { ServerVariables } from "../../../ServerVariables";
|
||||
import Method2FA from "../../../../../../shared/Method2FA";
|
||||
|
||||
|
||||
export default function(vars: ServerVariables) {
|
||||
return async function(_: Express.Request, res: Express.Response) {
|
||||
const availableMethods: Method2FA[] = ["u2f", "totp"];
|
||||
if (vars.config.duo_api) {
|
||||
availableMethods.push("duo_push");
|
||||
}
|
||||
res.json(availableMethods);
|
||||
};
|
||||
}
|
|
@ -2,6 +2,7 @@ import * as Express from "express";
|
|||
import SecondFactorPreferencesGet from "../routes/secondfactor/preferences/Get";
|
||||
import SecondFactorPreferencesPost from "../routes/secondfactor/preferences/Post";
|
||||
import SecondFactorDuoPushPost from "../routes/secondfactor/duo-push/Post";
|
||||
import SecondFactorAvailableGet from "../routes/secondfactor/available/Get";
|
||||
|
||||
import FirstFactorPost = require("../routes/firstfactor/post");
|
||||
import LogoutPost from "../routes/logout/post";
|
||||
|
@ -109,6 +110,10 @@ export class RestApi {
|
|||
SecondFactorDuoPushPost(vars));
|
||||
}
|
||||
|
||||
app.get(Endpoints.SECOND_FACTOR_AVAILABLE_GET,
|
||||
RequireValidatedFirstFactor.middleware(vars.logger),
|
||||
SecondFactorAvailableGet(vars));
|
||||
|
||||
setupTotp(app, vars);
|
||||
setupU2f(app, vars);
|
||||
setupResetPassword(app, vars);
|
||||
|
|
|
@ -197,6 +197,16 @@ export const SECOND_FACTOR_PREFERENCES_GET = "/api/secondfactor/preferences";
|
|||
*/
|
||||
export const SECOND_FACTOR_PREFERENCES_POST = "/api/secondfactor/preferences";
|
||||
|
||||
/**
|
||||
* @api {post} /api/secondfactor/available List the available methods.
|
||||
* @apiName GetAvailableMethods
|
||||
* @apiGroup 2FA
|
||||
* @apiVersion 1.0.0
|
||||
*
|
||||
* @apiDescription Get the available 2FA methods.
|
||||
*/
|
||||
export const SECOND_FACTOR_AVAILABLE_GET = "/api/secondfactor/available";
|
||||
|
||||
|
||||
/**
|
||||
* @api {post} /api/password-reset Set new password
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
import SeleniumWebDriver, { WebDriver } from "selenium-webdriver";
|
||||
import VerifyElementDoesNotExist from "./VerifyElementDoesNotExist";
|
||||
|
||||
/**
|
||||
* Verify that an element does not exist.
|
||||
*
|
||||
* @param driver The selenium driver
|
||||
* @param content The content of the button to select.
|
||||
*/
|
||||
export default async function(driver: WebDriver, content: string) {
|
||||
await VerifyElementDoesNotExist(driver, SeleniumWebDriver.By.xpath("//button[text()='" + content + "']"))
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
import SeleniumWebDriver, { WebDriver } from "selenium-webdriver";
|
||||
import VerifyElementExists from "./VerifyElementExists";
|
||||
|
||||
/**
|
||||
* Verify if a button with given content exists in the DOM.
|
||||
* @param driver The selenium web driver.
|
||||
* @param content The content of the button to find in the DOM.
|
||||
*/
|
||||
export default async function(driver: WebDriver, content: string) {
|
||||
await VerifyElementExists(driver, SeleniumWebDriver.By.xpath("//button[text()='" + content + "']"));
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
import SeleniumWebDriver, { WebDriver } from "selenium-webdriver";
|
||||
|
||||
/**
|
||||
*
|
||||
* @param driver The selenium web driver
|
||||
* @param locator The locator of the element to check it does not exist.
|
||||
*/
|
||||
export default async function(driver: WebDriver, locator: SeleniumWebDriver.Locator) {
|
||||
const els = await driver.findElements(locator);
|
||||
if (els.length > 0) {
|
||||
throw new Error("Element exists.");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
import SeleniumWebDriver, { WebDriver } from "selenium-webdriver";
|
||||
|
||||
/**
|
||||
*
|
||||
* @param driver The selenium web driver.
|
||||
* @param locator The locator of the element to find in the DOM.
|
||||
*/
|
||||
export default async function(driver: WebDriver, locator: SeleniumWebDriver.Locator) {
|
||||
const els = await driver.findElements(locator);
|
||||
if (els.length == 0) {
|
||||
throw new Error("Element does not exist.");
|
||||
}
|
||||
}
|
|
@ -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 VerifyElementDoesNotExist from "../../../helpers/assertions/VerifyElementDoesNotExist";
|
||||
import SeleniumWebDriver from "selenium-webdriver";
|
||||
import VerifyButtonDoesNotExist from "../../../helpers/assertions/VerifyButtonDoesNotExist";
|
||||
import VerifyButtonExists from "../../../helpers/assertions/VerifyButtonExists";
|
||||
|
||||
|
||||
|
||||
export default function() {
|
||||
before(async function() {
|
||||
this.driver = await StartDriver();
|
||||
});
|
||||
|
||||
after(async function() {
|
||||
await StopDriver(this.driver);
|
||||
});
|
||||
|
||||
// The Duo API is not configured so we should not see the method.
|
||||
it("should not display duo push notification method", async function() {
|
||||
await LoginAs(this.driver, "john", "password", "https://secure.example.com:8080/");
|
||||
await VerifyIsSecondFactorStage(this.driver);
|
||||
|
||||
await ClickOnLink(this.driver, 'Use another method');
|
||||
await VerifyIsUseAnotherMethodView(this.driver);
|
||||
await VerifyButtonExists(this.driver, "Security Key (U2F)");
|
||||
await VerifyButtonExists(this.driver, "One-Time Password");
|
||||
await VerifyButtonDoesNotExist(this.driver, "Duo Push Notification");
|
||||
});
|
||||
}
|
|
@ -11,6 +11,7 @@ import { exec } from '../../helpers/utils/exec';
|
|||
import TwoFactorAuthentication from "../../helpers/scenarii/TwoFactorAuthentication";
|
||||
import BypassPolicy from "./scenarii/BypassPolicy";
|
||||
import Prefered2faMethod from "./scenarii/Prefered2faMethod";
|
||||
import NoDuoPushOption from "./scenarii/NoDuoPushOption";
|
||||
|
||||
AutheliaSuite(__dirname, function() {
|
||||
this.timeout(10000);
|
||||
|
@ -30,4 +31,5 @@ AutheliaSuite(__dirname, function() {
|
|||
describe('Required two factor', RequiredTwoFactor);
|
||||
describe('Logout endpoint redirect to already logged in page', LogoutRedirectToAlreadyLoggedIn);
|
||||
describe('Prefered 2FA method', Prefered2faMethod);
|
||||
describe('No Duo Push method available', NoDuoPushOption);
|
||||
});
|
Loading…
Reference in New Issue