Merge pull request #161 from clems4ever/inactivity_timeout
Inactivity timeout (soft session expiration timeout)pull/162/head
commit
5300f67217
|
@ -29,6 +29,7 @@ dist/
|
|||
|
||||
# Specific files
|
||||
/config.yml
|
||||
/config.test.yml
|
||||
|
||||
example/ldap/private.ldif
|
||||
|
||||
|
|
|
@ -42,8 +42,8 @@ module.exports = function (grunt) {
|
|||
args: ['--colors', '--compilers', 'ts:ts-node/register', '--recursive', 'client/test']
|
||||
},
|
||||
"test-int": {
|
||||
cmd: "./node_modules/.bin/cucumber-js",
|
||||
args: ["--colors", "--compiler", "ts:ts-node/register", "./test/features"]
|
||||
cmd: "./scripts/run-cucumber.sh",
|
||||
args: ["./test/features"]
|
||||
},
|
||||
"docker-build": {
|
||||
cmd: "docker",
|
||||
|
@ -194,6 +194,8 @@ module.exports = function (grunt) {
|
|||
grunt.registerTask('build', ['build-client', 'build-server']);
|
||||
grunt.registerTask('build-dist', ['build', 'run:minify', 'cssmin', 'run:include-minified-script']);
|
||||
|
||||
grunt.registerTask('schema', ['run:generate-config-schema'])
|
||||
|
||||
grunt.registerTask('docker-build', ['run:docker-build']);
|
||||
|
||||
grunt.registerTask('default', ['build-dist']);
|
||||
|
|
|
@ -153,8 +153,11 @@ session:
|
|||
# The secret to encrypt the session cookie.
|
||||
secret: unsecure_session_secret
|
||||
|
||||
# The time before the cookie expires.
|
||||
expiration: 3600000
|
||||
# The time in ms before the cookie expires and session is reset.
|
||||
expiration: 3600000 # 1 hour
|
||||
|
||||
# The inactivity time in ms before the session is reset.
|
||||
inactivity: 300000 # 5 minutes
|
||||
|
||||
# The domain to protect.
|
||||
# Note: the authenticator must also be in that domain. If empty, the cookie
|
||||
|
|
|
@ -23,8 +23,8 @@ ldap:
|
|||
# An additional dn to define the scope to all users
|
||||
additional_users_dn: ou=users
|
||||
|
||||
# The users filter.
|
||||
# {0} is the matcher replaced by username.
|
||||
# The users filter used to find the user DN
|
||||
# {0} is a matcher replaced by username.
|
||||
# 'cn={0}' by default.
|
||||
users_filter: cn={0}
|
||||
|
||||
|
@ -47,6 +47,7 @@ ldap:
|
|||
user: cn=admin,dc=example,dc=com
|
||||
password: password
|
||||
|
||||
|
||||
# Authentication methods
|
||||
#
|
||||
# Authentication methods can be defined per subdomain.
|
||||
|
@ -65,17 +66,36 @@ authentication_methods:
|
|||
|
||||
# Access Control
|
||||
#
|
||||
# Access control is a set of rules you can use to restrict the user access.
|
||||
# Default (anyone), per-user or per-group rules can be defined.
|
||||
# Access control is a set of rules you can use to restrict user access to certain
|
||||
# resources.
|
||||
# Any (apply to anyone), per-user or per-group rules can be defined.
|
||||
#
|
||||
# If 'access_control' is not defined, ACL rules are disabled and a default policy
|
||||
# is applied, i.e., access is allowed to anyone. Otherwise restrictions follow
|
||||
# the rules defined below.
|
||||
# If no rule is provided, all domains are denied.
|
||||
# If 'access_control' is not defined, ACL rules are disabled and the `allow` default
|
||||
# policy is applied, i.e., access is allowed to anyone. Otherwise restrictions follow
|
||||
# the rules defined.
|
||||
#
|
||||
# Note: One can use the wildcard * to match any subdomain.
|
||||
# It must stand at the beginning of the pattern. (example: *.mydomain.com)
|
||||
#
|
||||
# Note: You must put the pattern in simple quotes when using the wildcard for the YAML
|
||||
# to be syntaxically correct.
|
||||
#
|
||||
# Definition: A `rule` is an object with the following keys: `domain`, `policy`
|
||||
# and `resources`.
|
||||
# - `domain` defines which domain or set of domains the rule applies to.
|
||||
# - `policy` is the policy to apply to resources. It must be either `allow` or `deny`.
|
||||
# - `resources` is a list of regular expressions that matches a set of resources to
|
||||
# apply the policy to.
|
||||
#
|
||||
# Note: Rules follow an order of priority defined as follows:
|
||||
# In each category (`any`, `groups`, `users`), the latest rules have the highest
|
||||
# priority. In other words, it means that if a given resource matches two rules in the
|
||||
# same category, the latest one overrides the first one.
|
||||
# Each category has also its own priority. That is, `users` has the highest priority, then
|
||||
# `groups` and `any` has the lowest priority. It means if two rules in different categories
|
||||
# match a given resource, the one in the category with the highest priority overrides the
|
||||
# other one.
|
||||
#
|
||||
# One can use the wildcard * to match any subdomain.
|
||||
# Note 1: It must stand at the beginning of the pattern. (example: *.mydomain.com)
|
||||
# Note 2: You must put the pattern in simple quotes when using the wildcard.
|
||||
access_control:
|
||||
# Default policy can either be `allow` or `deny`.
|
||||
# It is the policy applied to any resource if it has not been overriden
|
||||
|
@ -125,15 +145,19 @@ access_control:
|
|||
resources:
|
||||
- '^/users/bob/.*$'
|
||||
|
||||
|
||||
# Configuration of session cookies
|
||||
#
|
||||
# The session cookies identify the user once logged in.
|
||||
session:
|
||||
# The secret to encrypt the session cookie.
|
||||
secret: unsecure_secret
|
||||
secret: unsecure_session_secret
|
||||
|
||||
# The time before the cookie expires.
|
||||
expiration: 3600000
|
||||
# The time in ms before the cookie expires and session is reset.
|
||||
expiration: 3600000 # 1 hour
|
||||
|
||||
# The inactivity time in ms before the session is reset.
|
||||
inactivity: 300000 # 5 minutes
|
||||
|
||||
# The domain to protect.
|
||||
# Note: the authenticator must also be in that domain. If empty, the cookie
|
||||
|
@ -178,6 +202,10 @@ storage:
|
|||
# registration or a TOTP registration.
|
||||
# Use only an available configuration: filesystem, gmail
|
||||
notifier:
|
||||
# For testing purpose, notifications can be sent in a file
|
||||
# filesystem:
|
||||
# filename: /tmp/authelia/notification.txt
|
||||
|
||||
# Use your gmail account to send the notifications. You can use an app password.
|
||||
# gmail:
|
||||
# username: user@example.com
|
||||
|
@ -187,7 +215,7 @@ notifier:
|
|||
# Use a SMTP server for sending notifications
|
||||
smtp:
|
||||
username: test
|
||||
password: test
|
||||
password: password
|
||||
secure: false
|
||||
host: 'smtp'
|
||||
port: 1025
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
#!/bin/bash
|
||||
|
||||
./node_modules/.bin/cucumber-js --colors --compiler ts:ts-node/register $*
|
|
@ -5,7 +5,8 @@ set -e
|
|||
docker --version
|
||||
docker-compose --version
|
||||
|
||||
grunt run:generate-config-schema
|
||||
# Generate configuration schema
|
||||
grunt schema
|
||||
|
||||
# Run unit tests
|
||||
grunt test-unit
|
||||
|
|
|
@ -8,8 +8,11 @@ export class AuthenticationMethodCalculator {
|
|||
}
|
||||
|
||||
compute(subDomain: string): AuthenticationMethod {
|
||||
if (subDomain in this.configuration.per_subdomain_methods)
|
||||
if (this.configuration
|
||||
&& this.configuration.per_subdomain_methods
|
||||
&& subDomain in this.configuration.per_subdomain_methods) {
|
||||
return this.configuration.per_subdomain_methods[subDomain];
|
||||
}
|
||||
return this.configuration.default_method;
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ export interface AuthenticationSession {
|
|||
userid: string;
|
||||
first_factor: boolean;
|
||||
second_factor: boolean;
|
||||
last_activity_datetime: number;
|
||||
identity_check?: {
|
||||
challenge: string;
|
||||
userid: string;
|
||||
|
@ -23,6 +24,7 @@ export interface AuthenticationSession {
|
|||
const INITIAL_AUTHENTICATION_SESSION: AuthenticationSession = {
|
||||
first_factor: false,
|
||||
second_factor: false,
|
||||
last_activity_datetime: undefined,
|
||||
userid: undefined,
|
||||
email: undefined,
|
||||
groups: [],
|
||||
|
@ -36,6 +38,9 @@ export function reset(req: express.Request): void {
|
|||
const logger = ServerVariablesHandler.getLogger(req.app);
|
||||
logger.debug(req, "Authentication session %s is being reset.", req.sessionID);
|
||||
req.session.auth = Object.assign({}, INITIAL_AUTHENTICATION_SESSION, {});
|
||||
|
||||
// Initialize last activity with current time
|
||||
req.session.auth.last_activity_datetime = new Date().getTime();
|
||||
}
|
||||
|
||||
export function get(req: express.Request): BluebirdPromise<AuthenticationSession> {
|
||||
|
|
|
@ -33,6 +33,7 @@ import Error404Get = require("./routes/error/404/get");
|
|||
import LoggedIn = require("./routes/loggedin/get");
|
||||
|
||||
import { ServerVariablesHandler } from "./ServerVariablesHandler";
|
||||
import { ServerVariables } from "./ServerVariables";
|
||||
|
||||
import Endpoints = require("../../../shared/api");
|
||||
|
||||
|
@ -45,7 +46,7 @@ function withLog(fn: (req: Express.Request, res: Express.Response) => void) {
|
|||
}
|
||||
|
||||
export class RestApi {
|
||||
static setup(app: Express.Application): void {
|
||||
static setup(app: Express.Application, vars: ServerVariables): void {
|
||||
app.get(Endpoints.FIRST_FACTOR_GET, withLog(FirstFactorGet.default));
|
||||
app.get(Endpoints.SECOND_FACTOR_GET, withLog(SecondFactorGet.default));
|
||||
app.get(Endpoints.LOGOUT_GET, withLog(LogoutGet.default));
|
||||
|
@ -62,9 +63,9 @@ export class RestApi {
|
|||
app.get(Endpoints.RESET_PASSWORD_REQUEST_GET, withLog(ResetPasswordRequestPost.default));
|
||||
app.post(Endpoints.RESET_PASSWORD_FORM_POST, withLog(ResetPasswordFormPost.default));
|
||||
|
||||
app.get(Endpoints.VERIFY_GET, withLog(VerifyGet.default));
|
||||
app.post(Endpoints.FIRST_FACTOR_POST, withLog(FirstFactorPost.default));
|
||||
app.post(Endpoints.SECOND_FACTOR_TOTP_POST, withLog(TOTPSignGet.default));
|
||||
app.get(Endpoints.VERIFY_GET, withLog(VerifyGet.default(vars)));
|
||||
app.post(Endpoints.FIRST_FACTOR_POST, withLog(FirstFactorPost.default(vars)));
|
||||
app.post(Endpoints.SECOND_FACTOR_TOTP_POST, withLog(TOTPSignGet.default(vars)));
|
||||
|
||||
app.get(Endpoints.SECOND_FACTOR_U2F_SIGN_REQUEST_GET, withLog(U2FSignRequestGet.default));
|
||||
app.post(Endpoints.SECOND_FACTOR_U2F_SIGN_POST, withLog(U2FSignPost.default));
|
||||
|
|
|
@ -4,16 +4,14 @@ import ObjectPath = require("object-path");
|
|||
import { AccessController } from "./access_control/AccessController";
|
||||
import { AppConfiguration, UserConfiguration } from "./configuration/Configuration";
|
||||
import { GlobalDependencies } from "../../types/Dependencies";
|
||||
import { AuthenticationRegulator } from "./AuthenticationRegulator";
|
||||
import { UserDataStore } from "./storage/UserDataStore";
|
||||
import { ConfigurationParser } from "./configuration/ConfigurationParser";
|
||||
import { TOTPValidator } from "./TOTPValidator";
|
||||
import { TOTPGenerator } from "./TOTPGenerator";
|
||||
import { RestApi } from "./RestApi";
|
||||
import { ServerVariablesHandler } from "./ServerVariablesHandler";
|
||||
import { ServerVariablesHandler, ServerVariablesInitializer } from "./ServerVariablesHandler";
|
||||
import { SessionConfigurationBuilder } from "./configuration/SessionConfigurationBuilder";
|
||||
import { GlobalLogger } from "./logging/GlobalLogger";
|
||||
import { RequestLogger } from "./logging/RequestLogger";
|
||||
import { ServerVariables } from "./ServerVariables";
|
||||
|
||||
import * as Express from "express";
|
||||
import * as BodyParser from "body-parser";
|
||||
|
@ -37,13 +35,16 @@ export default class Server {
|
|||
private httpServer: http.Server;
|
||||
private globalLogger: GlobalLogger;
|
||||
private requestLogger: RequestLogger;
|
||||
private serverVariables: ServerVariables;
|
||||
|
||||
constructor(deps: GlobalDependencies) {
|
||||
this.globalLogger = new GlobalLogger(deps.winston);
|
||||
this.requestLogger = new RequestLogger(deps.winston);
|
||||
}
|
||||
|
||||
private setupExpressApplication(config: AppConfiguration, app: Express.Application, deps: GlobalDependencies): void {
|
||||
private setupExpressApplication(config: AppConfiguration,
|
||||
app: Express.Application,
|
||||
deps: GlobalDependencies): void {
|
||||
const viewsDirectory = Path.resolve(__dirname, "../views");
|
||||
const publicHtmlDirectory = Path.resolve(__dirname, "../public_html");
|
||||
|
||||
|
@ -60,7 +61,7 @@ export default class Server {
|
|||
app.set(VIEWS, viewsDirectory);
|
||||
app.set(VIEW_ENGINE, PUG);
|
||||
|
||||
RestApi.setup(app);
|
||||
RestApi.setup(app, this.serverVariables);
|
||||
}
|
||||
|
||||
private displayConfigurations(userConfiguration: UserConfiguration,
|
||||
|
@ -90,8 +91,14 @@ export default class Server {
|
|||
}
|
||||
|
||||
private setup(config: AppConfiguration, app: Express.Application, deps: GlobalDependencies): BluebirdPromise<void> {
|
||||
this.setupExpressApplication(config, app, deps);
|
||||
return ServerVariablesHandler.initialize(app, config, this.requestLogger, deps);
|
||||
const that = this;
|
||||
return ServerVariablesInitializer.initialize(config, this.requestLogger, deps)
|
||||
.then(function (vars: ServerVariables) {
|
||||
that.serverVariables = vars;
|
||||
that.setupExpressApplication(config, app, deps);
|
||||
ServerVariablesHandler.setup(app, vars);
|
||||
return BluebirdPromise.resolve();
|
||||
});
|
||||
}
|
||||
|
||||
private startServer(app: Express.Application, port: number) {
|
||||
|
|
|
@ -1,33 +1,25 @@
|
|||
import U2F = require("u2f");
|
||||
|
||||
import { IRequestLogger } from "./logging/IRequestLogger";
|
||||
import { IAuthenticator } from "./ldap/IAuthenticator";
|
||||
import { IPasswordUpdater } from "./ldap/IPasswordUpdater";
|
||||
import { IEmailsRetriever } from "./ldap/IEmailsRetriever";
|
||||
|
||||
import { TOTPValidator } from "./TOTPValidator";
|
||||
import { TOTPGenerator } from "./TOTPGenerator";
|
||||
import { ITotpHandler } from "./authentication/totp/ITotpHandler";
|
||||
import { IU2fHandler } from "./authentication/u2f/IU2fHandler";
|
||||
import { IUserDataStore } from "./storage/IUserDataStore";
|
||||
import { INotifier } from "./notifiers/INotifier";
|
||||
import { AuthenticationRegulator } from "./AuthenticationRegulator";
|
||||
import Configuration = require("./configuration/Configuration");
|
||||
import { AccessController } from "./access_control/AccessController";
|
||||
import { AuthenticationMethodCalculator } from "./AuthenticationMethodCalculator";
|
||||
|
||||
|
||||
import { IRegulator } from "./regulation/IRegulator";
|
||||
import { AppConfiguration } from "./configuration/Configuration";
|
||||
import { IAccessController } from "./access_control/IAccessController";
|
||||
|
||||
export interface ServerVariables {
|
||||
logger: IRequestLogger;
|
||||
ldapAuthenticator: IAuthenticator;
|
||||
ldapPasswordUpdater: IPasswordUpdater;
|
||||
ldapEmailsRetriever: IEmailsRetriever;
|
||||
totpValidator: TOTPValidator;
|
||||
totpGenerator: TOTPGenerator;
|
||||
u2f: typeof U2F;
|
||||
totpHandler: ITotpHandler;
|
||||
u2f: IU2fHandler;
|
||||
userDataStore: IUserDataStore;
|
||||
notifier: INotifier;
|
||||
regulator: AuthenticationRegulator;
|
||||
config: Configuration.AppConfiguration;
|
||||
accessController: AccessController;
|
||||
authenticationMethodsCalculator: AuthenticationMethodCalculator;
|
||||
regulator: IRegulator;
|
||||
config: AppConfiguration;
|
||||
accessController: IAccessController;
|
||||
}
|
|
@ -16,18 +16,19 @@ import { EmailsRetriever } from "./ldap/EmailsRetriever";
|
|||
import { ClientFactory } from "./ldap/ClientFactory";
|
||||
import { LdapClientFactory } from "./ldap/LdapClientFactory";
|
||||
|
||||
import { TOTPValidator } from "./TOTPValidator";
|
||||
import { TOTPGenerator } from "./TOTPGenerator";
|
||||
|
||||
import { TotpHandler } from "./authentication/totp/TotpHandler";
|
||||
import { ITotpHandler } from "./authentication/totp/ITotpHandler";
|
||||
import { NotifierFactory } from "./notifiers/NotifierFactory";
|
||||
import { MailSenderBuilder } from "./notifiers/MailSenderBuilder";
|
||||
|
||||
import { IUserDataStore } from "./storage/IUserDataStore";
|
||||
import { UserDataStore } from "./storage/UserDataStore";
|
||||
import { INotifier } from "./notifiers/INotifier";
|
||||
import { AuthenticationRegulator } from "./AuthenticationRegulator";
|
||||
import { Regulator } from "./regulation/Regulator";
|
||||
import { IRegulator } from "./regulation/IRegulator";
|
||||
import Configuration = require("./configuration/Configuration");
|
||||
import { AccessController } from "./access_control/AccessController";
|
||||
import { IAccessController } from "./access_control/IAccessController";
|
||||
import { CollectionFactoryFactory } from "./storage/CollectionFactoryFactory";
|
||||
import { ICollectionFactory } from "./storage/ICollectionFactory";
|
||||
import { MongoCollectionFactory } from "./storage/mongo/MongoCollectionFactory";
|
||||
|
@ -67,9 +68,9 @@ class UserDataStoreFactory {
|
|||
}
|
||||
}
|
||||
|
||||
export class ServerVariablesHandler {
|
||||
static initialize(app: express.Application, config: Configuration.AppConfiguration, requestLogger: IRequestLogger,
|
||||
deps: GlobalDependencies): BluebirdPromise<void> {
|
||||
export class ServerVariablesInitializer {
|
||||
static initialize(config: Configuration.AppConfiguration, requestLogger: IRequestLogger,
|
||||
deps: GlobalDependencies): BluebirdPromise<ServerVariables> {
|
||||
const mailSenderBuilder = new MailSenderBuilder(Nodemailer);
|
||||
const notifier = NotifierFactory.build(config.notifier, mailSenderBuilder);
|
||||
const ldapClientFactory = new LdapClientFactory(config.ldap, deps.ldapjs);
|
||||
|
@ -79,13 +80,11 @@ export class ServerVariablesHandler {
|
|||
const ldapPasswordUpdater = new PasswordUpdater(config.ldap, clientFactory);
|
||||
const ldapEmailsRetriever = new EmailsRetriever(config.ldap, clientFactory);
|
||||
const accessController = new AccessController(config.access_control, deps.winston);
|
||||
const totpValidator = new TOTPValidator(deps.speakeasy);
|
||||
const totpGenerator = new TOTPGenerator(deps.speakeasy);
|
||||
const authenticationMethodCalculator = new AuthenticationMethodCalculator(config.authentication_methods);
|
||||
const totpHandler = new TotpHandler(deps.speakeasy);
|
||||
|
||||
return UserDataStoreFactory.create(config)
|
||||
.then(function (userDataStore: UserDataStore) {
|
||||
const regulator = new AuthenticationRegulator(userDataStore, config.regulation.max_retries,
|
||||
const regulator = new Regulator(userDataStore, config.regulation.max_retries,
|
||||
config.regulation.find_time, config.regulation.ban_time);
|
||||
|
||||
const variables: ServerVariables = {
|
||||
|
@ -97,16 +96,19 @@ export class ServerVariablesHandler {
|
|||
logger: requestLogger,
|
||||
notifier: notifier,
|
||||
regulator: regulator,
|
||||
totpGenerator: totpGenerator,
|
||||
totpValidator: totpValidator,
|
||||
totpHandler: totpHandler,
|
||||
u2f: deps.u2f,
|
||||
userDataStore: userDataStore,
|
||||
authenticationMethodsCalculator: authenticationMethodCalculator
|
||||
userDataStore: userDataStore
|
||||
};
|
||||
|
||||
app.set(VARIABLES_KEY, variables);
|
||||
return BluebirdPromise.resolve(variables);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class ServerVariablesHandler {
|
||||
static setup(app: express.Application, variables: ServerVariables): void {
|
||||
app.set(VARIABLES_KEY, variables);
|
||||
}
|
||||
|
||||
static getLogger(app: express.Application): IRequestLogger {
|
||||
return (app.get(VARIABLES_KEY) as ServerVariables).logger;
|
||||
|
@ -136,27 +138,19 @@ export class ServerVariablesHandler {
|
|||
return (app.get(VARIABLES_KEY) as ServerVariables).config;
|
||||
}
|
||||
|
||||
static getAuthenticationRegulator(app: express.Application): AuthenticationRegulator {
|
||||
static getAuthenticationRegulator(app: express.Application): IRegulator {
|
||||
return (app.get(VARIABLES_KEY) as ServerVariables).regulator;
|
||||
}
|
||||
|
||||
static getAccessController(app: express.Application): AccessController {
|
||||
static getAccessController(app: express.Application): IAccessController {
|
||||
return (app.get(VARIABLES_KEY) as ServerVariables).accessController;
|
||||
}
|
||||
|
||||
static getTOTPGenerator(app: express.Application): TOTPGenerator {
|
||||
return (app.get(VARIABLES_KEY) as ServerVariables).totpGenerator;
|
||||
}
|
||||
|
||||
static getTOTPValidator(app: express.Application): TOTPValidator {
|
||||
return (app.get(VARIABLES_KEY) as ServerVariables).totpValidator;
|
||||
static getTotpHandler(app: express.Application): ITotpHandler {
|
||||
return (app.get(VARIABLES_KEY) as ServerVariables).totpHandler;
|
||||
}
|
||||
|
||||
static getU2F(app: express.Application): typeof U2F {
|
||||
return (app.get(VARIABLES_KEY) as ServerVariables).u2f;
|
||||
}
|
||||
|
||||
static getAuthenticationMethodCalculator(app: express.Application): AuthenticationMethodCalculator {
|
||||
return (app.get(VARIABLES_KEY) as ServerVariables).authenticationMethodsCalculator;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
|
||||
import Speakeasy = require("speakeasy");
|
||||
import BluebirdPromise = require("bluebird");
|
||||
import { TOTPSecret } from "../../types/TOTPSecret";
|
||||
|
||||
interface GenerateSecretOptions {
|
||||
length?: number;
|
||||
symbols?: boolean;
|
||||
otpauth_url?: boolean;
|
||||
name?: string;
|
||||
issuer?: string;
|
||||
}
|
||||
|
||||
export class TOTPGenerator {
|
||||
private speakeasy: typeof Speakeasy;
|
||||
|
||||
constructor(speakeasy: typeof Speakeasy) {
|
||||
this.speakeasy = speakeasy;
|
||||
}
|
||||
|
||||
generate(options?: GenerateSecretOptions): TOTPSecret {
|
||||
return this.speakeasy.generateSecret(options);
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
import Speakeasy = require("speakeasy");
|
||||
import BluebirdPromise = require("bluebird");
|
||||
|
||||
const TOTP_ENCODING = "base32";
|
||||
const WINDOW: number = 1;
|
||||
|
||||
export class TOTPValidator {
|
||||
private speakeasy: typeof Speakeasy;
|
||||
|
||||
constructor(speakeasy: typeof Speakeasy) {
|
||||
this.speakeasy = speakeasy;
|
||||
}
|
||||
|
||||
validate(token: string, secret: string): BluebirdPromise<void> {
|
||||
const isValid = this.speakeasy.totp.verify({
|
||||
secret: secret,
|
||||
encoding: TOTP_ENCODING,
|
||||
token: token,
|
||||
window: WINDOW
|
||||
} as any);
|
||||
|
||||
if (isValid)
|
||||
return BluebirdPromise.resolve();
|
||||
else
|
||||
return BluebirdPromise.reject(new Error("Wrong TOTP token."));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
import { TOTPSecret } from "../../../../types/TOTPSecret";
|
||||
|
||||
export interface GenerateSecretOptions {
|
||||
length?: number;
|
||||
symbols?: boolean;
|
||||
otpauth_url?: boolean;
|
||||
name?: string;
|
||||
issuer?: string;
|
||||
}
|
||||
|
||||
export interface ITotpHandler {
|
||||
generate(options?: GenerateSecretOptions): TOTPSecret;
|
||||
validate(token: string, secret: string): boolean;
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
import { ITotpHandler, GenerateSecretOptions } from "./ITotpHandler";
|
||||
import { TOTPSecret } from "../../../../types/TOTPSecret";
|
||||
import Speakeasy = require("speakeasy");
|
||||
|
||||
const TOTP_ENCODING = "base32";
|
||||
const WINDOW: number = 1;
|
||||
|
||||
export class TotpHandler implements ITotpHandler {
|
||||
private speakeasy: typeof Speakeasy;
|
||||
|
||||
constructor(speakeasy: typeof Speakeasy) {
|
||||
this.speakeasy = speakeasy;
|
||||
}
|
||||
|
||||
generate(options?: GenerateSecretOptions): TOTPSecret {
|
||||
return this.speakeasy.generateSecret(options);
|
||||
}
|
||||
|
||||
validate(token: string, secret: string): boolean {
|
||||
return this.speakeasy.totp.verify({
|
||||
secret: secret,
|
||||
encoding: TOTP_ENCODING,
|
||||
token: token,
|
||||
window: WINDOW
|
||||
} as any);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
import U2f = require("u2f");
|
||||
|
||||
export interface IU2fHandler {
|
||||
request(appId: string, keyHandle?: string): U2f.Request;
|
||||
checkRegistration(registrationRequest: U2f.Request, registrationResponse: U2f.RegistrationData)
|
||||
: U2f.RegistrationResult | U2f.Error;
|
||||
checkSignature(signatureRequest: U2f.Request, signatureResponse: U2f.SignatureData, publicKey: string)
|
||||
: U2f.SignatureResult | U2f.Error;
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
import { IU2fHandler } from "./IU2fHandler";
|
||||
import U2f = require("u2f");
|
||||
|
||||
export class U2fHandler implements IU2fHandler {
|
||||
private u2f: typeof U2f;
|
||||
|
||||
constructor(u2f: typeof U2f) {
|
||||
this.u2f = u2f;
|
||||
}
|
||||
|
||||
request(appId: string, keyHandle?: string): U2f.Request {
|
||||
return this.u2f.request(appId, keyHandle);
|
||||
}
|
||||
|
||||
checkRegistration(registrationRequest: U2f.Request, registrationResponse: U2f.RegistrationData)
|
||||
: U2f.RegistrationResult | U2f.Error {
|
||||
return this.u2f.checkRegistration(registrationRequest, registrationResponse);
|
||||
}
|
||||
|
||||
checkSignature(signatureRequest: U2f.Request, signatureResponse: U2f.SignatureData, publicKey: string)
|
||||
: U2f.SignatureResult | U2f.Error {
|
||||
return this.u2f.checkSignature(signatureRequest, signatureResponse, publicKey);
|
||||
}
|
||||
}
|
|
@ -62,6 +62,7 @@ export interface SessionRedisOptions {
|
|||
interface SessionCookieConfiguration {
|
||||
secret: string;
|
||||
expiration?: number;
|
||||
inactivity?: number;
|
||||
domain?: string;
|
||||
redis?: SessionRedisOptions;
|
||||
}
|
||||
|
|
|
@ -71,6 +71,7 @@ function adaptFromUserConfiguration(userConfiguration: UserConfiguration)
|
|||
domain: ObjectPath.get<object, string>(userConfiguration, "session.domain"),
|
||||
secret: ObjectPath.get<object, string>(userConfiguration, "session.secret"),
|
||||
expiration: get_optional<number>(userConfiguration, "session.expiration", 3600000), // in ms
|
||||
inactivity: get_optional<number>(userConfiguration, "session.inactivity", undefined),
|
||||
redis: ObjectPath.get<object, SessionRedisOptions>(userConfiguration, "session.redis")
|
||||
},
|
||||
storage: {
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
import BluebirdPromise = require("bluebird");
|
||||
|
||||
export interface IRegulator {
|
||||
mark(userId: string, isAuthenticationSuccessful: boolean): BluebirdPromise<void>;
|
||||
regulate(userId: string): BluebirdPromise<void>;
|
||||
}
|
|
@ -1,10 +1,11 @@
|
|||
|
||||
import * as BluebirdPromise from "bluebird";
|
||||
import exceptions = require("./Exceptions");
|
||||
import { IUserDataStore } from "./storage/IUserDataStore";
|
||||
import { AuthenticationTraceDocument } from "./storage/AuthenticationTraceDocument";
|
||||
import exceptions = require("../Exceptions");
|
||||
import { IUserDataStore } from "../storage/IUserDataStore";
|
||||
import { AuthenticationTraceDocument } from "../storage/AuthenticationTraceDocument";
|
||||
import { IRegulator } from "./IRegulator";
|
||||
|
||||
export class AuthenticationRegulator {
|
||||
export class Regulator implements IRegulator {
|
||||
private userDataStore: IUserDataStore;
|
||||
private banTime: number;
|
||||
private findTime: number;
|
|
@ -4,7 +4,7 @@ import objectPath = require("object-path");
|
|||
import BluebirdPromise = require("bluebird");
|
||||
import express = require("express");
|
||||
import { AccessController } from "../../access_control/AccessController";
|
||||
import { AuthenticationRegulator } from "../../AuthenticationRegulator";
|
||||
import { Regulator } from "../../regulation/Regulator";
|
||||
import { GroupsAndEmails } from "../../ldap/IClient";
|
||||
import Endpoint = require("../../../../../shared/api");
|
||||
import ErrorReplies = require("../../ErrorReplies");
|
||||
|
@ -13,18 +13,14 @@ import AuthenticationSession = require("../../AuthenticationSession");
|
|||
import Constants = require("../../../../../shared/constants");
|
||||
import { DomainExtractor } from "../../utils/DomainExtractor";
|
||||
import UserMessages = require("../../../../../shared/UserMessages");
|
||||
import { AuthenticationMethodCalculator } from "../../AuthenticationMethodCalculator";
|
||||
import { ServerVariables } from "../../ServerVariables";
|
||||
|
||||
export default function (req: express.Request, res: express.Response): BluebirdPromise<void> {
|
||||
export default function (vars: ServerVariables) {
|
||||
return function (req: express.Request, res: express.Response)
|
||||
: BluebirdPromise<void> {
|
||||
const username: string = req.body.username;
|
||||
const password: string = req.body.password;
|
||||
|
||||
const logger = ServerVariablesHandler.getLogger(req.app);
|
||||
const ldap = ServerVariablesHandler.getLdapAuthenticator(req.app);
|
||||
const config = ServerVariablesHandler.getConfiguration(req.app);
|
||||
const regulator = ServerVariablesHandler.getAuthenticationRegulator(req.app);
|
||||
const accessController = ServerVariablesHandler.getAccessController(req.app);
|
||||
const authenticationMethodsCalculator =
|
||||
ServerVariablesHandler.getAuthenticationMethodCalculator(req.app);
|
||||
let authSession: AuthenticationSession.AuthenticationSession;
|
||||
|
||||
return BluebirdPromise.resolve()
|
||||
|
@ -32,19 +28,19 @@ export default function (req: express.Request, res: express.Response): BluebirdP
|
|||
if (!username || !password) {
|
||||
return BluebirdPromise.reject(new Error("No username or password."));
|
||||
}
|
||||
logger.info(req, "Starting authentication of user \"%s\"", username);
|
||||
vars.logger.info(req, "Starting authentication of user \"%s\"", username);
|
||||
return AuthenticationSession.get(req);
|
||||
})
|
||||
.then(function (_authSession: AuthenticationSession.AuthenticationSession) {
|
||||
authSession = _authSession;
|
||||
return regulator.regulate(username);
|
||||
return vars.regulator.regulate(username);
|
||||
})
|
||||
.then(function () {
|
||||
logger.info(req, "No regulation applied.");
|
||||
return ldap.authenticate(username, password);
|
||||
vars.logger.info(req, "No regulation applied.");
|
||||
return vars.ldapAuthenticator.authenticate(username, password);
|
||||
})
|
||||
.then(function (groupsAndEmails: GroupsAndEmails) {
|
||||
logger.info(req, "LDAP binding successful. Retrieved information about user are %s",
|
||||
vars.logger.info(req, "LDAP binding successful. Retrieved information about user are %s",
|
||||
JSON.stringify(groupsAndEmails));
|
||||
authSession.userid = username;
|
||||
authSession.first_factor = true;
|
||||
|
@ -56,26 +52,29 @@ export default function (req: express.Request, res: express.Response): BluebirdP
|
|||
const emails: string[] = groupsAndEmails.emails;
|
||||
const groups: string[] = groupsAndEmails.groups;
|
||||
const redirectHost: string = DomainExtractor.fromUrl(redirectUrl);
|
||||
const authMethod = authenticationMethodsCalculator.compute(redirectHost);
|
||||
logger.debug(req, "Authentication method for \"%s\" is \"%s\"", redirectHost, authMethod);
|
||||
const authMethod =
|
||||
new AuthenticationMethodCalculator(vars.config.authentication_methods)
|
||||
.compute(redirectHost);
|
||||
vars.logger.debug(req, "Authentication method for \"%s\" is \"%s\"", redirectHost, authMethod);
|
||||
|
||||
if (!emails || emails.length <= 0) {
|
||||
const errMessage = "No emails found. The user should have at least one email address to reset password.";
|
||||
logger.error(req, "%s", errMessage);
|
||||
const errMessage =
|
||||
"No emails found. The user should have at least one email address to reset password.";
|
||||
vars.logger.error(req, "%s", errMessage);
|
||||
return BluebirdPromise.reject(new Error(errMessage));
|
||||
}
|
||||
|
||||
authSession.email = emails[0];
|
||||
authSession.groups = groups;
|
||||
|
||||
logger.debug(req, "Mark successful authentication to regulator.");
|
||||
regulator.mark(username, true);
|
||||
vars.logger.debug(req, "Mark successful authentication to regulator.");
|
||||
vars.regulator.mark(username, true);
|
||||
|
||||
if (authMethod == "basic_auth") {
|
||||
res.send({
|
||||
redirect: redirectUrl
|
||||
});
|
||||
logger.debug(req, "Redirect to '%s'", redirectUrl);
|
||||
vars.logger.debug(req, "Redirect to '%s'", redirectUrl);
|
||||
}
|
||||
else if (authMethod == "two_factor") {
|
||||
let newRedirectUrl = Endpoint.SECOND_FACTOR_GET;
|
||||
|
@ -83,7 +82,7 @@ export default function (req: express.Request, res: express.Response): BluebirdP
|
|||
newRedirectUrl += "?" + Constants.REDIRECT_QUERY_PARAM + "="
|
||||
+ encodeURIComponent(redirectUrl);
|
||||
}
|
||||
logger.debug(req, "Redirect to '%s'", newRedirectUrl, typeof redirectUrl);
|
||||
vars.logger.debug(req, "Redirect to '%s'", newRedirectUrl, typeof redirectUrl);
|
||||
res.send({
|
||||
redirect: newRedirectUrl
|
||||
});
|
||||
|
@ -94,8 +93,9 @@ export default function (req: express.Request, res: express.Response): BluebirdP
|
|||
return BluebirdPromise.resolve();
|
||||
})
|
||||
.catch(Exceptions.LdapBindError, function (err: Error) {
|
||||
regulator.mark(username, false);
|
||||
return ErrorReplies.replyWithError200(req, res, logger, UserMessages.OPERATION_FAILED)(err);
|
||||
vars.regulator.mark(username, false);
|
||||
return ErrorReplies.replyWithError200(req, res, vars.logger, UserMessages.OPERATION_FAILED)(err);
|
||||
})
|
||||
.catch(ErrorReplies.replyWithError200(req, res, logger, UserMessages.OPERATION_FAILED));
|
||||
.catch(ErrorReplies.replyWithError200(req, res, vars.logger, UserMessages.OPERATION_FAILED));
|
||||
};
|
||||
}
|
|
@ -7,6 +7,7 @@ import { ServerVariablesHandler } from "../../ServerVariablesHandler";
|
|||
import AuthenticationSession = require("../../AuthenticationSession");
|
||||
import BluebirdPromise = require("bluebird");
|
||||
import ErrorReplies = require("../../ErrorReplies");
|
||||
import UserMessages = require("../../../../../shared/UserMessages");
|
||||
|
||||
export default function (req: express.Request, res: express.Response): BluebirdPromise<void> {
|
||||
const logger = ServerVariablesHandler.getLogger(req.app);
|
||||
|
@ -19,5 +20,5 @@ export default function (req: express.Request, res: express.Response): BluebirdP
|
|||
return BluebirdPromise.resolve();
|
||||
})
|
||||
.catch(ErrorReplies.replyWithError200(req, res, logger,
|
||||
"Unexpected error."));
|
||||
UserMessages.OPERATION_FAILED));
|
||||
}
|
|
@ -67,8 +67,8 @@ export default class RegistrationHandler implements IdentityValidable {
|
|||
}
|
||||
|
||||
const userDataStore = ServerVariablesHandler.getUserDataStore(req.app);
|
||||
const totpGenerator = ServerVariablesHandler.getTOTPGenerator(req.app);
|
||||
const secret = totpGenerator.generate();
|
||||
const totpHandler = ServerVariablesHandler.getTotpHandler(req.app);
|
||||
const secret = totpHandler.generate();
|
||||
|
||||
logger.debug(req, "Save the TOTP secret in DB");
|
||||
return userDataStore.saveTOTPSecret(userid, secret)
|
||||
|
|
|
@ -11,34 +11,34 @@ import ErrorReplies = require("../../../../ErrorReplies");
|
|||
import { ServerVariablesHandler } from "./../../../../ServerVariablesHandler";
|
||||
import AuthenticationSession = require("../../../../AuthenticationSession");
|
||||
import UserMessages = require("../../../../../../../shared/UserMessages");
|
||||
import { ServerVariables } from "../../../../ServerVariables";
|
||||
|
||||
const UNAUTHORIZED_MESSAGE = "Unauthorized access";
|
||||
|
||||
export default FirstFactorBlocker(handler);
|
||||
|
||||
export function handler(req: express.Request, res: express.Response): BluebirdPromise<void> {
|
||||
export default function (vars: ServerVariables) {
|
||||
function handler(req: express.Request, res: express.Response): BluebirdPromise<void> {
|
||||
let authSession: AuthenticationSession.AuthenticationSession;
|
||||
const logger = ServerVariablesHandler.getLogger(req.app);
|
||||
const token = req.body.token;
|
||||
const totpValidator = ServerVariablesHandler.getTOTPValidator(req.app);
|
||||
const userDataStore = ServerVariablesHandler.getUserDataStore(req.app);
|
||||
|
||||
return AuthenticationSession.get(req)
|
||||
.then(function (_authSession: AuthenticationSession.AuthenticationSession) {
|
||||
authSession = _authSession;
|
||||
logger.info(req, "Initiate TOTP validation for user '%s'.", authSession.userid);
|
||||
return userDataStore.retrieveTOTPSecret(authSession.userid);
|
||||
vars.logger.info(req, "Initiate TOTP validation for user '%s'.", authSession.userid);
|
||||
return vars.userDataStore.retrieveTOTPSecret(authSession.userid);
|
||||
})
|
||||
.then(function (doc: TOTPSecretDocument) {
|
||||
logger.debug(req, "TOTP secret is %s", JSON.stringify(doc));
|
||||
return totpValidator.validate(token, doc.secret.base32);
|
||||
})
|
||||
.then(function () {
|
||||
logger.debug(req, "TOTP validation succeeded.");
|
||||
vars.logger.debug(req, "TOTP secret is %s", JSON.stringify(doc));
|
||||
|
||||
if (!vars.totpHandler.validate(token, doc.secret.base32))
|
||||
return BluebirdPromise.reject(new Error("Invalid TOTP token."));
|
||||
|
||||
vars.logger.debug(req, "TOTP validation succeeded.");
|
||||
authSession.second_factor = true;
|
||||
redirect(req, res);
|
||||
return BluebirdPromise.resolve();
|
||||
})
|
||||
.catch(ErrorReplies.replyWithError200(req, res, logger,
|
||||
.catch(ErrorReplies.replyWithError200(req, res, vars.logger,
|
||||
UserMessages.OPERATION_FAILED));
|
||||
}
|
||||
return FirstFactorBlocker(handler);
|
||||
}
|
||||
|
|
|
@ -6,28 +6,63 @@ import exceptions = require("../../Exceptions");
|
|||
import winston = require("winston");
|
||||
import AuthenticationValidator = require("../../AuthenticationValidator");
|
||||
import ErrorReplies = require("../../ErrorReplies");
|
||||
import { ServerVariablesHandler } from "../../ServerVariablesHandler";
|
||||
import { AppConfiguration } from "../../configuration/Configuration";
|
||||
import AuthenticationSession = require("../../AuthenticationSession");
|
||||
import Constants = require("../../../../../shared/constants");
|
||||
import Util = require("util");
|
||||
import { DomainExtractor } from "../../utils/DomainExtractor";
|
||||
import { ServerVariables } from "../../ServerVariables";
|
||||
import { AuthenticationMethodCalculator } from "../../AuthenticationMethodCalculator";
|
||||
import { IRequestLogger } from "../../logging/IRequestLogger";
|
||||
|
||||
const FIRST_FACTOR_NOT_VALIDATED_MESSAGE = "First factor not yet validated";
|
||||
const SECOND_FACTOR_NOT_VALIDATED_MESSAGE = "Second factor not yet validated";
|
||||
|
||||
function verify_filter(req: express.Request, res: express.Response): BluebirdPromise<void> {
|
||||
const logger = ServerVariablesHandler.getLogger(req.app);
|
||||
const accessController = ServerVariablesHandler.getAccessController(req.app);
|
||||
const authenticationMethodsCalculator = ServerVariablesHandler.getAuthenticationMethodCalculator(req.app);
|
||||
const REMOTE_USER = "Remote-User";
|
||||
const REMOTE_GROUPS = "Remote-Groups";
|
||||
|
||||
function verify_inactivity(req: express.Request,
|
||||
authSession: AuthenticationSession.AuthenticationSession,
|
||||
configuration: AppConfiguration, logger: IRequestLogger)
|
||||
: BluebirdPromise<void> {
|
||||
|
||||
const lastActivityTime = authSession.last_activity_datetime;
|
||||
const currentTime = new Date().getTime();
|
||||
authSession.last_activity_datetime = currentTime;
|
||||
|
||||
// If inactivity is not specified, then inactivity timeout does not apply
|
||||
if (!configuration.session.inactivity) {
|
||||
return BluebirdPromise.resolve();
|
||||
}
|
||||
|
||||
const inactivityPeriodMs = currentTime - lastActivityTime;
|
||||
logger.debug(req, "Inactivity period was %s s and max period was %s.",
|
||||
inactivityPeriodMs / 1000, configuration.session.inactivity / 1000);
|
||||
if (inactivityPeriodMs < configuration.session.inactivity) {
|
||||
return BluebirdPromise.resolve();
|
||||
}
|
||||
|
||||
logger.debug(req, "Session has been reset after too long inactivity period.");
|
||||
AuthenticationSession.reset(req);
|
||||
return BluebirdPromise.reject(new Error("Inactivity period exceeded."));
|
||||
}
|
||||
|
||||
function verify_filter(req: express.Request, res: express.Response,
|
||||
vars: ServerVariables): BluebirdPromise<void> {
|
||||
let _authSession: AuthenticationSession.AuthenticationSession;
|
||||
let username: string;
|
||||
let groups: string[];
|
||||
|
||||
return AuthenticationSession.get(req)
|
||||
.then(function (authSession) {
|
||||
_authSession = authSession;
|
||||
username = _authSession.userid;
|
||||
groups = _authSession.groups;
|
||||
|
||||
res.set("Redirect", encodeURIComponent("https://" + req.headers["host"] +
|
||||
req.headers["x-original-uri"]));
|
||||
|
||||
const username = authSession.userid;
|
||||
const groups = authSession.groups;
|
||||
if (!authSession.userid)
|
||||
if (!_authSession.userid)
|
||||
return BluebirdPromise.reject(
|
||||
new exceptions.AccessDeniedError(FIRST_FACTOR_NOT_VALIDATED_MESSAGE));
|
||||
|
||||
|
@ -35,33 +70,40 @@ function verify_filter(req: express.Request, res: express.Response): BluebirdPro
|
|||
const path = objectPath.get<express.Request, string>(req, "headers.x-original-uri");
|
||||
|
||||
const domain = DomainExtractor.fromHostHeader(host);
|
||||
const authenticationMethod = authenticationMethodsCalculator.compute(domain);
|
||||
logger.debug(req, "domain=%s, path=%s, user=%s, groups=%s", domain, path,
|
||||
const authenticationMethod =
|
||||
new AuthenticationMethodCalculator(vars.config.authentication_methods)
|
||||
.compute(domain);
|
||||
vars.logger.debug(req, "domain=%s, path=%s, user=%s, groups=%s", domain, path,
|
||||
username, groups.join(","));
|
||||
|
||||
if (!authSession.first_factor)
|
||||
if (!_authSession.first_factor)
|
||||
return BluebirdPromise.reject(
|
||||
new exceptions.AccessDeniedError(FIRST_FACTOR_NOT_VALIDATED_MESSAGE));
|
||||
|
||||
const isAllowed = accessController.isAccessAllowed(domain, path, username, groups);
|
||||
if (!isAllowed) return BluebirdPromise.reject(
|
||||
new exceptions.DomainAccessDenied(Util.format("User '%s' does not have access to '%s'",
|
||||
username, domain)));
|
||||
|
||||
if (authenticationMethod == "two_factor" && !authSession.second_factor)
|
||||
if (authenticationMethod == "two_factor" && !_authSession.second_factor)
|
||||
return BluebirdPromise.reject(
|
||||
new exceptions.AccessDeniedError(SECOND_FACTOR_NOT_VALIDATED_MESSAGE));
|
||||
|
||||
res.setHeader("Remote-User", username);
|
||||
res.setHeader("Remote-Groups", groups.join(","));
|
||||
|
||||
const isAllowed = vars.accessController.isAccessAllowed(domain, path, username, groups);
|
||||
if (!isAllowed) return BluebirdPromise.reject(
|
||||
new exceptions.DomainAccessDenied(Util.format("User '%s' does not have access to '%s'",
|
||||
username, domain)));
|
||||
return BluebirdPromise.resolve();
|
||||
})
|
||||
.then(function () {
|
||||
return verify_inactivity(req, _authSession,
|
||||
vars.config, vars.logger);
|
||||
})
|
||||
.then(function () {
|
||||
res.setHeader(REMOTE_USER, username);
|
||||
res.setHeader(REMOTE_GROUPS, groups.join(","));
|
||||
});
|
||||
}
|
||||
|
||||
export default function (req: express.Request, res: express.Response): BluebirdPromise<void> {
|
||||
const logger = ServerVariablesHandler.getLogger(req.app);
|
||||
return verify_filter(req, res)
|
||||
export default function (vars: ServerVariables) {
|
||||
return function (req: express.Request, res: express.Response)
|
||||
: BluebirdPromise<void> {
|
||||
return verify_filter(req, res, vars)
|
||||
.then(function () {
|
||||
res.status(204);
|
||||
res.send();
|
||||
|
@ -69,8 +111,9 @@ export default function (req: express.Request, res: express.Response): BluebirdP
|
|||
})
|
||||
// The user is authenticated but has restricted access -> 403
|
||||
.catch(exceptions.DomainAccessDenied, ErrorReplies
|
||||
.replyWithError403(req, res, logger))
|
||||
.replyWithError403(req, res, vars.logger))
|
||||
// The user is not yet authenticated -> 401
|
||||
.catch(ErrorReplies.replyWithError401(req, res, logger));
|
||||
.catch(ErrorReplies.replyWithError401(req, res, vars.logger));
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
import assert = require("assert");
|
||||
import Assert = require("assert");
|
||||
import Sinon = require("sinon");
|
||||
import nedb = require("nedb");
|
||||
import express = require("express");
|
||||
|
@ -72,9 +72,10 @@ describe("test server configuration", function () {
|
|||
};
|
||||
|
||||
const server = new Server(deps);
|
||||
server.start(config, deps);
|
||||
|
||||
assert(sessionMock.calledOnce);
|
||||
assert.equal(sessionMock.getCall(0).args[0].cookie.domain, "example.com");
|
||||
server.start(config, deps)
|
||||
.then(function () {
|
||||
Assert(sessionMock.calledOnce);
|
||||
Assert.equal(sessionMock.getCall(0).args[0].cookie.domain, "example.com");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -113,6 +113,7 @@ describe("test session configuration builder", function () {
|
|||
domain: "example.com",
|
||||
expiration: 3600,
|
||||
secret: "secret",
|
||||
inactivity: 4000,
|
||||
redis: {
|
||||
host: "redis.example.com",
|
||||
port: 6379
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
|
||||
import { TOTPValidator } from "../src/lib/TOTPValidator";
|
||||
import Sinon = require("sinon");
|
||||
import Speakeasy = require("speakeasy");
|
||||
|
||||
describe("test TOTP validation", function() {
|
||||
let totpValidator: TOTPValidator;
|
||||
let totpValidateStub: Sinon.SinonStub;
|
||||
|
||||
beforeEach(() => {
|
||||
totpValidateStub = Sinon.stub(Speakeasy.totp, "verify");
|
||||
totpValidator = new TOTPValidator(Speakeasy);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
totpValidateStub.restore();
|
||||
});
|
||||
|
||||
it("should validate the TOTP token", function() {
|
||||
const totp_secret = "NBD2ZV64R9UV1O7K";
|
||||
const token = "token";
|
||||
totpValidateStub.withArgs({
|
||||
secret: totp_secret,
|
||||
token: token,
|
||||
encoding: "base32",
|
||||
window: 1
|
||||
}).returns(true);
|
||||
return totpValidator.validate(token, totp_secret);
|
||||
});
|
||||
|
||||
it("should not validate a wrong TOTP token", function(done) {
|
||||
const totp_secret = "NBD2ZV64R9UV1O7K";
|
||||
const token = "wrong token";
|
||||
totpValidateStub.returns(false);
|
||||
totpValidator.validate(token, totp_secret)
|
||||
.catch(function() {
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
|
||||
import { TotpHandler } from "../../../src/lib/authentication/totp/TotpHandler";
|
||||
import Sinon = require("sinon");
|
||||
import Speakeasy = require("speakeasy");
|
||||
import Assert = require("assert");
|
||||
|
||||
describe("test TOTP validation", function() {
|
||||
let totpValidator: TotpHandler;
|
||||
let validateStub: Sinon.SinonStub;
|
||||
|
||||
beforeEach(() => {
|
||||
validateStub = Sinon.stub(Speakeasy.totp, "verify");
|
||||
totpValidator = new TotpHandler(Speakeasy);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
validateStub.restore();
|
||||
});
|
||||
|
||||
it("should validate the TOTP token", function() {
|
||||
const totp_secret = "NBD2ZV64R9UV1O7K";
|
||||
const token = "token";
|
||||
validateStub.withArgs({
|
||||
secret: totp_secret,
|
||||
token: token,
|
||||
encoding: "base32",
|
||||
window: 1
|
||||
}).returns(true);
|
||||
Assert(totpValidator.validate(token, totp_secret));
|
||||
});
|
||||
|
||||
it("should not validate a wrong TOTP token", function() {
|
||||
const totp_secret = "NBD2ZV64R9UV1O7K";
|
||||
const token = "wrong token";
|
||||
validateStub.returns(false);
|
||||
Assert(!totpValidator.validate(token, totp_secret));
|
||||
});
|
||||
});
|
||||
|
|
@ -60,7 +60,23 @@ describe("test config parser", function () {
|
|||
});
|
||||
});
|
||||
|
||||
describe("test session configuration", function() {
|
||||
it("should get the session attributes", function () {
|
||||
const yaml_config = buildYamlConfig();
|
||||
yaml_config.session = {
|
||||
domain: "example.com",
|
||||
secret: "secret",
|
||||
expiration: 3600,
|
||||
inactivity: 4000
|
||||
};
|
||||
const config = ConfigurationParser.parse(yaml_config);
|
||||
Assert.equal(config.session.domain, "example.com");
|
||||
Assert.equal(config.session.secret, "secret");
|
||||
Assert.equal(config.session.expiration, 3600);
|
||||
Assert.equal(config.session.inactivity, 4000);
|
||||
});
|
||||
|
||||
it("should be ok not specifying inactivity", function () {
|
||||
const yaml_config = buildYamlConfig();
|
||||
yaml_config.session = {
|
||||
domain: "example.com",
|
||||
|
@ -71,6 +87,8 @@ describe("test config parser", function () {
|
|||
Assert.equal(config.session.domain, "example.com");
|
||||
Assert.equal(config.session.secret, "secret");
|
||||
Assert.equal(config.session.expiration, 3600);
|
||||
Assert.equal(config.session.inactivity, undefined);
|
||||
});
|
||||
});
|
||||
|
||||
it("should get the log level", function () {
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
import Sinon = require("sinon");
|
||||
import BluebirdPromise = require("bluebird");
|
||||
|
||||
import { INotifier } from "../../src/lib/notifiers/INotifier";
|
||||
|
||||
export class NotifierStub implements INotifier {
|
||||
notifyStub: Sinon.SinonStub;
|
||||
|
||||
constructor() {
|
||||
this.notifyStub = Sinon.stub();
|
||||
}
|
||||
|
||||
notify(to: string, subject: string, link: string): BluebirdPromise<void> {
|
||||
return this.notifyStub(to, subject, link);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
import Sinon = require("sinon");
|
||||
import BluebirdPromise = require("bluebird");
|
||||
import { IRegulator } from "../../src/lib/regulation/IRegulator";
|
||||
|
||||
export class RegulatorStub implements IRegulator {
|
||||
markStub: Sinon.SinonStub;
|
||||
regulateStub: Sinon.SinonStub;
|
||||
|
||||
constructor() {
|
||||
this.markStub = Sinon.stub();
|
||||
this.regulateStub = Sinon.stub();
|
||||
}
|
||||
|
||||
mark(userId: string, isAuthenticationSuccessful: boolean): BluebirdPromise<void> {
|
||||
return this.markStub(userId, isAuthenticationSuccessful);
|
||||
}
|
||||
|
||||
regulate(userId: string): BluebirdPromise<void> {
|
||||
return this.regulateStub(userId);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
import { ServerVariables } from "../../src/lib/ServerVariables";
|
||||
|
||||
import { AppConfiguration } from "../../src/lib/configuration/Configuration";
|
||||
import { AuthenticatorStub } from "./ldap/AuthenticatorStub";
|
||||
import { EmailsRetrieverStub } from "./ldap/EmailsRetrieverStub";
|
||||
import { PasswordUpdaterStub } from "./ldap/PasswordUpdaterStub";
|
||||
import { AccessControllerStub } from "./AccessControllerStub";
|
||||
import { RequestLoggerStub } from "./RequestLoggerStub";
|
||||
import { NotifierStub } from "./NotifierStub";
|
||||
import { RegulatorStub } from "./RegulatorStub";
|
||||
import { TotpHandlerStub } from "./TotpHandlerStub";
|
||||
import { UserDataStoreStub } from "./storage/UserDataStoreStub";
|
||||
import { U2fHandlerStub } from "./U2fHandlerStub";
|
||||
|
||||
export interface ServerVariablesMock {
|
||||
accessController: AccessControllerStub;
|
||||
config: AppConfiguration;
|
||||
ldapAuthenticator: AuthenticatorStub;
|
||||
ldapEmailsRetriever: EmailsRetrieverStub;
|
||||
ldapPasswordUpdater: PasswordUpdaterStub;
|
||||
logger: RequestLoggerStub;
|
||||
notifier: NotifierStub;
|
||||
regulator: RegulatorStub;
|
||||
totpHandler: TotpHandlerStub;
|
||||
userDataStore: UserDataStoreStub;
|
||||
u2f: U2fHandlerStub;
|
||||
}
|
||||
|
||||
export class ServerVariablesMockBuilder {
|
||||
static build(): { variables: ServerVariables, mocks: ServerVariablesMock} {
|
||||
const mocks: ServerVariablesMock = {
|
||||
accessController: new AccessControllerStub(),
|
||||
config: {
|
||||
access_control: {},
|
||||
authentication_methods: {
|
||||
default_method: "two_factor"
|
||||
},
|
||||
ldap: {
|
||||
url: "ldap://ldap",
|
||||
user: "user",
|
||||
password: "password",
|
||||
mail_attribute: "mail",
|
||||
users_dn: "ou=users,dc=example,dc=com",
|
||||
groups_dn: "ou=groups,dc=example,dc=com",
|
||||
users_filter: "cn={0}",
|
||||
groups_filter: "member={dn}",
|
||||
group_name_attribute: "cn"
|
||||
},
|
||||
logs_level: "debug",
|
||||
notifier: {},
|
||||
port: 8080,
|
||||
regulation: {
|
||||
ban_time: 50,
|
||||
find_time: 50,
|
||||
max_retries: 3
|
||||
},
|
||||
session: {
|
||||
secret: "my_secret"
|
||||
},
|
||||
storage: {}
|
||||
},
|
||||
ldapAuthenticator: new AuthenticatorStub(),
|
||||
ldapEmailsRetriever: new EmailsRetrieverStub(),
|
||||
ldapPasswordUpdater: new PasswordUpdaterStub(),
|
||||
logger: new RequestLoggerStub(),
|
||||
notifier: new NotifierStub(),
|
||||
regulator: new RegulatorStub(),
|
||||
totpHandler: new TotpHandlerStub(),
|
||||
userDataStore: new UserDataStoreStub(),
|
||||
u2f: new U2fHandlerStub()
|
||||
};
|
||||
const vars: ServerVariables = {
|
||||
accessController: mocks.accessController,
|
||||
config: mocks.config,
|
||||
ldapAuthenticator: mocks.ldapAuthenticator,
|
||||
ldapEmailsRetriever: mocks.ldapEmailsRetriever,
|
||||
ldapPasswordUpdater: mocks.ldapPasswordUpdater,
|
||||
logger: mocks.logger,
|
||||
notifier: mocks.notifier,
|
||||
regulator: mocks.regulator,
|
||||
totpHandler: mocks.totpHandler,
|
||||
userDataStore: mocks.userDataStore,
|
||||
u2f: mocks.u2f
|
||||
};
|
||||
|
||||
return {
|
||||
variables: vars,
|
||||
mocks: mocks
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
import Sinon = require("sinon");
|
||||
import BluebirdPromise = require("bluebird");
|
||||
import { ITotpHandler, GenerateSecretOptions } from "../../src/lib/authentication/totp/ITotpHandler";
|
||||
import { TOTPSecret } from "../../types/TOTPSecret";
|
||||
|
||||
export class TotpHandlerStub implements ITotpHandler {
|
||||
generateStub: Sinon.SinonStub;
|
||||
validateStub: Sinon.SinonStub;
|
||||
|
||||
constructor() {
|
||||
this.generateStub = Sinon.stub();
|
||||
this.validateStub = Sinon.stub();
|
||||
}
|
||||
|
||||
generate(options?: GenerateSecretOptions): TOTPSecret {
|
||||
return this.generateStub(options);
|
||||
}
|
||||
|
||||
validate(token: string, secret: string): boolean {
|
||||
return this.validateStub(token, secret);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
import Sinon = require("sinon");
|
||||
import BluebirdPromise = require("bluebird");
|
||||
import U2f = require("u2f");
|
||||
import { IU2fHandler } from "../../src/lib/authentication/u2f/IU2fHandler";
|
||||
|
||||
|
||||
export class U2fHandlerStub implements IU2fHandler {
|
||||
requestStub: Sinon.SinonStub;
|
||||
checkRegistrationStub: Sinon.SinonStub;
|
||||
checkSignatureStub: Sinon.SinonStub;
|
||||
|
||||
constructor() {
|
||||
this.requestStub = Sinon.stub();
|
||||
this.checkRegistrationStub = Sinon.stub();
|
||||
this.checkSignatureStub = Sinon.stub();
|
||||
}
|
||||
|
||||
request(appId: string, keyHandle?: string): U2f.Request {
|
||||
return this.requestStub(appId, keyHandle);
|
||||
}
|
||||
|
||||
checkRegistration(registrationRequest: U2f.Request, registrationResponse: U2f.RegistrationData)
|
||||
: U2f.RegistrationResult | U2f.Error {
|
||||
return this.checkRegistrationStub(registrationRequest, registrationResponse);
|
||||
}
|
||||
|
||||
checkSignature(signatureRequest: U2f.Request, signatureResponse: U2f.SignatureData, publicKey: string)
|
||||
: U2f.SignatureResult | U2f.Error {
|
||||
return this.checkSignatureStub(signatureRequest, signatureResponse, publicKey);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
import BluebirdPromise = require("bluebird");
|
||||
import { IAuthenticator } from "../../../src/lib/ldap/IAuthenticator";
|
||||
import { GroupsAndEmails } from "../../../src/lib/ldap/IClient";
|
||||
import Sinon = require("sinon");
|
||||
|
||||
export class AuthenticatorStub implements IAuthenticator {
|
||||
authenticateStub: Sinon.SinonStub;
|
||||
|
||||
constructor() {
|
||||
this.authenticateStub = Sinon.stub();
|
||||
}
|
||||
|
||||
authenticate(username: string, password: string): BluebirdPromise<GroupsAndEmails> {
|
||||
return this.authenticateStub(username, password);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
import BluebirdPromise = require("bluebird");
|
||||
import { IClient } from "../../../src/lib/ldap/IClient";
|
||||
import { IEmailsRetriever } from "../../../src/lib/ldap/IEmailsRetriever";
|
||||
import Sinon = require("sinon");
|
||||
|
||||
export class EmailsRetrieverStub implements IEmailsRetriever {
|
||||
retrieveStub: Sinon.SinonStub;
|
||||
|
||||
constructor() {
|
||||
this.retrieveStub = Sinon.stub();
|
||||
}
|
||||
|
||||
retrieve(username: string, client?: IClient): BluebirdPromise<string[]> {
|
||||
return this.retrieveStub(username, client);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
import BluebirdPromise = require("bluebird");
|
||||
import { IClient } from "../../../src/lib/ldap/IClient";
|
||||
import { IPasswordUpdater } from "../../../src/lib/ldap/IPasswordUpdater";
|
||||
import Sinon = require("sinon");
|
||||
|
||||
export class PasswordUpdaterStub implements IPasswordUpdater {
|
||||
updatePasswordStub: Sinon.SinonStub;
|
||||
|
||||
constructor() {
|
||||
this.updatePasswordStub = Sinon.stub();
|
||||
}
|
||||
|
||||
updatePassword(username: string, newPassword: string): BluebirdPromise<void> {
|
||||
return this.updatePasswordStub(username, newPassword);
|
||||
}
|
||||
}
|
|
@ -3,10 +3,10 @@ import Sinon = require("sinon");
|
|||
import BluebirdPromise = require("bluebird");
|
||||
import Assert = require("assert");
|
||||
|
||||
import { AuthenticationRegulator } from "../src/lib/AuthenticationRegulator";
|
||||
import { Regulator } from "../../src/lib/regulation/Regulator";
|
||||
import MockDate = require("mockdate");
|
||||
import exceptions = require("../src/lib/Exceptions");
|
||||
import { UserDataStoreStub } from "./mocks/storage/UserDataStoreStub";
|
||||
import exceptions = require("../../src/lib/Exceptions");
|
||||
import { UserDataStoreStub } from "../mocks/storage/UserDataStoreStub";
|
||||
|
||||
describe("test authentication regulator", function () {
|
||||
const USER1 = "USER1";
|
||||
|
@ -39,13 +39,13 @@ describe("test authentication regulator", function () {
|
|||
MockDate.reset();
|
||||
});
|
||||
|
||||
function markAuthenticationAt(regulator: AuthenticationRegulator, user: string, time: string, success: boolean) {
|
||||
function markAuthenticationAt(regulator: Regulator, user: string, time: string, success: boolean) {
|
||||
MockDate.set(time);
|
||||
return regulator.mark(user, success);
|
||||
}
|
||||
|
||||
it("should mark 2 authentication and regulate (accept)", function () {
|
||||
const regulator = new AuthenticationRegulator(userDataStoreStub, 3, 10, 10);
|
||||
const regulator = new Regulator(userDataStoreStub, 3, 10, 10);
|
||||
|
||||
return regulator.mark(USER1, false)
|
||||
.then(function () {
|
||||
|
@ -57,7 +57,7 @@ describe("test authentication regulator", function () {
|
|||
});
|
||||
|
||||
it("should mark 3 authentications and regulate (reject)", function () {
|
||||
const regulator = new AuthenticationRegulator(userDataStoreStub, 3, 10, 10);
|
||||
const regulator = new Regulator(userDataStoreStub, 3, 10, 10);
|
||||
|
||||
return regulator.mark(USER1, false)
|
||||
.then(function () {
|
||||
|
@ -76,7 +76,7 @@ describe("test authentication regulator", function () {
|
|||
});
|
||||
|
||||
it("should mark 1 failed, 1 successful and 1 failed authentications within minimum time and regulate (accept)", function () {
|
||||
const regulator = new AuthenticationRegulator(userDataStoreStub, 3, 60, 30);
|
||||
const regulator = new Regulator(userDataStoreStub, 3, 60, 30);
|
||||
|
||||
return markAuthenticationAt(regulator, USER1, "1/2/2000 00:00:00", false)
|
||||
.then(function () {
|
||||
|
@ -109,7 +109,7 @@ describe("test authentication regulator", function () {
|
|||
});
|
||||
|
||||
it("should regulate user if number of failures is greater than 3 in allowed time lapse", function () {
|
||||
function markAuthentications(regulator: AuthenticationRegulator, user: string) {
|
||||
function markAuthentications(regulator: Regulator, user: string) {
|
||||
return markAuthenticationAt(regulator, user, "1/2/2000 00:00:00", false)
|
||||
.then(function () {
|
||||
return markAuthenticationAt(regulator, user, "1/2/2000 00:00:45", false);
|
||||
|
@ -122,8 +122,8 @@ describe("test authentication regulator", function () {
|
|||
});
|
||||
}
|
||||
|
||||
const regulator1 = new AuthenticationRegulator(userDataStoreStub, 3, 60, 60);
|
||||
const regulator2 = new AuthenticationRegulator(userDataStoreStub, 3, 2 * 60, 60);
|
||||
const regulator1 = new Regulator(userDataStoreStub, 3, 60, 60);
|
||||
const regulator2 = new Regulator(userDataStoreStub, 3, 2 * 60, 60);
|
||||
|
||||
const p1 = markAuthentications(regulator1, USER1);
|
||||
const p2 = markAuthentications(regulator2, USER2);
|
||||
|
@ -138,7 +138,7 @@ describe("test authentication regulator", function () {
|
|||
});
|
||||
|
||||
it("should user wait after regulation to authenticate again", function () {
|
||||
function markAuthentications(regulator: AuthenticationRegulator, user: string) {
|
||||
function markAuthentications(regulator: Regulator, user: string) {
|
||||
return markAuthenticationAt(regulator, user, "1/2/2000 00:00:00", false)
|
||||
.then(function () {
|
||||
return markAuthenticationAt(regulator, user, "1/2/2000 00:00:10", false);
|
||||
|
@ -161,13 +161,13 @@ describe("test authentication regulator", function () {
|
|||
});
|
||||
}
|
||||
|
||||
const regulator = new AuthenticationRegulator(userDataStoreStub, 4, 30, 30);
|
||||
const regulator = new Regulator(userDataStoreStub, 4, 30, 30);
|
||||
return markAuthentications(regulator, USER1);
|
||||
});
|
||||
|
||||
it("should disable regulation when max_retries is set to 0", function () {
|
||||
const maxRetries = 0;
|
||||
const regulator = new AuthenticationRegulator(userDataStoreStub, maxRetries, 60, 30);
|
||||
const regulator = new Regulator(userDataStoreStub, maxRetries, 60, 30);
|
||||
return markAuthenticationAt(regulator, USER1, "1/2/2000 00:00:00", false)
|
||||
.then(function () {
|
||||
return markAuthenticationAt(regulator, USER1, "1/2/2000 00:00:10", false);
|
|
@ -1,8 +1,8 @@
|
|||
|
||||
import sinon = require("sinon");
|
||||
import Sinon = require("sinon");
|
||||
import BluebirdPromise = require("bluebird");
|
||||
import Assert = require("assert");
|
||||
import winston = require("winston");
|
||||
import Winston = require("winston");
|
||||
|
||||
import FirstFactorPost = require("../../../src/lib/routes/firstfactor/post");
|
||||
import exceptions = require("../../../src/lib/Exceptions");
|
||||
|
@ -12,7 +12,7 @@ import Endpoints = require("../../../../shared/api");
|
|||
import AuthenticationRegulatorMock = require("../../mocks/AuthenticationRegulator");
|
||||
import { AccessControllerStub } from "../../mocks/AccessControllerStub";
|
||||
import ExpressMock = require("../../mocks/express");
|
||||
import ServerVariablesMock = require("../../mocks/ServerVariablesMock");
|
||||
import { ServerVariablesMock, ServerVariablesMockBuilder } from "../../mocks/ServerVariablesMockBuilder";
|
||||
import { ServerVariables } from "../../../src/lib/ServerVariables";
|
||||
|
||||
describe("test the first factor validation route", function () {
|
||||
|
@ -20,32 +20,23 @@ describe("test the first factor validation route", function () {
|
|||
let res: ExpressMock.ResponseMock;
|
||||
let emails: string[];
|
||||
let groups: string[];
|
||||
let configuration;
|
||||
let regulator: AuthenticationRegulatorMock.AuthenticationRegulatorMock;
|
||||
let accessController: AccessControllerStub;
|
||||
let serverVariables: ServerVariables;
|
||||
let vars: ServerVariables;
|
||||
let mocks: ServerVariablesMock;
|
||||
|
||||
beforeEach(function () {
|
||||
configuration = {
|
||||
ldap: {
|
||||
base_dn: "ou=users,dc=example,dc=com",
|
||||
user_name_attribute: "uid"
|
||||
}
|
||||
};
|
||||
|
||||
emails = ["test_ok@example.com"];
|
||||
groups = ["group1", "group2" ];
|
||||
const s = ServerVariablesMockBuilder.build();
|
||||
mocks = s.mocks;
|
||||
vars = s.variables;
|
||||
|
||||
accessController = new AccessControllerStub();
|
||||
accessController.isAccessAllowedMock.returns(true);
|
||||
|
||||
regulator = AuthenticationRegulatorMock.AuthenticationRegulatorMock();
|
||||
regulator.regulate.returns(BluebirdPromise.resolve());
|
||||
regulator.mark.returns(BluebirdPromise.resolve());
|
||||
mocks.accessController.isAccessAllowedMock.returns(true);
|
||||
mocks.regulator.regulateStub.returns(BluebirdPromise.resolve());
|
||||
mocks.regulator.markStub.returns(BluebirdPromise.resolve());
|
||||
|
||||
req = {
|
||||
app: {
|
||||
get: sinon.stub().returns({ logger: winston })
|
||||
get: Sinon.stub().returns({ logger: Winston })
|
||||
},
|
||||
body: {
|
||||
username: "username",
|
||||
|
@ -62,20 +53,11 @@ describe("test the first factor validation route", function () {
|
|||
};
|
||||
|
||||
AuthenticationSession.reset(req as any);
|
||||
|
||||
serverVariables = ServerVariablesMock.mock(req.app);
|
||||
serverVariables.ldapAuthenticator = {
|
||||
authenticate: sinon.stub()
|
||||
} as any;
|
||||
serverVariables.config = configuration as any;
|
||||
serverVariables.regulator = regulator as any;
|
||||
serverVariables.accessController = accessController as any;
|
||||
|
||||
res = ExpressMock.ResponseMock();
|
||||
});
|
||||
|
||||
it("should reply with 204 if success", function () {
|
||||
(serverVariables.ldapAuthenticator as any).authenticate.withArgs("username", "password")
|
||||
mocks.ldapAuthenticator.authenticateStub.withArgs("username", "password")
|
||||
.returns(BluebirdPromise.resolve({
|
||||
emails: emails,
|
||||
groups: groups
|
||||
|
@ -84,7 +66,7 @@ describe("test the first factor validation route", function () {
|
|||
return AuthenticationSession.get(req as any)
|
||||
.then(function (_authSession: AuthenticationSession.AuthenticationSession) {
|
||||
authSession = _authSession;
|
||||
return FirstFactorPost.default(req as any, res as any);
|
||||
return FirstFactorPost.default(vars)(req as any, res as any);
|
||||
})
|
||||
.then(function () {
|
||||
Assert.equal("username", authSession.userid);
|
||||
|
@ -93,15 +75,15 @@ describe("test the first factor validation route", function () {
|
|||
});
|
||||
|
||||
it("should retrieve email from LDAP", function () {
|
||||
(serverVariables.ldapAuthenticator as any).authenticate.withArgs("username", "password")
|
||||
mocks.ldapAuthenticator.authenticateStub.withArgs("username", "password")
|
||||
.returns(BluebirdPromise.resolve([{ mail: ["test@example.com"] }]));
|
||||
return FirstFactorPost.default(req as any, res as any);
|
||||
return FirstFactorPost.default(vars)(req as any, res as any);
|
||||
});
|
||||
|
||||
it("should set first email address as user session variable", function () {
|
||||
const emails = ["test_ok@example.com"];
|
||||
let authSession: AuthenticationSession.AuthenticationSession;
|
||||
(serverVariables.ldapAuthenticator as any).authenticate.withArgs("username", "password")
|
||||
mocks.ldapAuthenticator.authenticateStub.withArgs("username", "password")
|
||||
.returns(BluebirdPromise.resolve({
|
||||
emails: emails,
|
||||
groups: groups
|
||||
|
@ -110,7 +92,7 @@ describe("test the first factor validation route", function () {
|
|||
return AuthenticationSession.get(req as any)
|
||||
.then(function (_authSession: AuthenticationSession.AuthenticationSession) {
|
||||
authSession = _authSession;
|
||||
return FirstFactorPost.default(req as any, res as any);
|
||||
return FirstFactorPost.default(vars)(req as any, res as any);
|
||||
})
|
||||
.then(function () {
|
||||
Assert.equal("test_ok@example.com", authSession.email);
|
||||
|
@ -118,13 +100,13 @@ describe("test the first factor validation route", function () {
|
|||
});
|
||||
|
||||
it("should return error message when LDAP authenticator throws", function () {
|
||||
(serverVariables.ldapAuthenticator as any).authenticate.withArgs("username", "password")
|
||||
mocks.ldapAuthenticator.authenticateStub.withArgs("username", "password")
|
||||
.returns(BluebirdPromise.reject(new exceptions.LdapBindError("Bad credentials")));
|
||||
|
||||
return FirstFactorPost.default(req as any, res as any)
|
||||
return FirstFactorPost.default(vars)(req as any, res as any)
|
||||
.then(function () {
|
||||
Assert.equal(res.status.getCall(0).args[0], 200);
|
||||
Assert.equal(regulator.mark.getCall(0).args[0], "username");
|
||||
Assert.equal(mocks.regulator.markStub.getCall(0).args[0], "username");
|
||||
Assert.deepEqual(res.send.getCall(0).args[0], {
|
||||
error: "Operation failed."
|
||||
});
|
||||
|
@ -133,8 +115,8 @@ describe("test the first factor validation route", function () {
|
|||
|
||||
it("should return error message when regulator rejects authentication", function () {
|
||||
const err = new exceptions.AuthenticationRegulationError("Authentication regulation...");
|
||||
regulator.regulate.returns(BluebirdPromise.reject(err));
|
||||
return FirstFactorPost.default(req as any, res as any)
|
||||
mocks.regulator.regulateStub.returns(BluebirdPromise.reject(err));
|
||||
return FirstFactorPost.default(vars)(req as any, res as any)
|
||||
.then(function () {
|
||||
Assert.equal(res.status.getCall(0).args[0], 200);
|
||||
Assert.deepEqual(res.send.getCall(0).args[0], {
|
||||
|
|
|
@ -1,41 +1,44 @@
|
|||
|
||||
import BluebirdPromise = require("bluebird");
|
||||
import sinon = require("sinon");
|
||||
import Sinon = require("sinon");
|
||||
import assert = require("assert");
|
||||
import winston = require("winston");
|
||||
|
||||
import exceptions = require("../../../../../src/lib/Exceptions");
|
||||
import AuthenticationSession = require("../../../../../src/lib/AuthenticationSession");
|
||||
import SignPost = require("../../../../../src/lib/routes/secondfactor/totp/sign/post");
|
||||
import { ServerVariables } from "../../../../../src/lib/ServerVariables";
|
||||
|
||||
import ExpressMock = require("../../../../mocks/express");
|
||||
import TOTPValidatorMock = require("../../../../mocks/TOTPValidator");
|
||||
import ServerVariablesMock = require("../../../../mocks/ServerVariablesMock");
|
||||
import { UserDataStoreStub } from "../../../../mocks/storage/UserDataStoreStub";
|
||||
import { ServerVariablesMock, ServerVariablesMockBuilder } from "../../../../mocks/ServerVariablesMockBuilder";
|
||||
|
||||
describe("test totp route", function () {
|
||||
let req: ExpressMock.RequestMock;
|
||||
let res: ExpressMock.ResponseMock;
|
||||
let totpValidator: TOTPValidatorMock.TOTPValidatorMock;
|
||||
let authSession: AuthenticationSession.AuthenticationSession;
|
||||
let vars: ServerVariables;
|
||||
let mocks: ServerVariablesMock;
|
||||
|
||||
beforeEach(function () {
|
||||
const app_get = sinon.stub();
|
||||
const s = ServerVariablesMockBuilder.build();
|
||||
vars = s.variables;
|
||||
mocks = s.mocks;
|
||||
const app_get = Sinon.stub();
|
||||
req = {
|
||||
app: {
|
||||
get: sinon.stub().returns({ logger: winston })
|
||||
get: Sinon.stub().returns({ logger: winston })
|
||||
},
|
||||
body: {
|
||||
token: "abc"
|
||||
},
|
||||
session: {}
|
||||
session: {},
|
||||
query: {
|
||||
redirect: "http://redirect"
|
||||
}
|
||||
};
|
||||
AuthenticationSession.reset(req as any);
|
||||
const mocks = ServerVariablesMock.mock(req.app);
|
||||
res = ExpressMock.ResponseMock();
|
||||
|
||||
const config = { totp_secret: "secret" };
|
||||
totpValidator = TOTPValidatorMock.TOTPValidatorMock();
|
||||
AuthenticationSession.reset(req as any);
|
||||
|
||||
const doc = {
|
||||
userid: "user",
|
||||
|
@ -44,9 +47,6 @@ describe("test totp route", function () {
|
|||
}
|
||||
};
|
||||
mocks.userDataStore.retrieveTOTPSecretStub.returns(BluebirdPromise.resolve(doc));
|
||||
mocks.totpValidator = totpValidator;
|
||||
mocks.config = config;
|
||||
|
||||
return AuthenticationSession.get(req as any)
|
||||
.then(function (_authSession: AuthenticationSession.AuthenticationSession) {
|
||||
authSession = _authSession;
|
||||
|
@ -58,8 +58,8 @@ describe("test totp route", function () {
|
|||
|
||||
|
||||
it("should send status code 200 when totp is valid", function () {
|
||||
totpValidator.validate.returns(BluebirdPromise.resolve("ok"));
|
||||
return SignPost.default(req as any, res as any)
|
||||
mocks.totpHandler.validateStub.returns(true);
|
||||
return SignPost.default(vars)(req as any, res as any)
|
||||
.then(function () {
|
||||
assert.equal(true, authSession.second_factor);
|
||||
return BluebirdPromise.resolve();
|
||||
|
@ -67,8 +67,8 @@ describe("test totp route", function () {
|
|||
});
|
||||
|
||||
it("should send error message when totp is not valid", function () {
|
||||
totpValidator.validate.returns(BluebirdPromise.reject(new exceptions.InvalidTOTPError("Bad TOTP token")));
|
||||
return SignPost.default(req as any, res as any)
|
||||
mocks.totpHandler.validateStub.returns(false);
|
||||
return SignPost.default(vars)(req as any, res as any)
|
||||
.then(function () {
|
||||
assert.equal(false, authSession.second_factor);
|
||||
assert.equal(res.status.getCall(0).args[0], 200);
|
||||
|
@ -80,9 +80,9 @@ describe("test totp route", function () {
|
|||
});
|
||||
|
||||
it("should send status code 401 when session has not been initiated", function () {
|
||||
totpValidator.validate.returns(BluebirdPromise.resolve("abc"));
|
||||
mocks.totpHandler.validateStub.returns(true);
|
||||
req.session = {};
|
||||
return SignPost.default(req as any, res as any)
|
||||
return SignPost.default(vars)(req as any, res as any)
|
||||
.then(function () { return BluebirdPromise.reject(new Error("It should fail")); })
|
||||
.catch(function () {
|
||||
assert.equal(401, res.status.getCall(0).args[0]);
|
||||
|
|
|
@ -4,27 +4,21 @@ import VerifyGet = require("../../../src/lib/routes/verify/get");
|
|||
import AuthenticationSession = require("../../../src/lib/AuthenticationSession");
|
||||
import { AuthenticationMethodCalculator } from "../../../src/lib/AuthenticationMethodCalculator";
|
||||
import { AuthenticationMethodsConfiguration } from "../../../src/lib/configuration/Configuration";
|
||||
|
||||
import Sinon = require("sinon");
|
||||
import winston = require("winston");
|
||||
import BluebirdPromise = require("bluebird");
|
||||
|
||||
import express = require("express");
|
||||
|
||||
import ExpressMock = require("../../mocks/express");
|
||||
import { AccessControllerStub } from "../../mocks/AccessControllerStub";
|
||||
import ServerVariablesMock = require("../../mocks/ServerVariablesMock");
|
||||
import { ServerVariables } from "../../../src/lib/ServerVariables";
|
||||
import { ServerVariablesMockBuilder, ServerVariablesMock } from "../../mocks/ServerVariablesMockBuilder";
|
||||
|
||||
describe("test authentication token verification", function () {
|
||||
describe("test /verify endpoint", function () {
|
||||
let req: ExpressMock.RequestMock;
|
||||
let res: ExpressMock.ResponseMock;
|
||||
let accessController: AccessControllerStub;
|
||||
let mocks: any;
|
||||
let mocks: ServerVariablesMock;
|
||||
let vars: ServerVariables;
|
||||
|
||||
beforeEach(function () {
|
||||
accessController = new AccessControllerStub();
|
||||
accessController.isAccessAllowedMock.returns(true);
|
||||
|
||||
req = ExpressMock.RequestMock();
|
||||
res = ExpressMock.ResponseMock();
|
||||
req.session = {};
|
||||
|
@ -37,20 +31,14 @@ describe("test authentication token verification", function () {
|
|||
AuthenticationSession.reset(req as any);
|
||||
req.headers = {};
|
||||
req.headers.host = "secret.example.com";
|
||||
mocks = ServerVariablesMock.mock(req.app);
|
||||
mocks.config = {} as any;
|
||||
mocks.accessController = accessController as any;
|
||||
const options: AuthenticationMethodsConfiguration = {
|
||||
default_method: "two_factor",
|
||||
per_subdomain_methods: {
|
||||
"redirect.url": "basic_auth"
|
||||
}
|
||||
};
|
||||
mocks.authenticationMethodsCalculator = new AuthenticationMethodCalculator(options);
|
||||
const s = ServerVariablesMockBuilder.build();
|
||||
mocks = s.mocks;
|
||||
vars = s.variables;
|
||||
});
|
||||
|
||||
it("should be already authenticated", function () {
|
||||
req.session = {};
|
||||
mocks.accessController.isAccessAllowedMock.returns(true);
|
||||
AuthenticationSession.reset(req as any);
|
||||
return AuthenticationSession.get(req as any)
|
||||
.then(function (authSession: AuthenticationSession.AuthenticationSession) {
|
||||
|
@ -58,7 +46,7 @@ describe("test authentication token verification", function () {
|
|||
authSession.second_factor = true;
|
||||
authSession.userid = "myuser";
|
||||
authSession.groups = ["mygroup", "othergroup"];
|
||||
return VerifyGet.default(req as express.Request, res as any);
|
||||
return VerifyGet.default(vars)(req as express.Request, res as any);
|
||||
})
|
||||
.then(function () {
|
||||
Sinon.assert.calledWithExactly(res.setHeader, "Remote-User", "myuser");
|
||||
|
@ -71,26 +59,30 @@ describe("test authentication token verification", function () {
|
|||
return AuthenticationSession.get(req as any)
|
||||
.then(function (authSession) {
|
||||
authSession = _authSession;
|
||||
return VerifyGet.default(req as express.Request, res as any);
|
||||
return VerifyGet.default(vars)(req as express.Request, res as any);
|
||||
})
|
||||
.then(function () {
|
||||
Assert.equal(status_code, res.status.getCall(0).args[0]);
|
||||
});
|
||||
}
|
||||
|
||||
function test_non_authenticated_401(auth_session: AuthenticationSession.AuthenticationSession) {
|
||||
return test_session(auth_session, 401);
|
||||
function test_non_authenticated_401(authSession: AuthenticationSession.AuthenticationSession) {
|
||||
return test_session(authSession, 401);
|
||||
}
|
||||
|
||||
function test_unauthorized_403(auth_session: AuthenticationSession.AuthenticationSession) {
|
||||
return test_session(auth_session, 403);
|
||||
function test_unauthorized_403(authSession: AuthenticationSession.AuthenticationSession) {
|
||||
return test_session(authSession, 403);
|
||||
}
|
||||
|
||||
function test_authorized(auth_session: AuthenticationSession.AuthenticationSession) {
|
||||
return test_session(auth_session, 204);
|
||||
function test_authorized(authSession: AuthenticationSession.AuthenticationSession) {
|
||||
return test_session(authSession, 204);
|
||||
}
|
||||
|
||||
describe("given user tries to access a 2-factor endpoint", function () {
|
||||
before(function () {
|
||||
mocks.accessController.isAccessAllowedMock.returns(true);
|
||||
});
|
||||
|
||||
describe("given different cases of session", function () {
|
||||
it("should not be authenticated when second factor is missing", function () {
|
||||
return test_non_authenticated_401({
|
||||
|
@ -99,6 +91,7 @@ describe("test authentication token verification", function () {
|
|||
second_factor: false,
|
||||
email: undefined,
|
||||
groups: [],
|
||||
last_activity_datetime: new Date().getTime()
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -109,6 +102,7 @@ describe("test authentication token verification", function () {
|
|||
second_factor: true,
|
||||
email: undefined,
|
||||
groups: [],
|
||||
last_activity_datetime: new Date().getTime()
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -119,6 +113,7 @@ describe("test authentication token verification", function () {
|
|||
second_factor: false,
|
||||
email: undefined,
|
||||
groups: [],
|
||||
last_activity_datetime: new Date().getTime()
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -129,6 +124,7 @@ describe("test authentication token verification", function () {
|
|||
second_factor: false,
|
||||
email: undefined,
|
||||
groups: [],
|
||||
last_activity_datetime: new Date().getTime()
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -138,22 +134,20 @@ describe("test authentication token verification", function () {
|
|||
|
||||
it("should not be authenticated when domain is not allowed for user", function () {
|
||||
return AuthenticationSession.get(req as any)
|
||||
.then(function (authSession: AuthenticationSession.AuthenticationSession) {
|
||||
.then(function (authSession) {
|
||||
authSession.first_factor = true;
|
||||
authSession.second_factor = true;
|
||||
authSession.userid = "myuser";
|
||||
|
||||
req.headers.host = "test.example.com";
|
||||
|
||||
accessController.isAccessAllowedMock.returns(false);
|
||||
accessController.isAccessAllowedMock.withArgs("test.example.com", "user", ["group1", "group2"]).returns(true);
|
||||
mocks.accessController.isAccessAllowedMock.returns(false);
|
||||
|
||||
return test_unauthorized_403({
|
||||
first_factor: true,
|
||||
second_factor: true,
|
||||
userid: "user",
|
||||
groups: ["group1", "group2"],
|
||||
email: undefined
|
||||
email: undefined,
|
||||
last_activity_datetime: new Date().getTime()
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -166,14 +160,18 @@ describe("test authentication token verification", function () {
|
|||
redirect: "http://redirect.url"
|
||||
};
|
||||
req.headers["host"] = "redirect.url";
|
||||
mocks.config.authentication_methods.per_subdomain_methods = {
|
||||
"redirect.url": "basic_auth"
|
||||
};
|
||||
});
|
||||
|
||||
it("should be authenticated when first factor is validated and not second factor", function () {
|
||||
it("should be authenticated when first factor is validated and second factor is not", function () {
|
||||
mocks.accessController.isAccessAllowedMock.returns(true);
|
||||
return AuthenticationSession.get(req as any)
|
||||
.then(function (authSession: AuthenticationSession.AuthenticationSession) {
|
||||
.then(function (authSession) {
|
||||
authSession.first_factor = true;
|
||||
authSession.userid = "user1";
|
||||
return VerifyGet.default(req as express.Request, res as any);
|
||||
return VerifyGet.default(vars)(req as express.Request, res as any);
|
||||
})
|
||||
.then(function () {
|
||||
Assert(res.status.calledWith(204));
|
||||
|
@ -182,15 +180,64 @@ describe("test authentication token verification", function () {
|
|||
});
|
||||
|
||||
it("should be rejected with 401 when first factor is not validated", function () {
|
||||
mocks.accessController.isAccessAllowedMock.returns(true);
|
||||
return AuthenticationSession.get(req as any)
|
||||
.then(function (authSession: AuthenticationSession.AuthenticationSession) {
|
||||
.then(function (authSession) {
|
||||
authSession.first_factor = false;
|
||||
return VerifyGet.default(req as express.Request, res as any);
|
||||
return VerifyGet.default(vars)(req as express.Request, res as any);
|
||||
})
|
||||
.then(function () {
|
||||
Assert(res.status.calledWith(401));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("inactivity period", function () {
|
||||
it("should update last inactivity period on requests on /verify", function () {
|
||||
mocks.config.session.inactivity = 200000;
|
||||
mocks.accessController.isAccessAllowedMock.returns(true);
|
||||
const currentTime = new Date().getTime() - 1000;
|
||||
AuthenticationSession.reset(req as any);
|
||||
return AuthenticationSession.get(req as any)
|
||||
.then(function (authSession: AuthenticationSession.AuthenticationSession) {
|
||||
authSession.first_factor = true;
|
||||
authSession.second_factor = true;
|
||||
authSession.userid = "myuser";
|
||||
authSession.groups = ["mygroup", "othergroup"];
|
||||
authSession.last_activity_datetime = currentTime;
|
||||
return VerifyGet.default(vars)(req as express.Request, res as any);
|
||||
})
|
||||
.then(function () {
|
||||
return AuthenticationSession.get(req as any);
|
||||
})
|
||||
.then(function (authSession) {
|
||||
Assert(authSession.last_activity_datetime > currentTime);
|
||||
});
|
||||
});
|
||||
|
||||
it("should reset session when max inactivity period has been reached", function () {
|
||||
mocks.config.session.inactivity = 1;
|
||||
mocks.accessController.isAccessAllowedMock.returns(true);
|
||||
const currentTime = new Date().getTime() - 1000;
|
||||
AuthenticationSession.reset(req as any);
|
||||
return AuthenticationSession.get(req as any)
|
||||
.then(function (authSession: AuthenticationSession.AuthenticationSession) {
|
||||
authSession.first_factor = true;
|
||||
authSession.second_factor = true;
|
||||
authSession.userid = "myuser";
|
||||
authSession.groups = ["mygroup", "othergroup"];
|
||||
authSession.last_activity_datetime = currentTime;
|
||||
return VerifyGet.default(vars)(req as express.Request, res as any);
|
||||
})
|
||||
.then(function () {
|
||||
return AuthenticationSession.get(req as any);
|
||||
})
|
||||
.then(function (authSession) {
|
||||
Assert.equal(authSession.first_factor, false);
|
||||
Assert.equal(authSession.second_factor, false);
|
||||
Assert.equal(authSession.userid, undefined);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -20,8 +20,6 @@ Feature: User is correctly redirected
|
|||
And I visit "https://admin.test.local:8080/secret.html"
|
||||
Then I get an error 403
|
||||
|
||||
|
||||
|
||||
Scenario: Redirection URL is propagated from restricted page to first factor
|
||||
When I visit "https://public.test.local:8080/secret.html"
|
||||
Then I'm redirected to "https://auth.test.local:8080/?redirect=https%3A%2F%2Fpublic.test.local%3A8080%2Fsecret.html"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
@needs-regulation-config
|
||||
Feature: Authelia regulates authentication to avoid brute force
|
||||
|
||||
@needs-test-config
|
||||
@need-registered-user-blackhat
|
||||
Scenario: Attacker tries too many authentication in a short period of time and get banned
|
||||
Given I visit "https://auth.test.local:8080/"
|
||||
|
@ -18,7 +18,6 @@ Feature: Authelia regulates authentication to avoid brute force
|
|||
And I click on "Sign in"
|
||||
Then I get a notification of type "error" with message "Authentication failed. Please check your credentials."
|
||||
|
||||
@needs-test-config
|
||||
@need-registered-user-blackhat
|
||||
Scenario: User is unbanned after a configured amount of time
|
||||
Given I visit "https://auth.test.local:8080/?redirect=https%3A%2F%2Fpublic.test.local%3A8080%2Fsecret.html"
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
@needs-inactivity-config
|
||||
Feature: Session is closed after a certain amount of time
|
||||
|
||||
@need-authenticated-user-john
|
||||
Scenario: An authenticated user is disconnected after a certain inactivity period
|
||||
Given I have access to:
|
||||
| url |
|
||||
| https://public.test.local:8080/secret.html |
|
||||
When I sleep for 6 seconds
|
||||
And I visit "https://public.test.local:8080/secret.html"
|
||||
Then I'm redirected to "https://auth.test.local:8080/?redirect=https%3A%2F%2Fpublic.test.local%3A8080%2Fsecret.html"
|
||||
|
||||
@need-authenticated-user-john
|
||||
Scenario: An authenticated user is disconnected after session expiration period
|
||||
Given I have access to:
|
||||
| url |
|
||||
| https://public.test.local:8080/secret.html |
|
||||
When I sleep for 4 seconds
|
||||
And I visit "https://public.test.local:8080/secret.html"
|
||||
And I sleep for 4 seconds
|
||||
And I visit "https://public.test.local:8080/secret.html"
|
||||
And I sleep for 4 seconds
|
||||
And I visit "https://public.test.local:8080/secret.html"
|
||||
Then I'm redirected to "https://auth.test.local:8080/?redirect=https%3A%2F%2Fpublic.test.local%3A8080%2Fsecret.html"
|
|
@ -6,7 +6,7 @@ import { UserDataStore } from "../../../server/src/lib/storage/UserDataStore";
|
|||
import { CollectionFactoryFactory } from "../../../server/src/lib/storage/CollectionFactoryFactory";
|
||||
import { MongoConnector } from "../../../server/src/lib/connectors/mongo/MongoConnector";
|
||||
import { IMongoClient } from "../../../server/src/lib/connectors/mongo/IMongoClient";
|
||||
import { TOTPGenerator } from "../../../server/src/lib/TOTPGenerator";
|
||||
import { TotpHandler } from "../../../server/src/lib/authentication/totp/TotpHandler";
|
||||
import Speakeasy = require("speakeasy");
|
||||
|
||||
Cucumber.defineSupportCode(function ({ setDefaultTimeout }) {
|
||||
|
@ -14,19 +14,46 @@ Cucumber.defineSupportCode(function ({ setDefaultTimeout }) {
|
|||
});
|
||||
|
||||
Cucumber.defineSupportCode(function ({ After, Before }) {
|
||||
const exec = BluebirdPromise.promisify(ChildProcess.exec);
|
||||
const exec = BluebirdPromise.promisify<any, any>(ChildProcess.exec);
|
||||
|
||||
After(function () {
|
||||
return this.driver.quit();
|
||||
});
|
||||
|
||||
Before({ tags: "@needs-test-config", timeout: 20 * 1000 }, function () {
|
||||
return exec("./scripts/example-commit/dc-example.sh -f docker-compose.test.yml up -d authelia && sleep 2");
|
||||
function createRegulationConfiguration(): BluebirdPromise<void> {
|
||||
return exec("\
|
||||
cat config.template.yml | \
|
||||
sed 's/find_time: [0-9]\\+/find_time: 15/' | \
|
||||
sed 's/ban_time: [0-9]\\+/ban_time: 4/' > config.test.yml \
|
||||
");
|
||||
}
|
||||
|
||||
function createInactivityConfiguration(): BluebirdPromise<void> {
|
||||
return exec("\
|
||||
cat config.template.yml | \
|
||||
sed 's/expiration: [0-9]\\+/expiration: 10000/' | \
|
||||
sed 's/inactivity: [0-9]\\+/inactivity: 5000/' > config.test.yml \
|
||||
");
|
||||
}
|
||||
|
||||
function declareNeedsConfiguration(tag: string, cb: () => BluebirdPromise<void>) {
|
||||
Before({ tags: "@needs-" + tag + "-config", timeout: 20 * 1000 }, function () {
|
||||
return cb()
|
||||
.then(function () {
|
||||
return exec("./scripts/example-commit/dc-example.sh -f docker-compose.test.yml up -d authelia && sleep 1");
|
||||
})
|
||||
});
|
||||
|
||||
After({ tags: "@needs-test-config", timeout: 20 * 1000 }, function () {
|
||||
return exec("./scripts/example-commit/dc-example.sh up -d authelia && sleep 2");
|
||||
After({ tags: "@needs-" + tag + "-config", timeout: 20 * 1000 }, function () {
|
||||
return exec("rm config.test.yml")
|
||||
.then(function () {
|
||||
return exec("./scripts/example-commit/dc-example.sh up -d authelia && sleep 1");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
declareNeedsConfiguration("regulation", createRegulationConfiguration);
|
||||
declareNeedsConfiguration("inactivity", createInactivityConfiguration);
|
||||
|
||||
function registerUser(context: any, username: string) {
|
||||
let secret: Speakeasy.Key;
|
||||
|
@ -36,7 +63,7 @@ Cucumber.defineSupportCode(function ({ After, Before }) {
|
|||
const collectionFactory = CollectionFactoryFactory.createMongo(mongoClient);
|
||||
const userDataStore = new UserDataStore(collectionFactory);
|
||||
|
||||
const generator = new TOTPGenerator(Speakeasy);
|
||||
const generator = new TotpHandler(Speakeasy);
|
||||
secret = generator.generate();
|
||||
return userDataStore.saveTOTPSecret(username, secret);
|
||||
})
|
||||
|
@ -56,7 +83,10 @@ Cucumber.defineSupportCode(function ({ After, Before }) {
|
|||
}
|
||||
|
||||
function needAuthenticatedUser(context: any, username: string): BluebirdPromise<void> {
|
||||
return context.visit("https://auth.test.local:8080/")
|
||||
return context.visit("https://auth.test.local:8080/logout")
|
||||
.then(function () {
|
||||
return context.visit("https://auth.test.local:8080/");
|
||||
})
|
||||
.then(function () {
|
||||
return registerUser(context, username);
|
||||
})
|
||||
|
@ -66,7 +96,7 @@ Cucumber.defineSupportCode(function ({ After, Before }) {
|
|||
.then(function () {
|
||||
return context.useTotpTokenHandle("REGISTERED");
|
||||
})
|
||||
.then(function() {
|
||||
.then(function () {
|
||||
return context.clickOnButton("TOTP");
|
||||
});
|
||||
}
|
||||
|
|
|
@ -7,6 +7,6 @@ import BluebirdPromise = require("bluebird");
|
|||
Cucumber.defineSupportCode(function ({ Given, When, Then }) {
|
||||
When(/^the application restarts$/, {timeout: 15 * 1000}, function () {
|
||||
const exec = BluebirdPromise.promisify(ChildProcess.exec);
|
||||
return exec("./scripts/example-commit/dc-example.sh restart authelia && sleep 2");
|
||||
return exec("./scripts/example-commit/dc-example.sh restart authelia && sleep 1");
|
||||
});
|
||||
});
|
|
@ -0,0 +1,8 @@
|
|||
import Cucumber = require("cucumber");
|
||||
import seleniumWebdriver = require("selenium-webdriver");
|
||||
|
||||
Cucumber.defineSupportCode(function ({ Given, When, Then }) {
|
||||
When("I sleep for {number} seconds", function (seconds: number) {
|
||||
return this.driver.sleep(seconds * 1000);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue