diff --git a/Gruntfile.js b/Gruntfile.js index 5564951cf..e0400f536 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -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']); diff --git a/config.template.yml b/config.template.yml index 83f1fed72..c0239420e 100644 --- a/config.template.yml +++ b/config.template.yml @@ -153,8 +153,8 @@ 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 domain to protect. # Note: the authenticator must also be in that domain. If empty, the cookie diff --git a/config.test.yml b/config.test.yml index ca70f5275..8f22fd83a 100644 --- a/config.test.yml +++ b/config.test.yml @@ -133,7 +133,7 @@ session: secret: unsecure_secret # The time before the cookie expires. - expiration: 3600000 + expiration: 10000 # The domain to protect. # Note: the authenticator must also be in that domain. If empty, the cookie diff --git a/scripts/travis.sh b/scripts/travis.sh index 332b24501..bd6ac908c 100755 --- a/scripts/travis.sh +++ b/scripts/travis.sh @@ -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 diff --git a/server/src/lib/AuthenticationMethodCalculator.ts b/server/src/lib/AuthenticationMethodCalculator.ts index 3791a9dbe..8f0810dcf 100644 --- a/server/src/lib/AuthenticationMethodCalculator.ts +++ b/server/src/lib/AuthenticationMethodCalculator.ts @@ -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; } } \ No newline at end of file diff --git a/server/src/lib/AuthenticationSession.ts b/server/src/lib/AuthenticationSession.ts index 9414d55d7..ad692f96f 100644 --- a/server/src/lib/AuthenticationSession.ts +++ b/server/src/lib/AuthenticationSession.ts @@ -9,6 +9,7 @@ export interface AuthenticationSession { userid: string; first_factor: boolean; second_factor: boolean; + last_activity_datetime: Date; 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(); } export function get(req: express.Request): BluebirdPromise { diff --git a/server/src/lib/RestApi.ts b/server/src/lib/RestApi.ts index 34686c719..74a240d09 100644 --- a/server/src/lib/RestApi.ts +++ b/server/src/lib/RestApi.ts @@ -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)); diff --git a/server/src/lib/Server.ts b/server/src/lib/Server.ts index f043d4f33..c0538e680 100644 --- a/server/src/lib/Server.ts +++ b/server/src/lib/Server.ts @@ -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 { - 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) { diff --git a/server/src/lib/ServerVariables.ts b/server/src/lib/ServerVariables.ts index 065a1dafa..a7b1e2e5e 100644 --- a/server/src/lib/ServerVariables.ts +++ b/server/src/lib/ServerVariables.ts @@ -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; } \ No newline at end of file diff --git a/server/src/lib/ServerVariablesHandler.ts b/server/src/lib/ServerVariablesHandler.ts index caf7a6332..c0a84765f 100644 --- a/server/src/lib/ServerVariablesHandler.ts +++ b/server/src/lib/ServerVariablesHandler.ts @@ -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 { +export class ServerVariablesInitializer { + static initialize(config: Configuration.AppConfiguration, requestLogger: IRequestLogger, + deps: GlobalDependencies): BluebirdPromise { 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,15 +96,18 @@ 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 { @@ -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; - } } diff --git a/server/src/lib/TOTPGenerator.ts b/server/src/lib/TOTPGenerator.ts deleted file mode 100644 index c0c7133bf..000000000 --- a/server/src/lib/TOTPGenerator.ts +++ /dev/null @@ -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); - } -} \ No newline at end of file diff --git a/server/src/lib/TOTPValidator.ts b/server/src/lib/TOTPValidator.ts deleted file mode 100644 index d99be78b8..000000000 --- a/server/src/lib/TOTPValidator.ts +++ /dev/null @@ -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 { - 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.")); - } -} \ No newline at end of file diff --git a/server/src/lib/authentication/totp/ITotpHandler.ts b/server/src/lib/authentication/totp/ITotpHandler.ts new file mode 100644 index 000000000..2830790f5 --- /dev/null +++ b/server/src/lib/authentication/totp/ITotpHandler.ts @@ -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; +} \ No newline at end of file diff --git a/server/src/lib/authentication/totp/TotpHandler.ts b/server/src/lib/authentication/totp/TotpHandler.ts new file mode 100644 index 000000000..9e1419e92 --- /dev/null +++ b/server/src/lib/authentication/totp/TotpHandler.ts @@ -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); + } +} \ No newline at end of file diff --git a/server/src/lib/authentication/u2f/IU2fHandler.ts b/server/src/lib/authentication/u2f/IU2fHandler.ts new file mode 100644 index 000000000..b9b7d6f26 --- /dev/null +++ b/server/src/lib/authentication/u2f/IU2fHandler.ts @@ -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; +} \ No newline at end of file diff --git a/server/src/lib/authentication/u2f/U2fHandler.ts b/server/src/lib/authentication/u2f/U2fHandler.ts new file mode 100644 index 000000000..bf3891e5b --- /dev/null +++ b/server/src/lib/authentication/u2f/U2fHandler.ts @@ -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); + } +} diff --git a/server/src/lib/regulation/IRegulator.ts b/server/src/lib/regulation/IRegulator.ts new file mode 100644 index 000000000..c49425b24 --- /dev/null +++ b/server/src/lib/regulation/IRegulator.ts @@ -0,0 +1,6 @@ +import BluebirdPromise = require("bluebird"); + +export interface IRegulator { + mark(userId: string, isAuthenticationSuccessful: boolean): BluebirdPromise; + regulate(userId: string): BluebirdPromise; +} \ No newline at end of file diff --git a/server/src/lib/AuthenticationRegulator.ts b/server/src/lib/regulation/Regulator.ts similarity index 87% rename from server/src/lib/AuthenticationRegulator.ts rename to server/src/lib/regulation/Regulator.ts index a04547335..1037a6a17 100644 --- a/server/src/lib/AuthenticationRegulator.ts +++ b/server/src/lib/regulation/Regulator.ts @@ -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; diff --git a/server/src/lib/routes/firstfactor/post.ts b/server/src/lib/routes/firstfactor/post.ts index e32ffc58e..1aacb50ce 100644 --- a/server/src/lib/routes/firstfactor/post.ts +++ b/server/src/lib/routes/firstfactor/post.ts @@ -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,89 +13,89 @@ 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 { - const username: string = req.body.username; - const password: string = req.body.password; +export default function (vars: ServerVariables) { + return function (req: express.Request, res: express.Response) + : BluebirdPromise { + const username: string = req.body.username; + const password: string = req.body.password; + let authSession: AuthenticationSession.AuthenticationSession; - 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() - .then(function () { - if (!username || !password) { - return BluebirdPromise.reject(new Error("No username or password.")); - } - logger.info(req, "Starting authentication of user \"%s\"", username); - return AuthenticationSession.get(req); - }) - .then(function (_authSession: AuthenticationSession.AuthenticationSession) { - authSession = _authSession; - return regulator.regulate(username); - }) - .then(function () { - logger.info(req, "No regulation applied."); - return ldap.authenticate(username, password); - }) - .then(function (groupsAndEmails: GroupsAndEmails) { - logger.info(req, "LDAP binding successful. Retrieved information about user are %s", - JSON.stringify(groupsAndEmails)); - authSession.userid = username; - authSession.first_factor = true; - const redirectUrl = req.query[Constants.REDIRECT_QUERY_PARAM] !== "undefined" - // Fuck, don't know why it is a string! - ? req.query[Constants.REDIRECT_QUERY_PARAM] - : undefined; - - 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); - - 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); - 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); - - if (authMethod == "basic_auth") { - res.send({ - redirect: redirectUrl - }); - logger.debug(req, "Redirect to '%s'", redirectUrl); - } - else if (authMethod == "two_factor") { - let newRedirectUrl = Endpoint.SECOND_FACTOR_GET; - if (redirectUrl) { - newRedirectUrl += "?" + Constants.REDIRECT_QUERY_PARAM + "=" - + encodeURIComponent(redirectUrl); + return BluebirdPromise.resolve() + .then(function () { + if (!username || !password) { + return BluebirdPromise.reject(new Error("No username or password.")); } - logger.debug(req, "Redirect to '%s'", newRedirectUrl, typeof redirectUrl); - res.send({ - redirect: newRedirectUrl - }); - } - else { - return BluebirdPromise.reject(new Error("Unknown authentication method for this domain.")); - } - return BluebirdPromise.resolve(); - }) - .catch(Exceptions.LdapBindError, function (err: Error) { - regulator.mark(username, false); - return ErrorReplies.replyWithError200(req, res, logger, UserMessages.OPERATION_FAILED)(err); - }) - .catch(ErrorReplies.replyWithError200(req, res, logger, UserMessages.OPERATION_FAILED)); -} + vars.logger.info(req, "Starting authentication of user \"%s\"", username); + return AuthenticationSession.get(req); + }) + .then(function (_authSession: AuthenticationSession.AuthenticationSession) { + authSession = _authSession; + return vars.regulator.regulate(username); + }) + .then(function () { + vars.logger.info(req, "No regulation applied."); + return vars.ldapAuthenticator.authenticate(username, password); + }) + .then(function (groupsAndEmails: GroupsAndEmails) { + vars.logger.info(req, "LDAP binding successful. Retrieved information about user are %s", + JSON.stringify(groupsAndEmails)); + authSession.userid = username; + authSession.first_factor = true; + const redirectUrl = req.query[Constants.REDIRECT_QUERY_PARAM] !== "undefined" + // Fuck, don't know why it is a string! + ? req.query[Constants.REDIRECT_QUERY_PARAM] + : undefined; + + const emails: string[] = groupsAndEmails.emails; + const groups: string[] = groupsAndEmails.groups; + const redirectHost: string = DomainExtractor.fromUrl(redirectUrl); + 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."; + vars.logger.error(req, "%s", errMessage); + return BluebirdPromise.reject(new Error(errMessage)); + } + + authSession.email = emails[0]; + authSession.groups = groups; + + vars.logger.debug(req, "Mark successful authentication to regulator."); + vars.regulator.mark(username, true); + + if (authMethod == "basic_auth") { + res.send({ + redirect: redirectUrl + }); + vars.logger.debug(req, "Redirect to '%s'", redirectUrl); + } + else if (authMethod == "two_factor") { + let newRedirectUrl = Endpoint.SECOND_FACTOR_GET; + if (redirectUrl) { + newRedirectUrl += "?" + Constants.REDIRECT_QUERY_PARAM + "=" + + encodeURIComponent(redirectUrl); + } + vars.logger.debug(req, "Redirect to '%s'", newRedirectUrl, typeof redirectUrl); + res.send({ + redirect: newRedirectUrl + }); + } + else { + return BluebirdPromise.reject(new Error("Unknown authentication method for this domain.")); + } + return BluebirdPromise.resolve(); + }) + .catch(Exceptions.LdapBindError, function (err: Error) { + vars.regulator.mark(username, false); + return ErrorReplies.replyWithError200(req, res, vars.logger, UserMessages.OPERATION_FAILED)(err); + }) + .catch(ErrorReplies.replyWithError200(req, res, vars.logger, UserMessages.OPERATION_FAILED)); + }; +} \ No newline at end of file diff --git a/server/src/lib/routes/secondfactor/redirect.ts b/server/src/lib/routes/secondfactor/redirect.ts index 4f258e8ab..cbfba27b7 100644 --- a/server/src/lib/routes/secondfactor/redirect.ts +++ b/server/src/lib/routes/secondfactor/redirect.ts @@ -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 { 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)); } \ No newline at end of file diff --git a/server/src/lib/routes/secondfactor/totp/identity/RegistrationHandler.ts b/server/src/lib/routes/secondfactor/totp/identity/RegistrationHandler.ts index d289773fe..c63cbae7b 100644 --- a/server/src/lib/routes/secondfactor/totp/identity/RegistrationHandler.ts +++ b/server/src/lib/routes/secondfactor/totp/identity/RegistrationHandler.ts @@ -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) diff --git a/server/src/lib/routes/secondfactor/totp/sign/post.ts b/server/src/lib/routes/secondfactor/totp/sign/post.ts index d9b5afd9f..194242bbf 100644 --- a/server/src/lib/routes/secondfactor/totp/sign/post.ts +++ b/server/src/lib/routes/secondfactor/totp/sign/post.ts @@ -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 default function (vars: ServerVariables) { + function handler(req: express.Request, res: express.Response): BluebirdPromise { + let authSession: AuthenticationSession.AuthenticationSession; + const token = req.body.token; -export function handler(req: express.Request, res: express.Response): BluebirdPromise { - 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; + vars.logger.info(req, "Initiate TOTP validation for user '%s'.", authSession.userid); + return vars.userDataStore.retrieveTOTPSecret(authSession.userid); + }) + .then(function (doc: TOTPSecretDocument) { + vars.logger.debug(req, "TOTP secret is %s", JSON.stringify(doc)); - 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); - }) - .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."); - authSession.second_factor = true; - redirect(req, res); - return BluebirdPromise.resolve(); - }) - .catch(ErrorReplies.replyWithError200(req, res, logger, - UserMessages.OPERATION_FAILED)); + 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, vars.logger, + UserMessages.OPERATION_FAILED)); + } + return FirstFactorBlocker(handler); } diff --git a/server/src/lib/routes/verify/get.ts b/server/src/lib/routes/verify/get.ts index 87c40f891..1afb53852 100644 --- a/server/src/lib/routes/verify/get.ts +++ b/server/src/lib/routes/verify/get.ts @@ -6,19 +6,22 @@ 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"; 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 { - 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_filter(req: express.Request, res: express.Response, + vars: ServerVariables): BluebirdPromise { return AuthenticationSession.get(req) .then(function (authSession) { @@ -35,15 +38,17 @@ function verify_filter(req: express.Request, res: express.Response): BluebirdPro const path = objectPath.get(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) return BluebirdPromise.reject( new exceptions.AccessDeniedError(FIRST_FACTOR_NOT_VALIDATED_MESSAGE)); - const isAllowed = accessController.isAccessAllowed(domain, path, username, groups); + 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))); @@ -52,25 +57,27 @@ function verify_filter(req: express.Request, res: express.Response): BluebirdPro return BluebirdPromise.reject( new exceptions.AccessDeniedError(SECOND_FACTOR_NOT_VALIDATED_MESSAGE)); - res.setHeader("Remote-User", username); - res.setHeader("Remote-Groups", groups.join(",")); + res.setHeader(REMOTE_USER, username); + res.setHeader(REMOTE_GROUPS, groups.join(",")); return BluebirdPromise.resolve(); }); } -export default function (req: express.Request, res: express.Response): BluebirdPromise { - const logger = ServerVariablesHandler.getLogger(req.app); - return verify_filter(req, res) - .then(function () { - res.status(204); - res.send(); - return BluebirdPromise.resolve(); - }) - // The user is authenticated but has restricted access -> 403 - .catch(exceptions.DomainAccessDenied, ErrorReplies - .replyWithError403(req, res, logger)) - // The user is not yet authenticated -> 401 - .catch(ErrorReplies.replyWithError401(req, res, logger)); +export default function (vars: ServerVariables) { + return function (req: express.Request, res: express.Response) + : BluebirdPromise { + return verify_filter(req, res, vars) + .then(function () { + res.status(204); + res.send(); + return BluebirdPromise.resolve(); + }) + // The user is authenticated but has restricted access -> 403 + .catch(exceptions.DomainAccessDenied, ErrorReplies + .replyWithError403(req, res, vars.logger)) + // The user is not yet authenticated -> 401 + .catch(ErrorReplies.replyWithError401(req, res, vars.logger)); + }; } diff --git a/server/test/ServerConfiguration.test.ts b/server/test/ServerConfiguration.test.ts index e33277655..9d67027ac 100644 --- a/server/test/ServerConfiguration.test.ts +++ b/server/test/ServerConfiguration.test.ts @@ -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"); + }); }); }); diff --git a/server/test/TOTPValidator.test.ts b/server/test/TOTPValidator.test.ts deleted file mode 100644 index 2856583f1..000000000 --- a/server/test/TOTPValidator.test.ts +++ /dev/null @@ -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(); - }); - }); -}); - diff --git a/server/test/authentication/totp/Validator.test.ts b/server/test/authentication/totp/Validator.test.ts new file mode 100644 index 000000000..3d0518440 --- /dev/null +++ b/server/test/authentication/totp/Validator.test.ts @@ -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)); + }); +}); + diff --git a/server/test/mocks/NotifierStub.ts b/server/test/mocks/NotifierStub.ts new file mode 100644 index 000000000..94a178e82 --- /dev/null +++ b/server/test/mocks/NotifierStub.ts @@ -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 { + return this.notifyStub(to, subject, link); + } +} \ No newline at end of file diff --git a/server/test/mocks/RegulatorStub.ts b/server/test/mocks/RegulatorStub.ts new file mode 100644 index 000000000..203c0e23a --- /dev/null +++ b/server/test/mocks/RegulatorStub.ts @@ -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 { + return this.markStub(userId, isAuthenticationSuccessful); + } + + regulate(userId: string): BluebirdPromise { + return this.regulateStub(userId); + } +} \ No newline at end of file diff --git a/server/test/mocks/ServerVariablesMockBuilder.ts b/server/test/mocks/ServerVariablesMockBuilder.ts new file mode 100644 index 000000000..accfb0652 --- /dev/null +++ b/server/test/mocks/ServerVariablesMockBuilder.ts @@ -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 + }; + } +} \ No newline at end of file diff --git a/server/test/mocks/TotpHandlerStub.ts b/server/test/mocks/TotpHandlerStub.ts new file mode 100644 index 000000000..276dbb15a --- /dev/null +++ b/server/test/mocks/TotpHandlerStub.ts @@ -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); + } +} \ No newline at end of file diff --git a/server/test/mocks/U2fHandlerStub.ts b/server/test/mocks/U2fHandlerStub.ts new file mode 100644 index 000000000..95256641f --- /dev/null +++ b/server/test/mocks/U2fHandlerStub.ts @@ -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); + } +} \ No newline at end of file diff --git a/server/test/mocks/ldap/AuthenticatorStub.ts b/server/test/mocks/ldap/AuthenticatorStub.ts new file mode 100644 index 000000000..4102b0fe8 --- /dev/null +++ b/server/test/mocks/ldap/AuthenticatorStub.ts @@ -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 { + return this.authenticateStub(username, password); + } +} \ No newline at end of file diff --git a/server/test/mocks/ldap/EmailsRetrieverStub.ts b/server/test/mocks/ldap/EmailsRetrieverStub.ts new file mode 100644 index 000000000..0e2b87546 --- /dev/null +++ b/server/test/mocks/ldap/EmailsRetrieverStub.ts @@ -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 { + return this.retrieveStub(username, client); + } +} \ No newline at end of file diff --git a/server/test/mocks/ldap/PasswordUpdaterStub.ts b/server/test/mocks/ldap/PasswordUpdaterStub.ts new file mode 100644 index 000000000..9443dddbb --- /dev/null +++ b/server/test/mocks/ldap/PasswordUpdaterStub.ts @@ -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 { + return this.updatePasswordStub(username, newPassword); + } +} \ No newline at end of file diff --git a/server/test/AuthenticationRegulator.test.ts b/server/test/regulation/Regulator.test.ts similarity index 84% rename from server/test/AuthenticationRegulator.test.ts rename to server/test/regulation/Regulator.test.ts index f3e40aa2d..5f10fd056 100644 --- a/server/test/AuthenticationRegulator.test.ts +++ b/server/test/regulation/Regulator.test.ts @@ -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); diff --git a/server/test/routes/firstfactor/post.test.ts b/server/test/routes/firstfactor/post.test.ts index 98de498bb..eb5fafec3 100644 --- a/server/test/routes/firstfactor/post.test.ts +++ b/server/test/routes/firstfactor/post.test.ts @@ -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], { diff --git a/server/test/routes/secondfactor/totp/sign/post.test.ts b/server/test/routes/secondfactor/totp/sign/post.test.ts index 22bfa5134..373308c3f 100644 --- a/server/test/routes/secondfactor/totp/sign/post.test.ts +++ b/server/test/routes/secondfactor/totp/sign/post.test.ts @@ -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]); diff --git a/server/test/routes/verify/get.test.ts b/server/test/routes/verify/get.test.ts index 7167199b1..cc89a2fee 100644 --- a/server/test/routes/verify/get.test.ts +++ b/server/test/routes/verify/get.test.ts @@ -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() }); }); @@ -109,6 +102,7 @@ describe("test authentication token verification", function () { second_factor: true, email: undefined, groups: [], + last_activity_datetime: new Date() }); }); @@ -119,6 +113,7 @@ describe("test authentication token verification", function () { second_factor: false, email: undefined, groups: [], + last_activity_datetime: new Date() }); }); @@ -129,6 +124,7 @@ describe("test authentication token verification", function () { second_factor: false, email: undefined, groups: [], + last_activity_datetime: new Date() }); }); @@ -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() }); }); }); @@ -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,10 +180,11 @@ 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));