Refactor endpoints to get server variables as input parameters
This refactoring aims to ease testability and clean up a lot of soft touchy typings in test code. This is the first step of this refactoring introducing the concept and implementing missing interfaces and stubs. At the end of the day, ServerVariablesHandler should completely disappear and every variable should be injected in the endpoint handler builder itself.pull/175/head
parent
34a595863a
commit
b9fa786df6
|
@ -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,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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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: 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<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,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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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,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<void> {
|
||||
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<void> {
|
||||
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));
|
||||
};
|
||||
}
|
|
@ -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 default function (vars: ServerVariables) {
|
||||
function handler(req: express.Request, res: express.Response): BluebirdPromise<void> {
|
||||
let authSession: AuthenticationSession.AuthenticationSession;
|
||||
const token = req.body.token;
|
||||
|
||||
export 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;
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -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<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_filter(req: express.Request, res: express.Response,
|
||||
vars: ServerVariables): BluebirdPromise<void> {
|
||||
|
||||
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<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)
|
||||
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<void> {
|
||||
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<void> {
|
||||
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));
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
import assert = require("assert");
|
||||
import Assert = require("assert");
|
||||
import Sinon = require("sinon");
|
||||
import nedb = require("nedb");
|
||||
import express = require("express");
|
||||
|
@ -73,9 +73,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");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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));
|
||||
});
|
||||
});
|
||||
|
|
@ -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()
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -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));
|
||||
|
|
Loading…
Reference in New Issue