mirror of
https://github.com/0rangebananaspy/authelia.git
synced 2024-09-14 22:47:21 +07:00
Merge pull request #287 from clems4ever/keep-logged-in
Add a "keep me logged in" checkbox.
This commit is contained in:
commit
bfaaf6214f
20
Dockerfile.dev
Normal file
20
Dockerfile.dev
Normal file
|
@ -0,0 +1,20 @@
|
|||
FROM node:8.7.0-alpine
|
||||
|
||||
WORKDIR /usr/src
|
||||
|
||||
COPY package.json /usr/src/package.json
|
||||
|
||||
RUN apk --update add --no-cache --virtual \
|
||||
.build-deps make g++ python && \
|
||||
npm install && \
|
||||
apk del .build-deps
|
||||
|
||||
COPY dist/server /usr/src/server
|
||||
COPY dist/shared /usr/src/shared
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
VOLUME /etc/authelia
|
||||
VOLUME /var/lib/authelia
|
||||
|
||||
CMD ["node", "server/src/index.js", "/etc/authelia/config.yml"]
|
|
@ -53,6 +53,10 @@ module.exports = function (grunt) {
|
|||
cmd: "./node_modules/.bin/mocha",
|
||||
args: ['--colors', '--require', 'ts-node/register', 'test/minimal-config/**/*.ts']
|
||||
},
|
||||
"test-inactivity": {
|
||||
cmd: "./node_modules/.bin/mocha",
|
||||
args: ['--colors', '--require', 'ts-node/register', 'test/inactivity/**/*.ts']
|
||||
},
|
||||
"docker-build": {
|
||||
cmd: "docker",
|
||||
args: ['build', '-t', 'clems4ever/authelia', '.']
|
||||
|
@ -191,7 +195,7 @@ module.exports = function (grunt) {
|
|||
grunt.registerTask('test-server', ['env:env-test-server-unit', 'run:test-server-unit'])
|
||||
grunt.registerTask('test-client', ['env:env-test-client-unit', 'run:test-client-unit'])
|
||||
grunt.registerTask('test-unit', ['test-server', 'test-client']);
|
||||
grunt.registerTask('test-int', ['run:test-cucumber', 'run:test-minimal-config', 'run:test-complete-config']);
|
||||
grunt.registerTask('test-int', ['run:test-cucumber', 'run:test-minimal-config', 'run:test-complete-config', 'run:test-inactivity']);
|
||||
|
||||
grunt.registerTask('copy-resources', ['copy:resources', 'copy:views', 'copy:images', 'copy:thirdparties', 'concat:css']);
|
||||
grunt.registerTask('generate-config-schema', ['run:generate-config-schema', 'copy:schema']);
|
||||
|
|
|
@ -108,3 +108,25 @@
|
|||
.u2f-token img {
|
||||
width: 70px;
|
||||
}
|
||||
|
||||
.keep-me-logged-in {
|
||||
margin-top: 10px;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.keep-me-logged-in input[type=checkbox] {
|
||||
transform: scale(0.8);
|
||||
margin: 0;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.keep-me-logged-in label {
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.keep-me-logged-in input,
|
||||
.keep-me-logged-in label {
|
||||
display: inline-block;
|
||||
margin-bottom: 0; /* I added this after I posted my reply */
|
||||
vertical-align: middle; /* Fixes any weird issues in Firefox and IE */
|
||||
}
|
|
@ -6,7 +6,8 @@ import Util = require("util");
|
|||
import UserMessages = require("../../../../shared/UserMessages");
|
||||
|
||||
export function validate(username: string, password: string,
|
||||
redirectUrl: string, $: JQueryStatic): BluebirdPromise<string> {
|
||||
keepMeLoggedIn: boolean, redirectUrl: string, $: JQueryStatic)
|
||||
: BluebirdPromise<string> {
|
||||
return new BluebirdPromise<string>(function (resolve, reject) {
|
||||
let url: string;
|
||||
if (redirectUrl != undefined) {
|
||||
|
@ -17,13 +18,19 @@ export function validate(username: string, password: string,
|
|||
url = Util.format("%s", Endpoints.FIRST_FACTOR_POST);
|
||||
}
|
||||
|
||||
const data: any = {
|
||||
username: username,
|
||||
password: password,
|
||||
};
|
||||
|
||||
if (keepMeLoggedIn) {
|
||||
data.keepMeLoggedIn = "true";
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
method: "POST",
|
||||
url: url,
|
||||
data: {
|
||||
username: username,
|
||||
password: password,
|
||||
}
|
||||
data: data
|
||||
})
|
||||
.done(function (body: any) {
|
||||
if (body && body.error) {
|
||||
|
|
|
@ -2,3 +2,4 @@
|
|||
export const USERNAME_FIELD_ID = "#username";
|
||||
export const PASSWORD_FIELD_ID = "#password";
|
||||
export const SIGN_IN_BUTTON_ID = "#signin";
|
||||
export const KEEP_ME_LOGGED_IN_ID = "#keep_me_logged_in";
|
||||
|
|
|
@ -15,13 +15,14 @@ export default function (window: Window, $: JQueryStatic,
|
|||
function onFormSubmitted() {
|
||||
const username: string = $(UISelectors.USERNAME_FIELD_ID).val() as string;
|
||||
const password: string = $(UISelectors.PASSWORD_FIELD_ID).val() as string;
|
||||
const keepMeLoggedIn: boolean = $(UISelectors.KEEP_ME_LOGGED_IN_ID).is(":checked");
|
||||
|
||||
$("form").css("opacity", 0.5);
|
||||
$("input,button").attr("disabled", "true");
|
||||
$(UISelectors.SIGN_IN_BUTTON_ID).text("Please wait...");
|
||||
|
||||
const redirectUrl = QueryParametersRetriever.get(Constants.REDIRECT_QUERY_PARAM);
|
||||
firstFactorValidator.validate(username, password, redirectUrl, $)
|
||||
firstFactorValidator.validate(username, password, keepMeLoggedIn, redirectUrl, $)
|
||||
.then(onFirstFactorSuccess, onFirstFactorFailure);
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -13,7 +13,8 @@ describe("test FirstFactorValidator", function () {
|
|||
const jqueryMock = JQueryMock.JQueryMock();
|
||||
jqueryMock.jquery.ajax.returns(postPromise);
|
||||
|
||||
return FirstFactorValidator.validate("username", "password", "http://redirect", jqueryMock.jquery as any);
|
||||
return FirstFactorValidator.validate("username", "password", false,
|
||||
"http://redirect", jqueryMock.jquery as any);
|
||||
});
|
||||
|
||||
function should_fail_first_factor_validation(errorMessage: string) {
|
||||
|
@ -27,7 +28,8 @@ describe("test FirstFactorValidator", function () {
|
|||
const jqueryMock = JQueryMock.JQueryMock();
|
||||
jqueryMock.jquery.ajax.returns(postPromise);
|
||||
|
||||
return FirstFactorValidator.validate("username", "password", "http://redirect", jqueryMock.jquery as any)
|
||||
return FirstFactorValidator.validate("username", "password", false,
|
||||
"http://redirect", jqueryMock.jquery as any)
|
||||
.then(function () {
|
||||
return BluebirdPromise.reject(new Error("First factor validation successfully finished while it should have not."));
|
||||
}, function (err: Error) {
|
||||
|
|
|
@ -3,16 +3,16 @@ services:
|
|||
authelia:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
dockerfile: Dockerfile.dev
|
||||
restart: always
|
||||
volumes:
|
||||
- ./server:/usr/src/server
|
||||
- ./dist/server/src/public_html:/usr/src/server/src/public_html
|
||||
- ./client:/usr/src/client
|
||||
- ./shared:/usr/src/shared
|
||||
- ./node_modules:/usr/src/node_modules
|
||||
- ./config.minimal.yml:/etc/authelia/config.yml:ro
|
||||
- /tmp/authelia:/tmp/authelia
|
||||
- ./users_database.yml:/etc/authelia/users_database.yml
|
||||
environment:
|
||||
- NODE_TLS_REJECT_UNAUTHORIZED=0
|
||||
depends_on:
|
||||
|
|
13
docker-compose.test.yml
Normal file
13
docker-compose.test.yml
Normal file
|
@ -0,0 +1,13 @@
|
|||
version: '2'
|
||||
services:
|
||||
authelia:
|
||||
build: .
|
||||
restart: always
|
||||
volumes:
|
||||
- ./config.test.yml:/etc/authelia/config.yml:ro
|
||||
- ./users_database.test.yml:/etc/authelia/users_database.yml:rw
|
||||
- /tmp/authelia:/tmp/authelia
|
||||
environment:
|
||||
- NODE_TLS_REJECT_UNAUTHORIZED=0
|
||||
networks:
|
||||
- example-network
|
1041
package-lock.json
generated
1041
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
|
@ -7,6 +7,7 @@ import { AuthenticationSession } from "../../types/AuthenticationSession";
|
|||
import { IRequestLogger } from "./logging/IRequestLogger";
|
||||
|
||||
const INITIAL_AUTHENTICATION_SESSION: AuthenticationSession = {
|
||||
keep_me_logged_in: false,
|
||||
first_factor: false,
|
||||
second_factor: false,
|
||||
last_activity_datetime: undefined,
|
||||
|
|
|
@ -43,6 +43,7 @@ describe("routes/firstfactor/post", function () {
|
|||
redirect: "http://redirect.url"
|
||||
},
|
||||
session: {
|
||||
cookie: {}
|
||||
},
|
||||
headers: {
|
||||
host: "home.example.com"
|
||||
|
@ -66,6 +67,26 @@ describe("routes/firstfactor/post", function () {
|
|||
});
|
||||
});
|
||||
|
||||
describe("keep me logged in", () => {
|
||||
beforeEach(() => {
|
||||
mocks.usersDatabase.checkUserPasswordStub.withArgs("username", "password")
|
||||
.returns(BluebirdPromise.resolve({
|
||||
emails: emails,
|
||||
groups: groups
|
||||
}));
|
||||
req.body.keepMeLoggedIn = "true";
|
||||
return FirstFactorPost.default(vars)(req as any, res as any);
|
||||
});
|
||||
|
||||
it("should set keep_me_logged_in session variable to true", function () {
|
||||
Assert.equal(authSession.keep_me_logged_in, true);
|
||||
});
|
||||
|
||||
it("should set cookie maxAge to one year", function () {
|
||||
Assert.equal(req.session.cookie.maxAge, 365 * 24 * 60 * 60 * 1000);
|
||||
});
|
||||
});
|
||||
|
||||
it("should retrieve email from LDAP", function () {
|
||||
mocks.usersDatabase.checkUserPasswordStub.withArgs("username", "password")
|
||||
.returns(BluebirdPromise.resolve([{ mail: ["test@example.com"] }]));
|
||||
|
|
|
@ -21,8 +21,16 @@ export default function (vars: ServerVariables) {
|
|||
: BluebirdPromise<void> {
|
||||
const username: string = req.body.username;
|
||||
const password: string = req.body.password;
|
||||
const keepMeLoggedIn: boolean = req.body.keepMeLoggedIn &&
|
||||
req.body.keepMeLoggedIn === "true";
|
||||
let authSession: AuthenticationSession;
|
||||
|
||||
if (keepMeLoggedIn) {
|
||||
// Stay connected for 1 year.
|
||||
vars.logger.debug(req, "User requested to stay logged in for one year.");
|
||||
req.session.cookie.maxAge = 365 * 24 * 60 * 60 * 1000;
|
||||
}
|
||||
|
||||
return BluebirdPromise.resolve()
|
||||
.then(function () {
|
||||
if (!username || !password) {
|
||||
|
@ -41,6 +49,7 @@ export default function (vars: ServerVariables) {
|
|||
"LDAP binding successful. Retrieved information about user are %s",
|
||||
JSON.stringify(groupsAndEmails));
|
||||
authSession.userid = username;
|
||||
authSession.keep_me_logged_in = keepMeLoggedIn;
|
||||
authSession.first_factor = true;
|
||||
const redirectUrl = req.query[Constants.REDIRECT_QUERY_PARAM] !== "undefined"
|
||||
// Fuck, don't know why it is a string!
|
||||
|
|
|
@ -80,6 +80,7 @@ describe("routes/verify/get", function () {
|
|||
describe("given different cases of session", function () {
|
||||
it("should not be authenticated when second factor is missing", function () {
|
||||
return test_non_authenticated_401({
|
||||
keep_me_logged_in: false,
|
||||
userid: "user",
|
||||
first_factor: true,
|
||||
second_factor: false,
|
||||
|
@ -91,6 +92,7 @@ describe("routes/verify/get", function () {
|
|||
|
||||
it("should not be authenticated when first factor is missing", function () {
|
||||
return test_non_authenticated_401({
|
||||
keep_me_logged_in: false,
|
||||
userid: "user",
|
||||
first_factor: false,
|
||||
second_factor: true,
|
||||
|
@ -102,6 +104,7 @@ describe("routes/verify/get", function () {
|
|||
|
||||
it("should not be authenticated when userid is missing", function () {
|
||||
return test_non_authenticated_401({
|
||||
keep_me_logged_in: false,
|
||||
userid: undefined,
|
||||
first_factor: true,
|
||||
second_factor: false,
|
||||
|
@ -113,6 +116,7 @@ describe("routes/verify/get", function () {
|
|||
|
||||
it("should not be authenticated when first and second factor are missing", function () {
|
||||
return test_non_authenticated_401({
|
||||
keep_me_logged_in: false,
|
||||
userid: "user",
|
||||
first_factor: false,
|
||||
second_factor: false,
|
||||
|
@ -134,6 +138,7 @@ describe("routes/verify/get", function () {
|
|||
mocks.accessController.isAccessAllowedMock.returns(false);
|
||||
|
||||
return test_unauthorized_403({
|
||||
keep_me_logged_in: false,
|
||||
first_factor: true,
|
||||
second_factor: true,
|
||||
userid: "user",
|
||||
|
|
|
@ -25,7 +25,7 @@ function verify_inactivity(req: Express.Request,
|
|||
: BluebirdPromise<void> {
|
||||
|
||||
// If inactivity is not specified, then inactivity timeout does not apply
|
||||
if (!configuration.session.inactivity) {
|
||||
if (!configuration.session.inactivity || authSession.keep_me_logged_in) {
|
||||
return BluebirdPromise.resolve();
|
||||
}
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ export class MongoCollection implements ICollection {
|
|||
.then((collection) => collection.insert(document));
|
||||
}
|
||||
|
||||
count(query: any): Bluebird<number> {
|
||||
count(query: any): Bluebird<any> {
|
||||
return this.collection()
|
||||
.then((collection) => collection.count(query));
|
||||
}
|
||||
|
|
|
@ -15,6 +15,9 @@ block content
|
|||
input(type="text" class="form-control" id="username" placeholder="Username" required autofocus)
|
||||
input(type="password" class="form-control" id="password" placeholder="Password" required)
|
||||
button(id="signin" class="btn btn-lg btn-primary btn-block" type="submit") Sign in
|
||||
div(class="keep-me-logged-in pull-left")
|
||||
input(type="checkbox" id="keep_me_logged_in" name="keep_me_logged_in" value="true")
|
||||
label(for="keep_me_logged_in") Keep me logged in
|
||||
div(class="bottom-right-links pull-right")
|
||||
a(href=reset_password_request_endpoint, class="link forgot-password") Forgot password?
|
||||
span(class="clearfix")
|
||||
|
|
|
@ -4,6 +4,7 @@ export interface AuthenticationSession {
|
|||
userid: string;
|
||||
first_factor: boolean;
|
||||
second_factor: boolean;
|
||||
keep_me_logged_in: boolean;
|
||||
last_activity_datetime: number;
|
||||
identity_check?: {
|
||||
challenge: string;
|
||||
|
|
32
test/configuration.ts
Normal file
32
test/configuration.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
import Bluebird = require("bluebird");
|
||||
import YamlJS = require("yamljs");
|
||||
import Fs = require("fs");
|
||||
import ChildProcess = require("child_process");
|
||||
|
||||
const execAsync = Bluebird.promisify(ChildProcess.exec);
|
||||
|
||||
export class Configuration {
|
||||
private outputPath: string;
|
||||
|
||||
setup(
|
||||
inputPath: string,
|
||||
outputPath: string,
|
||||
updateFn: (configuration: any) => void)
|
||||
: Bluebird<void> {
|
||||
|
||||
console.log("[CONFIGURATION] setup");
|
||||
this.outputPath = outputPath;
|
||||
return new Bluebird((resolve, reject) => {
|
||||
const configuration = YamlJS.load(inputPath);
|
||||
updateFn(configuration);
|
||||
const configurationStr = YamlJS.stringify(configuration);
|
||||
Fs.writeFileSync(outputPath, configurationStr);
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
cleanup(): Bluebird<{}> {
|
||||
console.log("[CONFIGURATION] cleanup");
|
||||
return execAsync(`rm ${this.outputPath}`);
|
||||
}
|
||||
}
|
|
@ -13,9 +13,9 @@ export class Environment {
|
|||
}
|
||||
|
||||
private runCommand(command: string, timeout?: number): Bluebird<void> {
|
||||
return new Bluebird<void>(function(resolve, reject) {
|
||||
return new Bluebird<void>((resolve, reject) => {
|
||||
console.log('[ENVIRONMENT] Running: %s', command);
|
||||
exec(command, function(err, stdout, stderr) {
|
||||
exec(command, (err, stdout, stderr) => {
|
||||
if(err) {
|
||||
reject(err);
|
||||
return;
|
||||
|
@ -34,10 +34,13 @@ export class Environment {
|
|||
}
|
||||
|
||||
cleanup(): Bluebird<void> {
|
||||
if(process.env.KEEP_ENV != "true") {
|
||||
const command = docker_compose(this.includes) + ' down'
|
||||
console.log('[ENVIRONMENT] Cleaning up...');
|
||||
return this.runCommand(command);
|
||||
}
|
||||
return Bluebird.resolve();
|
||||
}
|
||||
|
||||
stop_service(serviceName: string): Bluebird<void> {
|
||||
const command = docker_compose(this.includes) + ' stop ' + serviceName;
|
||||
|
|
|
@ -1,16 +1,28 @@
|
|||
import Bluebird = require("bluebird");
|
||||
import SeleniumWebdriver = require("selenium-webdriver");
|
||||
|
||||
export default function(driver: any, username: string, password: string) {
|
||||
export default function(
|
||||
driver: any,
|
||||
username: string,
|
||||
password: string,
|
||||
keepMeLoggedIn: boolean = false) {
|
||||
return driver.wait(SeleniumWebdriver.until.elementLocated(SeleniumWebdriver.By.id("username")), 5000)
|
||||
.then(function () {
|
||||
.then(() => {
|
||||
return driver.findElement(SeleniumWebdriver.By.id("username"))
|
||||
.sendKeys(username);
|
||||
})
|
||||
.then(function () {
|
||||
.then(() => {
|
||||
return driver.findElement(SeleniumWebdriver.By.id("password"))
|
||||
.sendKeys(password);
|
||||
})
|
||||
.then(function () {
|
||||
.then(() => {
|
||||
if (keepMeLoggedIn) {
|
||||
return driver.findElement(SeleniumWebdriver.By.id("keep_me_logged_in"))
|
||||
.click();
|
||||
}
|
||||
return Bluebird.resolve();
|
||||
})
|
||||
.then(() => {
|
||||
return driver.findElement(SeleniumWebdriver.By.tagName("button"))
|
||||
.click();
|
||||
});
|
||||
|
|
36
test/inactivity/00-suite.ts
Normal file
36
test/inactivity/00-suite.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
require("chromedriver");
|
||||
import Bluebird = require("bluebird");
|
||||
import Configuration = require("../configuration");
|
||||
import Environment = require("../environment");
|
||||
|
||||
import ChildProcess = require('child_process');
|
||||
const execAsync = Bluebird.promisify(ChildProcess.exec);
|
||||
|
||||
const includes = [
|
||||
"docker-compose.test.yml",
|
||||
"example/compose/docker-compose.base.yml",
|
||||
"example/compose/nginx/minimal/docker-compose.yml",
|
||||
]
|
||||
|
||||
|
||||
before(function() {
|
||||
this.timeout(20000);
|
||||
this.environment = new Environment.Environment(includes);
|
||||
this.configuration = new Configuration.Configuration();
|
||||
|
||||
return this.configuration.setup(
|
||||
"config.minimal.yml",
|
||||
"config.test.yml",
|
||||
conf => {
|
||||
conf.session.inactivity = 2000;
|
||||
})
|
||||
.then(() => execAsync("cp users_database.yml users_database.test.yml"))
|
||||
.then(() => this.environment.setup(2000));
|
||||
});
|
||||
|
||||
after(function() {
|
||||
this.timeout(30000);
|
||||
return this.configuration.cleanup()
|
||||
.then(() => execAsync("rm users_database.test.yml"))
|
||||
.then(() => this.environment.cleanup());
|
||||
});
|
48
test/inactivity/keep_me_logged_in.ts
Normal file
48
test/inactivity/keep_me_logged_in.ts
Normal file
|
@ -0,0 +1,48 @@
|
|||
import Bluebird = require("bluebird");
|
||||
import loginAndRegisterTotp from "../helpers/login-and-register-totp";
|
||||
import VisitPage from "../helpers/visit-page";
|
||||
import FillLoginPageWithUserAndPasswordAndClick from "../helpers/fill-login-page-and-click";
|
||||
import WithDriver from "../helpers/with-driver";
|
||||
import ValidateTotp from "../helpers/validate-totp";
|
||||
import WaitRedirected from "../helpers/wait-redirected";
|
||||
|
||||
describe("Keep me logged in", function() {
|
||||
this.timeout(15000);
|
||||
WithDriver();
|
||||
|
||||
before(function() {
|
||||
const that = this;
|
||||
return loginAndRegisterTotp(this.driver, "john")
|
||||
.then(function(secret: string) {
|
||||
that.secret = secret;
|
||||
if(!secret) return Bluebird.reject(new Error("No secret!"));
|
||||
return Bluebird.resolve();
|
||||
});
|
||||
});
|
||||
|
||||
it("should disconnect user after inactivity period", function() {
|
||||
const that = this;
|
||||
const driver = this.driver;
|
||||
return VisitPage(driver, "https://login.example.com:8080/?rd=https://admin.example.com:8080/secret.html")
|
||||
.then(() => FillLoginPageWithUserAndPasswordAndClick(driver, 'john', 'password', false))
|
||||
.then(() => ValidateTotp(driver, that.secret))
|
||||
.then(() => WaitRedirected(driver, "https://admin.example.com:8080/secret.html"))
|
||||
.then(() => VisitPage(driver, "https://home.example.com:8080/"))
|
||||
.then(() => driver.sleep(3000))
|
||||
.then(() => driver.get("https://admin.example.com:8080/secret.html"))
|
||||
.then(() => WaitRedirected(driver, "https://login.example.com:8080/?rd=https://admin.example.com:8080/secret.html"))
|
||||
});
|
||||
|
||||
it.only("should keep user logged in after inactivity period", function() {
|
||||
const that = this;
|
||||
const driver = this.driver;
|
||||
return VisitPage(driver, "https://login.example.com:8080/?rd=https://admin.example.com:8080/secret.html")
|
||||
.then(() => FillLoginPageWithUserAndPasswordAndClick(driver, 'john', 'password', true))
|
||||
.then(() => ValidateTotp(driver, that.secret))
|
||||
.then(() => WaitRedirected(driver, "https://admin.example.com:8080/secret.html"))
|
||||
.then(() => VisitPage(driver, "https://home.example.com:8080/"))
|
||||
.then(() => driver.sleep(5000))
|
||||
.then(() => driver.get("https://admin.example.com:8080/secret.html"))
|
||||
.then(() => WaitRedirected(driver, "https://admin.example.com:8080/secret.html"))
|
||||
});
|
||||
});
|
|
@ -37,13 +37,13 @@ describe('Validate TOTP factor', function() {
|
|||
const driver = this.driver;
|
||||
|
||||
return VisitPage(driver, "https://login.example.com:8080/?rd=https://admin.example.com:8080/secret.html")
|
||||
.then(function() {
|
||||
.then(() => {
|
||||
return FillLoginPageWithUserAndPasswordAndClick(driver, 'john', 'password');
|
||||
})
|
||||
.then(function () {
|
||||
.then(() => {
|
||||
return ValidateTotp(driver, secret);
|
||||
})
|
||||
.then(function() {
|
||||
.then(() => {
|
||||
return WaitRedirected(driver, "https://admin.example.com:8080/secret.html")
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue
Block a user