mirror of
https://github.com/0rangebananaspy/authelia.git
synced 2024-09-14 22:47:21 +07:00
Migrate server to typescript
This commit is contained in:
parent
923886667d
commit
b0c6c61df5
|
@ -5,7 +5,7 @@ WORKDIR /usr/src
|
|||
COPY package.json /usr/src/package.json
|
||||
RUN npm install --production
|
||||
|
||||
COPY src /usr/src
|
||||
COPY dist/src /usr/src
|
||||
|
||||
ENV PORT=80
|
||||
EXPOSE 80
|
||||
|
|
|
@ -7,10 +7,8 @@
|
|||
"authelia": "src/index.js"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "./node_modules/.bin/mocha -r ts-node/register --recursive test/unitary",
|
||||
"unit-test": "./node_modules/.bin/mocha --recursive test/unitary",
|
||||
"test": "./node_modules/.bin/mocha --compilers ts:ts-node/register --recursive test/unitary",
|
||||
"int-test": "./node_modules/.bin/mocha --recursive test/integration",
|
||||
"all-test": "./node_modules/.bin/mocha --recursive test",
|
||||
"coverage": "./node_modules/.bin/istanbul cover _mocha -- -R spec --recursive test",
|
||||
"build-ts": "tsc",
|
||||
"watch-ts": "tsc -w",
|
||||
|
@ -49,14 +47,19 @@
|
|||
"devDependencies": {
|
||||
"@types/assert": "0.0.31",
|
||||
"@types/bluebird": "^3.5.3",
|
||||
"@types/body-parser": "^1.16.3",
|
||||
"@types/express": "^4.0.35",
|
||||
"@types/express-session": "0.0.32",
|
||||
"@types/ldapjs": "^1.0.0",
|
||||
"@types/mocha": "^2.2.41",
|
||||
"@types/mockdate": "^2.0.0",
|
||||
"@types/nedb": "^1.8.3",
|
||||
"@types/nodemailer": "^1.3.32",
|
||||
"@types/object-path": "^0.9.28",
|
||||
"@types/request": "0.0.43",
|
||||
"@types/sinon": "^2.2.1",
|
||||
"@types/speakeasy": "^2.0.1",
|
||||
"@types/tmp": "0.0.33",
|
||||
"@types/winston": "^2.3.2",
|
||||
"@types/yamljs": "^0.2.30",
|
||||
"grunt": "^1.0.1",
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
||||
|
||||
import * as server from "./lib/server";
|
||||
import Server from "./lib/Server";
|
||||
const YAML = require("yamljs");
|
||||
|
||||
const config_path = process.argv[2];
|
||||
|
@ -26,4 +26,8 @@ const deps = {
|
|||
nedb: require("nedb")
|
||||
};
|
||||
|
||||
server.run(yaml_config, deps);
|
||||
const server = new Server();
|
||||
server.start(yaml_config, deps)
|
||||
.then(() => {
|
||||
console.log("The server is started!");
|
||||
});
|
||||
|
|
|
@ -11,8 +11,8 @@ interface DatedDocument {
|
|||
}
|
||||
|
||||
export class AuthenticationRegulator {
|
||||
_user_data_store: any;
|
||||
_lock_time_in_seconds: number;
|
||||
private _user_data_store: any;
|
||||
private _lock_time_in_seconds: number;
|
||||
|
||||
constructor(user_data_store: any, lock_time_in_seconds: number) {
|
||||
this._user_data_store = user_data_store;
|
||||
|
|
62
src/lib/Configuration.ts
Normal file
62
src/lib/Configuration.ts
Normal file
|
@ -0,0 +1,62 @@
|
|||
|
||||
export interface LdapConfiguration {
|
||||
url: string;
|
||||
base_dn: string;
|
||||
additional_user_dn?: string;
|
||||
user_name_attribute?: string; // cn by default
|
||||
additional_group_dn?: string;
|
||||
group_name_attribute?: string; // cn by default
|
||||
user: string; // admin username
|
||||
password: string; // admin password
|
||||
}
|
||||
|
||||
type UserName = string;
|
||||
type GroupName = string;
|
||||
type DomainPattern = string;
|
||||
|
||||
type ACLDefaultRules = Array<DomainPattern>;
|
||||
type ACLGroupsRules = Object;
|
||||
type ACLUsersRules = Object;
|
||||
|
||||
export interface ACLConfiguration {
|
||||
default: ACLDefaultRules;
|
||||
groups: ACLGroupsRules;
|
||||
users: ACLUsersRules;
|
||||
}
|
||||
|
||||
interface SessionCookieConfiguration {
|
||||
secret: string;
|
||||
expiration?: number;
|
||||
domain?: string;
|
||||
}
|
||||
|
||||
interface GMailNotifier {
|
||||
user: string;
|
||||
pass: string;
|
||||
}
|
||||
|
||||
type NotifierType = string;
|
||||
export interface NotifiersConfiguration {
|
||||
gmail: GMailNotifier;
|
||||
}
|
||||
|
||||
export interface UserConfiguration {
|
||||
port?: number;
|
||||
logs_level?: string;
|
||||
ldap: LdapConfiguration;
|
||||
session: SessionCookieConfiguration;
|
||||
store_directory?: string;
|
||||
notifier: NotifiersConfiguration;
|
||||
access_control?: ACLConfiguration;
|
||||
}
|
||||
|
||||
export interface AppConfiguration {
|
||||
port: number;
|
||||
logs_level: string;
|
||||
ldap: LdapConfiguration;
|
||||
session: SessionCookieConfiguration;
|
||||
store_in_memory?: boolean;
|
||||
store_directory?: string;
|
||||
notifier: NotifiersConfiguration;
|
||||
access_control?: ACLConfiguration;
|
||||
}
|
11
src/lib/GlobalDependencies.ts
Normal file
11
src/lib/GlobalDependencies.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
import * as winston from "winston";
|
||||
|
||||
export interface GlobalDependencies {
|
||||
u2f: object;
|
||||
nodemailer: any;
|
||||
ldapjs: object;
|
||||
session: any;
|
||||
winston: winston.Winston;
|
||||
speakeasy: object;
|
||||
nedb: any;
|
||||
}
|
84
src/lib/Server.ts
Normal file
84
src/lib/Server.ts
Normal file
|
@ -0,0 +1,84 @@
|
|||
|
||||
import { UserConfiguration } from "./Configuration";
|
||||
import { GlobalDependencies } from "./GlobalDependencies";
|
||||
import * as Express from "express";
|
||||
import * as BodyParser from "body-parser";
|
||||
import * as Path from "path";
|
||||
import { AuthenticationRegulator } from "./AuthenticationRegulator";
|
||||
import UserDataStore from "./UserDataStore";
|
||||
import * as http from "http";
|
||||
|
||||
import config_adapter = require("./config_adapter");
|
||||
|
||||
const Notifier = require("./notifier");
|
||||
const setup_endpoints = require("./setup_endpoints");
|
||||
const Ldap = require("./ldap");
|
||||
const AccessControl = require("./access_control");
|
||||
|
||||
export default class Server {
|
||||
private httpServer: http.Server;
|
||||
|
||||
start(yaml_configuration: UserConfiguration, deps: GlobalDependencies): Promise<void> {
|
||||
const config = config_adapter(yaml_configuration);
|
||||
|
||||
const view_directory = Path.resolve(__dirname, "../views");
|
||||
const public_html_directory = Path.resolve(__dirname, "../public_html");
|
||||
const datastore_options = {
|
||||
directory: config.store_directory,
|
||||
inMemory: config.store_in_memory
|
||||
};
|
||||
|
||||
const app = Express();
|
||||
app.use(Express.static(public_html_directory));
|
||||
app.use(BodyParser.urlencoded({ extended: false }));
|
||||
app.use(BodyParser.json());
|
||||
app.set("trust proxy", 1); // trust first proxy
|
||||
|
||||
app.use(deps.session({
|
||||
secret: config.session.secret,
|
||||
resave: false,
|
||||
saveUninitialized: true,
|
||||
cookie: {
|
||||
secure: false,
|
||||
maxAge: config.session.expiration,
|
||||
domain: config.session.domain
|
||||
},
|
||||
}));
|
||||
|
||||
app.set("views", view_directory);
|
||||
app.set("view engine", "ejs");
|
||||
|
||||
// by default the level of logs is info
|
||||
deps.winston.level = config.logs_level || "info";
|
||||
|
||||
const five_minutes = 5 * 60;
|
||||
const data_store = new UserDataStore(datastore_options);
|
||||
const regulator = new AuthenticationRegulator(data_store, five_minutes);
|
||||
const notifier = new Notifier(config.notifier, deps);
|
||||
const ldap = new Ldap(deps, config.ldap);
|
||||
const access_control = AccessControl(deps.winston, config.access_control);
|
||||
|
||||
app.set("logger", deps.winston);
|
||||
app.set("ldap", ldap);
|
||||
app.set("totp engine", deps.speakeasy);
|
||||
app.set("u2f", deps.u2f);
|
||||
app.set("user data store", data_store);
|
||||
app.set("notifier", notifier);
|
||||
app.set("authentication regulator", regulator);
|
||||
app.set("config", config);
|
||||
app.set("access control", access_control);
|
||||
setup_endpoints(app);
|
||||
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
this.httpServer = app.listen(config.port, function (err: string) {
|
||||
console.log("Listening on %d...", config.port);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.httpServer.close();
|
||||
}
|
||||
}
|
||||
|
6
src/lib/TOTPSecret.ts
Normal file
6
src/lib/TOTPSecret.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
|
||||
export interface TOTPSecret {
|
||||
base32: string;
|
||||
ascii: string;
|
||||
otpauth_url: string;
|
||||
}
|
169
src/lib/UserDataStore.ts
Normal file
169
src/lib/UserDataStore.ts
Normal file
|
@ -0,0 +1,169 @@
|
|||
import * as Promise from "bluebird";
|
||||
import * as path from "path";
|
||||
import Nedb = require("nedb");
|
||||
import { NedbAsync } from "nedb";
|
||||
import { TOTPSecret } from "./TOTPSecret";
|
||||
|
||||
// Constants
|
||||
|
||||
const U2F_META_COLLECTION_NAME = "u2f_meta";
|
||||
const IDENTITY_CHECK_TOKENS_COLLECTION_NAME = "identity_check_tokens";
|
||||
const AUTHENTICATION_TRACES_COLLECTION_NAME = "authentication_traces";
|
||||
const TOTP_SECRETS_COLLECTION_NAME = "totp_secrets";
|
||||
|
||||
|
||||
export interface TOTPSecretDocument {
|
||||
userid: string;
|
||||
secret: TOTPSecret;
|
||||
}
|
||||
|
||||
export interface U2FMetaDocument {
|
||||
meta: object;
|
||||
userid: string;
|
||||
appid: string;
|
||||
}
|
||||
|
||||
export interface Options {
|
||||
inMemoryOnly?: boolean;
|
||||
directory?: string;
|
||||
}
|
||||
|
||||
|
||||
// Source
|
||||
|
||||
export default class UserDataStore {
|
||||
private _u2f_meta_collection: NedbAsync;
|
||||
private _identity_check_tokens_collection: NedbAsync;
|
||||
private _authentication_traces_collection: NedbAsync;
|
||||
private _totp_secret_collection: NedbAsync;
|
||||
|
||||
constructor(options?: Options) {
|
||||
this._u2f_meta_collection = create_collection(U2F_META_COLLECTION_NAME, options);
|
||||
this._identity_check_tokens_collection =
|
||||
create_collection(IDENTITY_CHECK_TOKENS_COLLECTION_NAME, options);
|
||||
this._authentication_traces_collection =
|
||||
create_collection(AUTHENTICATION_TRACES_COLLECTION_NAME, options);
|
||||
this._totp_secret_collection =
|
||||
create_collection(TOTP_SECRETS_COLLECTION_NAME, options);
|
||||
}
|
||||
|
||||
set_u2f_meta(userid: string, appid: string, meta: Object): Promise<any> {
|
||||
const newDocument = {
|
||||
userid: userid,
|
||||
appid: appid,
|
||||
meta: meta
|
||||
};
|
||||
|
||||
const filter = {
|
||||
userid: userid,
|
||||
appid: appid
|
||||
};
|
||||
|
||||
return this._u2f_meta_collection.updateAsync(filter, newDocument, { upsert: true });
|
||||
}
|
||||
|
||||
get_u2f_meta(userid: string, appid: string): Promise<U2FMetaDocument> {
|
||||
const filter = {
|
||||
userid: userid,
|
||||
appid: appid
|
||||
};
|
||||
return this._u2f_meta_collection.findOneAsync(filter);
|
||||
}
|
||||
|
||||
save_authentication_trace(userid: string, type: string, is_success: boolean) {
|
||||
const newDocument = {
|
||||
userid: userid,
|
||||
date: new Date(),
|
||||
is_success: is_success,
|
||||
type: type
|
||||
};
|
||||
|
||||
return this._authentication_traces_collection.insertAsync(newDocument);
|
||||
}
|
||||
|
||||
get_last_authentication_traces(userid: string, type: string, is_success: boolean, count: number): Promise<any> {
|
||||
const q = {
|
||||
userid: userid,
|
||||
type: type,
|
||||
is_success: is_success
|
||||
};
|
||||
|
||||
const query = this._authentication_traces_collection.find(q)
|
||||
.sort({ date: -1 }).limit(count);
|
||||
const query_promisified = Promise.promisify(query.exec, { context: query });
|
||||
return query_promisified();
|
||||
}
|
||||
|
||||
issue_identity_check_token(userid: string, token: string, data: string | object, max_age: number): Promise<any> {
|
||||
const newDocument = {
|
||||
userid: userid,
|
||||
token: token,
|
||||
content: {
|
||||
userid: userid,
|
||||
data: data
|
||||
},
|
||||
max_date: new Date(new Date().getTime() + max_age)
|
||||
};
|
||||
|
||||
return this._identity_check_tokens_collection.insertAsync(newDocument);
|
||||
}
|
||||
|
||||
consume_identity_check_token(token: string): Promise<any> {
|
||||
const query = {
|
||||
token: token
|
||||
};
|
||||
|
||||
return this._identity_check_tokens_collection.findOneAsync(query)
|
||||
.then(function (doc) {
|
||||
if (!doc) {
|
||||
return Promise.reject("Registration token does not exist");
|
||||
}
|
||||
|
||||
const max_date = doc.max_date;
|
||||
const current_date = new Date();
|
||||
if (current_date > max_date) {
|
||||
return Promise.reject("Registration token is not valid anymore");
|
||||
}
|
||||
return Promise.resolve(doc.content);
|
||||
})
|
||||
.then((content) => {
|
||||
return Promise.join(this._identity_check_tokens_collection.removeAsync(query),
|
||||
Promise.resolve(content));
|
||||
})
|
||||
.then((v) => {
|
||||
return Promise.resolve(v[1]);
|
||||
});
|
||||
}
|
||||
|
||||
set_totp_secret(userid: string, secret: TOTPSecret): Promise<any> {
|
||||
const doc = {
|
||||
userid: userid,
|
||||
secret: secret
|
||||
};
|
||||
|
||||
const query = {
|
||||
userid: userid
|
||||
};
|
||||
return this._totp_secret_collection.updateAsync(query, doc, { upsert: true });
|
||||
}
|
||||
|
||||
get_totp_secret(userid: string): Promise<TOTPSecretDocument> {
|
||||
const query = {
|
||||
userid: userid
|
||||
};
|
||||
return this._totp_secret_collection.findOneAsync(query);
|
||||
}
|
||||
}
|
||||
|
||||
function create_collection(name: string, options: any): NedbAsync {
|
||||
const datastore_options = {
|
||||
inMemoryOnly: options.inMemoryOnly || false,
|
||||
autoload: true,
|
||||
filename: ""
|
||||
};
|
||||
|
||||
if (options.directory)
|
||||
datastore_options.filename = path.resolve(options.directory, name);
|
||||
|
||||
return Promise.promisifyAll(new Nedb(datastore_options)) as NedbAsync;
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
|
||||
import * as ObjectPath from "object-path";
|
||||
import { authelia } from "../types/authelia";
|
||||
import { AppConfiguration, UserConfiguration, NotifiersConfiguration, ACLConfiguration, LdapConfiguration } from "./Configuration";
|
||||
|
||||
|
||||
function get_optional<T>(config: object, path: string, default_value: T): T {
|
||||
|
@ -17,7 +17,7 @@ function ensure_key_existence(config: object, path: string): void {
|
|||
}
|
||||
}
|
||||
|
||||
export = function(yaml_config: object): authelia.Configuration {
|
||||
export = function(yaml_config: UserConfiguration): AppConfiguration {
|
||||
ensure_key_existence(yaml_config, "ldap");
|
||||
ensure_key_existence(yaml_config, "session.secret");
|
||||
|
||||
|
@ -25,14 +25,16 @@ export = function(yaml_config: object): authelia.Configuration {
|
|||
|
||||
return {
|
||||
port: port,
|
||||
ldap: ObjectPath.get(yaml_config, "ldap"),
|
||||
session_domain: ObjectPath.get<object, string>(yaml_config, "session.domain"),
|
||||
session_secret: ObjectPath.get<object, string>(yaml_config, "session.secret"),
|
||||
session_max_age: get_optional<number>(yaml_config, "session.expiration", 3600000), // in ms
|
||||
ldap: ObjectPath.get<object, LdapConfiguration>(yaml_config, "ldap"),
|
||||
session: {
|
||||
domain: ObjectPath.get<object, string>(yaml_config, "session.domain"),
|
||||
secret: ObjectPath.get<object, string>(yaml_config, "session.secret"),
|
||||
expiration: get_optional<number>(yaml_config, "session.expiration", 3600000), // in ms
|
||||
},
|
||||
store_directory: get_optional<string>(yaml_config, "store_directory", undefined),
|
||||
logs_level: get_optional<string>(yaml_config, "logs_level", "info"),
|
||||
notifier: ObjectPath.get<object, authelia.NotifiersConfiguration>(yaml_config, "notifier"),
|
||||
access_control: ObjectPath.get<object, authelia.ACLConfiguration>(yaml_config, "access_control")
|
||||
notifier: ObjectPath.get<object, NotifiersConfiguration>(yaml_config, "notifier"),
|
||||
access_control: ObjectPath.get<object, ACLConfiguration>(yaml_config, "access_control")
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -1,70 +0,0 @@
|
|||
|
||||
import { authelia } from "../types/authelia";
|
||||
import * as Express from "express";
|
||||
import * as BodyParser from "body-parser";
|
||||
import * as Path from "path";
|
||||
import { AuthenticationRegulator } from "./AuthenticationRegulator";
|
||||
|
||||
const UserDataStore = require("./user_data_store");
|
||||
const Notifier = require("./notifier");
|
||||
const setup_endpoints = require("./setup_endpoints");
|
||||
const config_adapter = require("./config_adapter");
|
||||
const Ldap = require("./ldap");
|
||||
const AccessControl = require("./access_control");
|
||||
|
||||
export function run(yaml_configuration: authelia.Configuration, deps: authelia.GlobalDependencies, fn?: () => undefined) {
|
||||
const config = config_adapter(yaml_configuration);
|
||||
|
||||
const view_directory = Path.resolve(__dirname, "../views");
|
||||
const public_html_directory = Path.resolve(__dirname, "../public_html");
|
||||
const datastore_options = {
|
||||
directory: config.store_directory,
|
||||
inMemory: config.store_in_memory
|
||||
};
|
||||
|
||||
const app = Express();
|
||||
app.use(Express.static(public_html_directory));
|
||||
app.use(BodyParser.urlencoded({ extended: false }));
|
||||
app.use(BodyParser.json());
|
||||
app.set("trust proxy", 1); // trust first proxy
|
||||
|
||||
app.use(deps.session({
|
||||
secret: config.session_secret,
|
||||
resave: false,
|
||||
saveUninitialized: true,
|
||||
cookie: {
|
||||
secure: false,
|
||||
maxAge: config.session_max_age,
|
||||
domain: config.session_domain
|
||||
},
|
||||
}));
|
||||
|
||||
app.set("views", view_directory);
|
||||
app.set("view engine", "ejs");
|
||||
|
||||
// by default the level of logs is info
|
||||
deps.winston.level = config.logs_level || "info";
|
||||
|
||||
const five_minutes = 5 * 60;
|
||||
const data_store = new UserDataStore(deps.nedb, datastore_options);
|
||||
const regulator = new AuthenticationRegulator(data_store, five_minutes);
|
||||
const notifier = new Notifier(config.notifier, deps);
|
||||
const ldap = new Ldap(deps, config.ldap);
|
||||
const access_control = AccessControl(deps.winston, config.access_control);
|
||||
|
||||
app.set("logger", deps.winston);
|
||||
app.set("ldap", ldap);
|
||||
app.set("totp engine", deps.speakeasy);
|
||||
app.set("u2f", deps.u2f);
|
||||
app.set("user data store", data_store);
|
||||
app.set("notifier", notifier);
|
||||
app.set("authentication regulator", regulator);
|
||||
app.set("config", config);
|
||||
app.set("access control", access_control);
|
||||
setup_endpoints(app);
|
||||
|
||||
return app.listen(config.port, function(err: string) {
|
||||
console.log("Listening on %d...", config.port);
|
||||
if (fn) fn();
|
||||
});
|
||||
}
|
|
@ -1,124 +0,0 @@
|
|||
|
||||
module.exports = UserDataStore;
|
||||
|
||||
var Promise = require('bluebird');
|
||||
var path = require('path');
|
||||
|
||||
function UserDataStore(DataStore, options) {
|
||||
this._u2f_meta_collection = create_collection('u2f_meta', options, DataStore);
|
||||
this._identity_check_tokens_collection =
|
||||
create_collection('identity_check_tokens', options, DataStore);
|
||||
this._authentication_traces_collection =
|
||||
create_collection('authentication_traces', options, DataStore);
|
||||
this._totp_secret_collection =
|
||||
create_collection('totp_secrets', options, DataStore);
|
||||
}
|
||||
|
||||
function create_collection(name, options, DataStore) {
|
||||
var datastore_options = {};
|
||||
|
||||
if(options.directory)
|
||||
datastore_options.filename = path.resolve(options.directory, name);
|
||||
|
||||
datastore_options.inMemoryOnly = options.inMemoryOnly || false;
|
||||
datastore_options.autoload = true;
|
||||
return Promise.promisifyAll(new DataStore(datastore_options));
|
||||
}
|
||||
|
||||
UserDataStore.prototype.set_u2f_meta = function(userid, app_id, meta) {
|
||||
var newDocument = {};
|
||||
newDocument.userid = userid;
|
||||
newDocument.appid = app_id;
|
||||
newDocument.meta = meta;
|
||||
|
||||
var filter = {};
|
||||
filter.userid = userid;
|
||||
filter.appid = app_id;
|
||||
|
||||
return this._u2f_meta_collection.updateAsync(filter, newDocument, { upsert: true });
|
||||
}
|
||||
|
||||
UserDataStore.prototype.get_u2f_meta = function(userid, app_id) {
|
||||
var filter = {};
|
||||
filter.userid = userid;
|
||||
filter.appid = app_id;
|
||||
|
||||
return this._u2f_meta_collection.findOneAsync(filter);
|
||||
}
|
||||
|
||||
UserDataStore.prototype.save_authentication_trace = function(userid, type, is_success) {
|
||||
var newDocument = {};
|
||||
newDocument.userid = userid;
|
||||
newDocument.date = new Date();
|
||||
newDocument.is_success = is_success;
|
||||
newDocument.type = type;
|
||||
|
||||
return this._authentication_traces_collection.insertAsync(newDocument);
|
||||
}
|
||||
|
||||
UserDataStore.prototype.get_last_authentication_traces = function(userid, type, is_success, count) {
|
||||
var query = {};
|
||||
query.userid = userid;
|
||||
query.type = type;
|
||||
query.is_success = is_success;
|
||||
|
||||
var query = this._authentication_traces_collection.find(query)
|
||||
.sort({ date: -1 }).limit(count);
|
||||
var query_promisified = Promise.promisify(query.exec, { context: query });
|
||||
return query_promisified();
|
||||
}
|
||||
|
||||
UserDataStore.prototype.issue_identity_check_token = function(userid, token, data, max_age) {
|
||||
var newDocument = {};
|
||||
newDocument.userid = userid;
|
||||
newDocument.token = token;
|
||||
newDocument.content = { userid: userid, data: data };
|
||||
newDocument.max_date = new Date(new Date().getTime() + max_age);
|
||||
|
||||
return this._identity_check_tokens_collection.insertAsync(newDocument);
|
||||
}
|
||||
|
||||
UserDataStore.prototype.consume_identity_check_token = function(token) {
|
||||
var query = {};
|
||||
query.token = token;
|
||||
var that = this;
|
||||
var doc_content;
|
||||
|
||||
return this._identity_check_tokens_collection.findOneAsync(query)
|
||||
.then(function(doc) {
|
||||
if(!doc) {
|
||||
return Promise.reject('Registration token does not exist');
|
||||
}
|
||||
|
||||
var max_date = doc.max_date;
|
||||
var current_date = new Date();
|
||||
if(current_date > max_date) {
|
||||
return Promise.reject('Registration token is not valid anymore');
|
||||
}
|
||||
|
||||
doc_content = doc.content;
|
||||
return Promise.resolve();
|
||||
})
|
||||
.then(function() {
|
||||
return that._identity_check_tokens_collection.removeAsync(query);
|
||||
})
|
||||
.then(function() {
|
||||
return Promise.resolve(doc_content);
|
||||
})
|
||||
}
|
||||
|
||||
UserDataStore.prototype.set_totp_secret = function(userid, secret) {
|
||||
var doc = {}
|
||||
doc.userid = userid;
|
||||
doc.secret = secret;
|
||||
|
||||
var query = {};
|
||||
query.userid = userid;
|
||||
return this._totp_secret_collection.updateAsync(query, doc, { upsert: true });
|
||||
}
|
||||
|
||||
UserDataStore.prototype.get_totp_secret = function(userid) {
|
||||
var query = {};
|
||||
query.userid = userid;
|
||||
return this._totp_secret_collection.findOneAsync(query);
|
||||
}
|
67
src/types/authdog.d.ts
vendored
Normal file
67
src/types/authdog.d.ts
vendored
Normal file
|
@ -0,0 +1,67 @@
|
|||
|
||||
declare module "authdog" {
|
||||
interface RegisterRequest {
|
||||
challenge: string;
|
||||
}
|
||||
|
||||
interface RegisteredKey {
|
||||
version: number;
|
||||
keyHandle: string;
|
||||
}
|
||||
|
||||
type RegisteredKeys = Array<RegisteredKey>;
|
||||
type RegisterRequests = Array<RegisterRequest>;
|
||||
type AppId = string;
|
||||
|
||||
interface RegistrationRequest {
|
||||
appId: AppId;
|
||||
type: string;
|
||||
registerRequests: RegisterRequests;
|
||||
registeredKeys: RegisteredKeys;
|
||||
}
|
||||
|
||||
interface Registration {
|
||||
publicKey: string;
|
||||
keyHandle: string;
|
||||
certificate: string;
|
||||
}
|
||||
|
||||
interface ClientData {
|
||||
challenge: string;
|
||||
}
|
||||
|
||||
interface RegistrationResponse {
|
||||
clientData: ClientData;
|
||||
registrationData: string;
|
||||
}
|
||||
|
||||
interface Options {
|
||||
timeoutSeconds: number;
|
||||
requestId: string;
|
||||
}
|
||||
|
||||
interface AuthenticationRequest {
|
||||
appId: AppId;
|
||||
type: string;
|
||||
challenge: string;
|
||||
registeredKeys: RegisteredKeys;
|
||||
timeoutSeconds: number;
|
||||
requestId: string;
|
||||
}
|
||||
|
||||
interface AuthenticationResponse {
|
||||
keyHandle: string;
|
||||
clientData: ClientData;
|
||||
signatureData: string;
|
||||
}
|
||||
|
||||
interface Authentication {
|
||||
userPresence: Uint8Array,
|
||||
counter: Uint32Array
|
||||
}
|
||||
|
||||
export function startRegistration(appId: AppId, registeredKeys: RegisteredKeys, options?: Options): Promise<RegistrationRequest>;
|
||||
export function finishRegistration(registrationRequest: RegistrationRequest, registrationResponse: RegistrationResponse): Promise<Registration>;
|
||||
export function startAuthentication(appId: AppId, registeredKeys: RegisteredKeys, options: Options): Promise<AuthenticationRequest>;
|
||||
export function finishAuthentication(challenge: string, deviceResponse: AuthenticationResponse, registeredKeys: RegisteredKeys): Promise<Authentication>;
|
||||
}
|
61
src/types/authelia.d.ts
vendored
61
src/types/authelia.d.ts
vendored
|
@ -1,61 +0,0 @@
|
|||
|
||||
import * as winston from "winston";
|
||||
import * as nedb from "nedb";
|
||||
|
||||
declare namespace authelia {
|
||||
interface LdapConfiguration {
|
||||
url: string;
|
||||
base_dn: string;
|
||||
additional_user_dn?: string;
|
||||
user_name_attribute?: string; // cn by default
|
||||
additional_group_dn?: string;
|
||||
group_name_attribute?: string; // cn by default
|
||||
user: string; // admin username
|
||||
password: string; // admin password
|
||||
}
|
||||
|
||||
type UserName = string;
|
||||
type GroupName = string;
|
||||
type DomainPattern = string;
|
||||
|
||||
type ACLDefaultRules = Array<DomainPattern>;
|
||||
type ACLGroupsRules = Map<GroupName, DomainPattern>;
|
||||
type ACLUsersRules = Map<UserName, DomainPattern>;
|
||||
|
||||
export interface ACLConfiguration {
|
||||
default: ACLDefaultRules;
|
||||
groups: ACLGroupsRules;
|
||||
users: ACLUsersRules;
|
||||
}
|
||||
|
||||
interface SessionCookieConfiguration {
|
||||
secret: string;
|
||||
expiration: number;
|
||||
domain: string
|
||||
}
|
||||
|
||||
type NotifierType = string;
|
||||
export type NotifiersConfiguration = Map<NotifierType, any>;
|
||||
|
||||
export interface Configuration {
|
||||
port: number;
|
||||
logs_level: string;
|
||||
ldap: LdapConfiguration | {};
|
||||
session_domain?: string;
|
||||
session_secret: string;
|
||||
session_max_age: number;
|
||||
store_directory?: string;
|
||||
notifier: NotifiersConfiguration;
|
||||
access_control: ACLConfiguration;
|
||||
}
|
||||
|
||||
export interface GlobalDependencies {
|
||||
u2f: object;
|
||||
nodemailer: any;
|
||||
ldapjs: object;
|
||||
session: any;
|
||||
winston: winston.Winston;
|
||||
speakeasy: object;
|
||||
nedb: object;
|
||||
}
|
||||
}
|
12
src/types/nedb-async.d.ts
vendored
Normal file
12
src/types/nedb-async.d.ts
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
import Nedb = require("nedb");
|
||||
import * as Promise from "bluebird";
|
||||
|
||||
declare module "nedb" {
|
||||
export class NedbAsync extends Nedb {
|
||||
constructor(pathOrOptions?: string | Nedb.DataStoreOptions);
|
||||
updateAsync(query: any, updateQuery: any, options?: Nedb.UpdateOptions): Promise<any>;
|
||||
findOneAsync(query: any): Promise<any>;
|
||||
insertAsync<T>(newDoc: T): Promise<any>;
|
||||
removeAsync(query: any): Promise<any>;
|
||||
}
|
||||
}
|
14
src/types/request-async.d.ts
vendored
Normal file
14
src/types/request-async.d.ts
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
import * as Promise from "bluebird";
|
||||
import * as request from "request";
|
||||
|
||||
declare module "request" {
|
||||
export interface RequestAsync extends RequestAPI<Request, CoreOptions, RequiredUriUrl> {
|
||||
getAsync(uri: string, options?: RequiredUriUrl): Promise<RequestResponse>;
|
||||
getAsync(uri: string): Promise<RequestResponse>;
|
||||
getAsync(options: RequiredUriUrl & CoreOptions): Promise<RequestResponse>;
|
||||
|
||||
postAsync(uri: string, options?: CoreOptions): Promise<RequestResponse>;
|
||||
postAsync(uri: string): Promise<RequestResponse>;
|
||||
postAsync(options: RequiredUriUrl & CoreOptions): Promise<RequestResponse>;
|
||||
}
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
|
||||
import { AuthenticationRegulator } from "../../src/lib/AuthenticationRegulator";
|
||||
import * as UserDataStore from "../../src/lib/user_data_store";
|
||||
import * as DataStore from "nedb";
|
||||
import * as MockDate from "mockdate";
|
||||
|
||||
var exceptions = require('../../src/lib/exceptions');
|
||||
|
||||
describe.only('test authentication regulator', function() {
|
||||
it('should mark 2 authentication and regulate (resolve)', function() {
|
||||
var options = {};
|
||||
options.inMemoryOnly = true;
|
||||
var data_store = new UserDataStore(DataStore, options);
|
||||
var regulator = new AuthenticationRegulator(data_store, 10);
|
||||
var user = 'user';
|
||||
|
||||
return regulator.mark(user, false)
|
||||
.then(function() {
|
||||
return regulator.mark(user, true);
|
||||
})
|
||||
.then(function() {
|
||||
return regulator.regulate(user);
|
||||
});
|
||||
});
|
||||
|
||||
it('should mark 3 authentications and regulate (reject)', function(done) {
|
||||
var options = {};
|
||||
options.inMemoryOnly = true;
|
||||
var data_store = new UserDataStore(DataStore, options);
|
||||
var regulator = new AuthenticationRegulator(data_store, 10);
|
||||
var user = 'user';
|
||||
|
||||
regulator.mark(user, false)
|
||||
.then(function() {
|
||||
return regulator.mark(user, false);
|
||||
})
|
||||
.then(function() {
|
||||
return regulator.mark(user, false);
|
||||
})
|
||||
.then(function() {
|
||||
return regulator.regulate(user);
|
||||
})
|
||||
.catch(exceptions.AuthenticationRegulationError, function() {
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('should mark 3 authentications and regulate (resolve)', function(done) {
|
||||
var options = {};
|
||||
options.inMemoryOnly = true;
|
||||
var data_store = new UserDataStore(DataStore, options);
|
||||
var regulator = new AuthenticationRegulator(data_store, 10);
|
||||
var user = 'user';
|
||||
|
||||
MockDate.set('1/2/2000 00:00:00');
|
||||
regulator.mark(user, false)
|
||||
.then(function() {
|
||||
MockDate.set('1/2/2000 00:00:15');
|
||||
return regulator.mark(user, false);
|
||||
})
|
||||
.then(function() {
|
||||
return regulator.mark(user, false);
|
||||
})
|
||||
.then(function() {
|
||||
return regulator.regulate(user);
|
||||
})
|
||||
.then(function() {
|
||||
done();
|
||||
})
|
||||
});
|
||||
});
|
73
test/unitary/AuthenticationRegulator.test.ts
Normal file
73
test/unitary/AuthenticationRegulator.test.ts
Normal file
|
@ -0,0 +1,73 @@
|
|||
|
||||
import { AuthenticationRegulator } from "../../src/lib/AuthenticationRegulator";
|
||||
import UserDataStore from "../../src/lib/UserDataStore";
|
||||
import * as MockDate from "mockdate";
|
||||
|
||||
const exceptions = require("../../src/lib/exceptions");
|
||||
|
||||
describe("test authentication regulator", function() {
|
||||
it("should mark 2 authentication and regulate (resolve)", function() {
|
||||
const options = {
|
||||
inMemoryOnly: true
|
||||
};
|
||||
const data_store = new UserDataStore(options);
|
||||
const regulator = new AuthenticationRegulator(data_store, 10);
|
||||
const user = "user";
|
||||
|
||||
return regulator.mark(user, false)
|
||||
.then(function() {
|
||||
return regulator.mark(user, true);
|
||||
})
|
||||
.then(function() {
|
||||
return regulator.regulate(user);
|
||||
});
|
||||
});
|
||||
|
||||
it("should mark 3 authentications and regulate (reject)", function(done) {
|
||||
const options = {
|
||||
inMemoryOnly: true
|
||||
};
|
||||
const data_store = new UserDataStore(options);
|
||||
const regulator = new AuthenticationRegulator(data_store, 10);
|
||||
const user = "user";
|
||||
|
||||
regulator.mark(user, false)
|
||||
.then(function() {
|
||||
return regulator.mark(user, false);
|
||||
})
|
||||
.then(function() {
|
||||
return regulator.mark(user, false);
|
||||
})
|
||||
.then(function() {
|
||||
return regulator.regulate(user);
|
||||
})
|
||||
.catch(exceptions.AuthenticationRegulationError, function() {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should mark 3 authentications and regulate (resolve)", function(done) {
|
||||
const options = {
|
||||
inMemoryOnly: true
|
||||
};
|
||||
const data_store = new UserDataStore(options);
|
||||
const regulator = new AuthenticationRegulator(data_store, 10);
|
||||
const user = "user";
|
||||
|
||||
MockDate.set("1/2/2000 00:00:00");
|
||||
regulator.mark(user, false)
|
||||
.then(function() {
|
||||
MockDate.set("1/2/2000 00:00:15");
|
||||
return regulator.mark(user, false);
|
||||
})
|
||||
.then(function() {
|
||||
return regulator.mark(user, false);
|
||||
})
|
||||
.then(function() {
|
||||
return regulator.regulate(user);
|
||||
})
|
||||
.then(function() {
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
393
test/unitary/Server.test.ts
Normal file
393
test/unitary/Server.test.ts
Normal file
|
@ -0,0 +1,393 @@
|
|||
|
||||
import Server from "../../src/lib/Server";
|
||||
import Ldap = require("../../src/lib/ldap");
|
||||
|
||||
import * as Promise from "bluebird";
|
||||
import * as speakeasy from "speakeasy";
|
||||
import * as request from "request";
|
||||
import * as nedb from "nedb";
|
||||
import { TOTPSecret } from "../../src/lib/TOTPSecret";
|
||||
|
||||
|
||||
const requestp = Promise.promisifyAll(request) as request.RequestAsync;
|
||||
const assert = require("assert");
|
||||
const sinon = require("sinon");
|
||||
const MockDate = require("mockdate");
|
||||
const session = require("express-session");
|
||||
const winston = require("winston");
|
||||
const ldapjs = require("ldapjs");
|
||||
|
||||
const PORT = 8090;
|
||||
const BASE_URL = "http://localhost:" + PORT;
|
||||
const requests = require("./requests")(PORT);
|
||||
|
||||
describe("test the server", function () {
|
||||
let server: Server;
|
||||
let transporter: object;
|
||||
let u2f: any;
|
||||
|
||||
beforeEach(function () {
|
||||
const config = {
|
||||
port: PORT,
|
||||
totp_secret: "totp_secret",
|
||||
ldap: {
|
||||
url: "ldap://127.0.0.1:389",
|
||||
base_dn: "ou=users,dc=example,dc=com",
|
||||
user_name_attribute: "cn",
|
||||
user: "cn=admin,dc=example,dc=com",
|
||||
password: "password",
|
||||
},
|
||||
session: {
|
||||
secret: "session_secret",
|
||||
expiration: 50000,
|
||||
},
|
||||
store_in_memory: true,
|
||||
notifier: {
|
||||
gmail: {
|
||||
user: "user@example.com",
|
||||
pass: "password"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const ldap_client = {
|
||||
bind: sinon.stub(),
|
||||
search: sinon.stub(),
|
||||
modify: sinon.stub(),
|
||||
on: sinon.spy()
|
||||
};
|
||||
const ldap = {
|
||||
Change: sinon.spy(),
|
||||
createClient: sinon.spy(function () {
|
||||
return ldap_client;
|
||||
})
|
||||
};
|
||||
|
||||
u2f = {
|
||||
startRegistration: sinon.stub(),
|
||||
finishRegistration: sinon.stub(),
|
||||
startAuthentication: sinon.stub(),
|
||||
finishAuthentication: sinon.stub()
|
||||
};
|
||||
|
||||
transporter = {
|
||||
sendMail: sinon.stub().yields()
|
||||
};
|
||||
|
||||
const nodemailer = {
|
||||
createTransport: sinon.spy(function () {
|
||||
return transporter;
|
||||
})
|
||||
};
|
||||
|
||||
const ldap_document = {
|
||||
object: {
|
||||
mail: "test_ok@example.com",
|
||||
}
|
||||
};
|
||||
|
||||
const search_res = {
|
||||
on: sinon.spy(function (event: string, fn: (s: any) => void) {
|
||||
if (event != "error") fn(ldap_document);
|
||||
})
|
||||
};
|
||||
|
||||
ldap_client.bind.withArgs("cn=test_ok,ou=users,dc=example,dc=com",
|
||||
"password").yields(undefined);
|
||||
ldap_client.bind.withArgs("cn=admin,dc=example,dc=com",
|
||||
"password").yields(undefined);
|
||||
|
||||
ldap_client.bind.withArgs("cn=test_nok,ou=users,dc=example,dc=com",
|
||||
"password").yields("error");
|
||||
|
||||
ldap_client.modify.yields(undefined);
|
||||
ldap_client.search.yields(undefined, search_res);
|
||||
|
||||
const deps = {
|
||||
u2f: u2f,
|
||||
nedb: nedb,
|
||||
nodemailer: nodemailer,
|
||||
ldapjs: ldap,
|
||||
session: session,
|
||||
winston: winston,
|
||||
speakeasy: speakeasy
|
||||
};
|
||||
|
||||
server = new Server();
|
||||
return server.start(config, deps);
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
server.stop();
|
||||
});
|
||||
|
||||
describe("test GET /login", function () {
|
||||
test_login();
|
||||
});
|
||||
|
||||
describe("test GET /logout", function () {
|
||||
test_logout();
|
||||
});
|
||||
|
||||
describe("test GET /reset-password-form", function () {
|
||||
test_reset_password_form();
|
||||
});
|
||||
|
||||
describe("test endpoints locks", function () {
|
||||
function should_post_and_reply_with(url: string, status_code: number) {
|
||||
return requestp.postAsync(url).then(function (response: request.RequestResponse) {
|
||||
assert.equal(response.statusCode, status_code);
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
|
||||
function should_get_and_reply_with(url: string, status_code: number) {
|
||||
return requestp.getAsync(url).then(function (response: request.RequestResponse) {
|
||||
assert.equal(response.statusCode, status_code);
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
|
||||
function should_post_and_reply_with_403(url: string) {
|
||||
return should_post_and_reply_with(url, 403);
|
||||
}
|
||||
function should_get_and_reply_with_403(url: string) {
|
||||
return should_get_and_reply_with(url, 403);
|
||||
}
|
||||
|
||||
function should_post_and_reply_with_401(url: string) {
|
||||
return should_post_and_reply_with(url, 401);
|
||||
}
|
||||
function should_get_and_reply_with_401(url: string) {
|
||||
return should_get_and_reply_with(url, 401);
|
||||
}
|
||||
|
||||
function should_get_and_post_reply_with_403(url: string) {
|
||||
const p1 = should_post_and_reply_with_403(url);
|
||||
const p2 = should_get_and_reply_with_403(url);
|
||||
return Promise.all([p1, p2]);
|
||||
}
|
||||
|
||||
it("should block /new-password", function () {
|
||||
return should_post_and_reply_with_403(BASE_URL + "/new-password");
|
||||
});
|
||||
|
||||
it("should block /u2f-register", function () {
|
||||
return should_get_and_post_reply_with_403(BASE_URL + "/u2f-register");
|
||||
});
|
||||
|
||||
it("should block /reset-password", function () {
|
||||
return should_get_and_post_reply_with_403(BASE_URL + "/reset-password");
|
||||
});
|
||||
|
||||
it("should block /2ndfactor/u2f/register_request", function () {
|
||||
return should_get_and_reply_with_403(BASE_URL + "/2ndfactor/u2f/register_request");
|
||||
});
|
||||
|
||||
it("should block /2ndfactor/u2f/register", function () {
|
||||
return should_post_and_reply_with_403(BASE_URL + "/2ndfactor/u2f/register");
|
||||
});
|
||||
|
||||
it("should block /2ndfactor/u2f/sign_request", function () {
|
||||
return should_get_and_reply_with_403(BASE_URL + "/2ndfactor/u2f/sign_request");
|
||||
});
|
||||
|
||||
it("should block /2ndfactor/u2f/sign", function () {
|
||||
return should_post_and_reply_with_403(BASE_URL + "/2ndfactor/u2f/sign");
|
||||
});
|
||||
});
|
||||
|
||||
describe("test authentication and verification", function () {
|
||||
test_authentication();
|
||||
test_reset_password();
|
||||
test_regulation();
|
||||
});
|
||||
|
||||
function test_reset_password_form() {
|
||||
it("should serve the reset password form page", function (done) {
|
||||
requestp.getAsync(BASE_URL + "/reset-password-form")
|
||||
.then(function (response: request.RequestResponse) {
|
||||
assert.equal(response.statusCode, 200);
|
||||
done();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function test_login() {
|
||||
it("should serve the login page", function (done) {
|
||||
requestp.getAsync(BASE_URL + "/login")
|
||||
.then(function (response: request.RequestResponse) {
|
||||
assert.equal(response.statusCode, 200);
|
||||
done();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function test_logout() {
|
||||
it("should logout and redirect to /", function (done) {
|
||||
requestp.getAsync(BASE_URL + "/logout")
|
||||
.then(function (response: any) {
|
||||
assert.equal(response.req.path, "/");
|
||||
done();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function test_authentication() {
|
||||
it("should return status code 401 when user is not authenticated", function () {
|
||||
return requestp.getAsync({ url: BASE_URL + "/verify" })
|
||||
.then(function (response: request.RequestResponse) {
|
||||
assert.equal(response.statusCode, 401);
|
||||
return Promise.resolve();
|
||||
});
|
||||
});
|
||||
|
||||
it("should return status code 204 when user is authenticated using totp", function () {
|
||||
const j = requestp.jar();
|
||||
return requests.login(j)
|
||||
.then(function (res: request.RequestResponse) {
|
||||
assert.equal(res.statusCode, 200, "get login page failed");
|
||||
return requests.first_factor(j);
|
||||
})
|
||||
.then(function (res: request.RequestResponse) {
|
||||
assert.equal(res.statusCode, 204, "first factor failed");
|
||||
return requests.register_totp(j, transporter);
|
||||
})
|
||||
.then(function (secret: string) {
|
||||
const sec = JSON.parse(secret) as TOTPSecret;
|
||||
const real_token = speakeasy.totp({
|
||||
secret: sec.base32,
|
||||
encoding: "base32"
|
||||
});
|
||||
return requests.totp(j, real_token);
|
||||
})
|
||||
.then(function (res: request.RequestResponse) {
|
||||
assert.equal(res.statusCode, 204, "second factor failed");
|
||||
return requests.verify(j);
|
||||
})
|
||||
.then(function (res: request.RequestResponse) {
|
||||
assert.equal(res.statusCode, 204, "verify failed");
|
||||
return Promise.resolve();
|
||||
});
|
||||
});
|
||||
|
||||
it("should keep session variables when login page is reloaded", function () {
|
||||
const real_token = speakeasy.totp({
|
||||
secret: "totp_secret",
|
||||
encoding: "base32"
|
||||
});
|
||||
const j = requestp.jar();
|
||||
return requests.login(j)
|
||||
.then(function (res: request.RequestResponse) {
|
||||
assert.equal(res.statusCode, 200, "get login page failed");
|
||||
return requests.first_factor(j);
|
||||
})
|
||||
.then(function (res: request.RequestResponse) {
|
||||
assert.equal(res.statusCode, 204, "first factor failed");
|
||||
return requests.totp(j, real_token);
|
||||
})
|
||||
.then(function (res: request.RequestResponse) {
|
||||
assert.equal(res.statusCode, 204, "second factor failed");
|
||||
return requests.login(j);
|
||||
})
|
||||
.then(function (res: request.RequestResponse) {
|
||||
assert.equal(res.statusCode, 200, "login page loading failed");
|
||||
return requests.verify(j);
|
||||
})
|
||||
.then(function (res: request.RequestResponse) {
|
||||
assert.equal(res.statusCode, 204, "verify failed");
|
||||
return Promise.resolve();
|
||||
})
|
||||
.catch(function (err: Error) {
|
||||
console.error(err);
|
||||
});
|
||||
});
|
||||
|
||||
it("should return status code 204 when user is authenticated using u2f", function () {
|
||||
const sign_request = {};
|
||||
const sign_status = {};
|
||||
const registration_request = {};
|
||||
const registration_status = {};
|
||||
u2f.startRegistration.returns(Promise.resolve(sign_request));
|
||||
u2f.finishRegistration.returns(Promise.resolve(sign_status));
|
||||
u2f.startAuthentication.returns(Promise.resolve(registration_request));
|
||||
u2f.finishAuthentication.returns(Promise.resolve(registration_status));
|
||||
|
||||
const j = requestp.jar();
|
||||
return requests.login(j)
|
||||
.then(function (res: request.RequestResponse) {
|
||||
assert.equal(res.statusCode, 200, "get login page failed");
|
||||
return requests.first_factor(j);
|
||||
})
|
||||
.then(function (res: request.RequestResponse) {
|
||||
assert.equal(res.statusCode, 204, "first factor failed");
|
||||
return requests.u2f_registration(j, transporter);
|
||||
})
|
||||
.then(function (res: request.RequestResponse) {
|
||||
assert.equal(res.statusCode, 204, "second factor, finish register failed");
|
||||
return requests.u2f_authentication(j);
|
||||
})
|
||||
.then(function (res: request.RequestResponse) {
|
||||
assert.equal(res.statusCode, 204, "second factor, finish sign failed");
|
||||
return requests.verify(j);
|
||||
})
|
||||
.then(function (res: request.RequestResponse) {
|
||||
assert.equal(res.statusCode, 204, "verify failed");
|
||||
return Promise.resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function test_reset_password() {
|
||||
it("should reset the password", function () {
|
||||
const j = requestp.jar();
|
||||
return requests.login(j)
|
||||
.then(function (res: request.RequestResponse) {
|
||||
assert.equal(res.statusCode, 200, "get login page failed");
|
||||
return requests.first_factor(j);
|
||||
})
|
||||
.then(function (res: request.RequestResponse) {
|
||||
assert.equal(res.statusCode, 204, "first factor failed");
|
||||
return requests.reset_password(j, transporter, "user", "new-password");
|
||||
})
|
||||
.then(function (res: request.RequestResponse) {
|
||||
assert.equal(res.statusCode, 204, "second factor, finish register failed");
|
||||
return Promise.resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function test_regulation() {
|
||||
it("should regulate authentication", function () {
|
||||
const j = requestp.jar();
|
||||
MockDate.set("1/2/2017 00:00:00");
|
||||
return requests.login(j)
|
||||
.then(function (res: request.RequestResponse) {
|
||||
assert.equal(res.statusCode, 200, "get login page failed");
|
||||
return requests.failing_first_factor(j);
|
||||
})
|
||||
.then(function (res: request.RequestResponse) {
|
||||
assert.equal(res.statusCode, 401, "first factor failed");
|
||||
return requests.failing_first_factor(j);
|
||||
})
|
||||
.then(function (res: request.RequestResponse) {
|
||||
assert.equal(res.statusCode, 401, "first factor failed");
|
||||
return requests.failing_first_factor(j);
|
||||
})
|
||||
.then(function (res: request.RequestResponse) {
|
||||
assert.equal(res.statusCode, 401, "first factor failed");
|
||||
return requests.failing_first_factor(j);
|
||||
})
|
||||
.then(function (res: request.RequestResponse) {
|
||||
assert.equal(res.statusCode, 403, "first factor failed");
|
||||
MockDate.set("1/2/2017 00:30:00");
|
||||
return requests.failing_first_factor(j);
|
||||
})
|
||||
.then(function (res: request.RequestResponse) {
|
||||
assert.equal(res.statusCode, 401, "first factor failed");
|
||||
return Promise.resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
206
test/unitary/UserDataStore.test.ts
Normal file
206
test/unitary/UserDataStore.test.ts
Normal file
|
@ -0,0 +1,206 @@
|
|||
|
||||
import UserDataStore from "../../src/lib/UserDataStore";
|
||||
import { U2FMetaDocument, Options } from "../../src/lib/UserDataStore";
|
||||
|
||||
import DataStore = require("nedb");
|
||||
import assert = require("assert");
|
||||
import Promise = require("bluebird");
|
||||
import sinon = require("sinon");
|
||||
import MockDate = require("mockdate");
|
||||
|
||||
describe("test user data store", () => {
|
||||
let options: Options;
|
||||
|
||||
beforeEach(function () {
|
||||
options = {
|
||||
inMemoryOnly: true
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
describe("test u2f meta", () => {
|
||||
it("should save a u2f meta", function () {
|
||||
const data_store = new UserDataStore(options);
|
||||
|
||||
const userid = "user";
|
||||
const app_id = "https://localhost";
|
||||
const meta = {
|
||||
publicKey: "pbk"
|
||||
};
|
||||
|
||||
return data_store.set_u2f_meta(userid, app_id, meta)
|
||||
.then(function (numUpdated) {
|
||||
assert.equal(1, numUpdated);
|
||||
return Promise.resolve();
|
||||
});
|
||||
});
|
||||
|
||||
it("should retrieve no u2f meta", function () {
|
||||
const options = {
|
||||
inMemoryOnly: true
|
||||
};
|
||||
|
||||
const data_store = new UserDataStore(options);
|
||||
|
||||
const userid = "user";
|
||||
const app_id = "https://localhost";
|
||||
const meta = {
|
||||
publicKey: "pbk"
|
||||
};
|
||||
|
||||
return data_store.get_u2f_meta(userid, app_id)
|
||||
.then(function (doc) {
|
||||
assert.equal(undefined, doc);
|
||||
return Promise.resolve();
|
||||
});
|
||||
});
|
||||
|
||||
it("should insert and retrieve a u2f meta", function () {
|
||||
const options = {
|
||||
inMemoryOnly: true
|
||||
};
|
||||
|
||||
const data_store = new UserDataStore(options);
|
||||
|
||||
const userid = "user";
|
||||
const app_id = "https://localhost";
|
||||
const meta = {
|
||||
publicKey: "pbk"
|
||||
};
|
||||
|
||||
return data_store.set_u2f_meta(userid, app_id, meta)
|
||||
.then(function (numUpdated: number) {
|
||||
assert.equal(1, numUpdated);
|
||||
return data_store.get_u2f_meta(userid, app_id);
|
||||
})
|
||||
.then(function (doc: U2FMetaDocument) {
|
||||
assert.deepEqual(meta, doc.meta);
|
||||
assert.deepEqual(userid, doc.userid);
|
||||
assert.deepEqual(app_id, doc.appid);
|
||||
assert("_id" in doc);
|
||||
return Promise.resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe("test u2f registration token", () => {
|
||||
it("should save u2f registration token", function () {
|
||||
const data_store = new UserDataStore(options);
|
||||
|
||||
const userid = "user";
|
||||
const token = "token";
|
||||
const max_age = 60;
|
||||
const content = "abc";
|
||||
|
||||
return data_store.issue_identity_check_token(userid, token, content, max_age)
|
||||
.then(function (document) {
|
||||
assert.equal(document.userid, userid);
|
||||
assert.equal(document.token, token);
|
||||
assert.deepEqual(document.content, { userid: "user", data: content });
|
||||
assert("max_date" in document);
|
||||
assert("_id" in document);
|
||||
return Promise.resolve();
|
||||
})
|
||||
.catch(function (err) {
|
||||
console.error(err);
|
||||
return Promise.reject(err);
|
||||
});
|
||||
});
|
||||
|
||||
it("should save u2f registration token and consume it", function (done) {
|
||||
const data_store = new UserDataStore(options);
|
||||
|
||||
const userid = "user";
|
||||
const token = "token";
|
||||
const max_age = 50;
|
||||
|
||||
data_store.issue_identity_check_token(userid, token, {}, max_age)
|
||||
.then(function (document) {
|
||||
return data_store.consume_identity_check_token(token);
|
||||
})
|
||||
.then(function () {
|
||||
done();
|
||||
})
|
||||
.catch(function (err) {
|
||||
console.error(err);
|
||||
});
|
||||
});
|
||||
|
||||
it("should not be able to consume registration token twice", function (done) {
|
||||
const data_store = new UserDataStore(options);
|
||||
|
||||
const userid = "user";
|
||||
const token = "token";
|
||||
const max_age = 50;
|
||||
|
||||
data_store.issue_identity_check_token(userid, token, {}, max_age)
|
||||
.then(function (document) {
|
||||
return data_store.consume_identity_check_token(token);
|
||||
})
|
||||
.then(function (document) {
|
||||
return data_store.consume_identity_check_token(token);
|
||||
})
|
||||
.catch(function (err) {
|
||||
console.error(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should fail when token does not exist", function () {
|
||||
const data_store = new UserDataStore(options);
|
||||
|
||||
const token = "token";
|
||||
|
||||
return data_store.consume_identity_check_token(token)
|
||||
.then(function (document) {
|
||||
return Promise.reject("Error while checking token");
|
||||
})
|
||||
.catch(function (err) {
|
||||
return Promise.resolve(err);
|
||||
});
|
||||
});
|
||||
|
||||
it("should fail when token expired", function (done) {
|
||||
const data_store = new UserDataStore(options);
|
||||
|
||||
const userid = "user";
|
||||
const token = "token";
|
||||
const max_age = 60;
|
||||
MockDate.set("1/1/2000");
|
||||
|
||||
data_store.issue_identity_check_token(userid, token, {}, max_age)
|
||||
.then(function () {
|
||||
MockDate.set("1/2/2000");
|
||||
return data_store.consume_identity_check_token(token);
|
||||
})
|
||||
.catch(function (err) {
|
||||
MockDate.reset();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should save the userid and some data with the token", function (done) {
|
||||
const data_store = new UserDataStore(options);
|
||||
|
||||
const userid = "user";
|
||||
const token = "token";
|
||||
const max_age = 60;
|
||||
MockDate.set("1/1/2000");
|
||||
const data = "abc";
|
||||
|
||||
data_store.issue_identity_check_token(userid, token, data, max_age)
|
||||
.then(function () {
|
||||
return data_store.consume_identity_check_token(token);
|
||||
})
|
||||
.then(function (content) {
|
||||
const expected_content = {
|
||||
userid: "user",
|
||||
data: "abc"
|
||||
};
|
||||
assert.deepEqual(content, expected_content);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,19 +1,31 @@
|
|||
import * as Assert from "assert";
|
||||
import { UserConfiguration } from "../../src/lib/Configuration";
|
||||
import config_adapter = require("../../src/lib/config_adapter");
|
||||
|
||||
const config_adapter = require("../../src/lib/config_adapter");
|
||||
|
||||
describe("test config adapter", function() {
|
||||
function build_yaml_config(): any {
|
||||
function build_yaml_config(): UserConfiguration {
|
||||
const yaml_config = {
|
||||
port: 8080,
|
||||
ldap: {},
|
||||
ldap: {
|
||||
url: "http://ldap",
|
||||
base_dn: "cn=test,dc=example,dc=com",
|
||||
user: "user",
|
||||
password: "pass"
|
||||
},
|
||||
session: {
|
||||
domain: "example.com",
|
||||
secret: "secret",
|
||||
max_age: 40000
|
||||
},
|
||||
store_directory: "/mydirectory",
|
||||
logs_level: "debug"
|
||||
logs_level: "debug",
|
||||
notifier: {
|
||||
gmail: {
|
||||
user: "user",
|
||||
pass: "password"
|
||||
}
|
||||
}
|
||||
};
|
||||
return yaml_config;
|
||||
}
|
||||
|
@ -36,8 +48,9 @@ describe("test config adapter", function() {
|
|||
const yaml_config = build_yaml_config();
|
||||
yaml_config.ldap = {
|
||||
url: "http://ldap",
|
||||
user_search_base: "ou=groups,dc=example,dc=com",
|
||||
user_search_filter: "uid",
|
||||
base_dn: "cn=test,dc=example,dc=com",
|
||||
additional_user_dn: "ou=users",
|
||||
user_name_attribute: "uid",
|
||||
user: "admin",
|
||||
password: "pass"
|
||||
};
|
||||
|
@ -45,8 +58,8 @@ describe("test config adapter", function() {
|
|||
const config = config_adapter(yaml_config);
|
||||
|
||||
Assert.equal(config.ldap.url, "http://ldap");
|
||||
Assert.equal(config.ldap.user_search_base, "ou=groups,dc=example,dc=com");
|
||||
Assert.equal(config.ldap.user_search_filter, "uid");
|
||||
Assert.equal(config.ldap.additional_user_dn, "ou=users");
|
||||
Assert.equal(config.ldap.user_name_attribute, "uid");
|
||||
Assert.equal(config.ldap.user, "admin");
|
||||
Assert.equal(config.ldap.password, "pass");
|
||||
});
|
||||
|
@ -59,9 +72,9 @@ describe("test config adapter", function() {
|
|||
expiration: 3600
|
||||
};
|
||||
const config = config_adapter(yaml_config);
|
||||
Assert.equal(config.session_domain, "example.com");
|
||||
Assert.equal(config.session_secret, "secret");
|
||||
Assert.equal(config.session_max_age, 3600);
|
||||
Assert.equal(config.session.domain, "example.com");
|
||||
Assert.equal(config.session.secret, "secret");
|
||||
Assert.equal(config.session.expiration, 3600);
|
||||
});
|
||||
|
||||
it("should get the log level", function() {
|
||||
|
@ -73,15 +86,33 @@ describe("test config adapter", function() {
|
|||
|
||||
it("should get the notifier config", function() {
|
||||
const yaml_config = build_yaml_config();
|
||||
yaml_config.notifier = "notifier";
|
||||
yaml_config.notifier = {
|
||||
gmail: {
|
||||
user: "user",
|
||||
pass: "pass"
|
||||
}
|
||||
};
|
||||
const config = config_adapter(yaml_config);
|
||||
Assert.equal(config.notifier, "notifier");
|
||||
Assert.deepEqual(config.notifier, {
|
||||
gmail: {
|
||||
user: "user",
|
||||
pass: "pass"
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("should get the access_control config", function() {
|
||||
const yaml_config = build_yaml_config();
|
||||
yaml_config.access_control = "access_control";
|
||||
yaml_config.access_control = {
|
||||
default: [],
|
||||
users: {},
|
||||
groups: {}
|
||||
};
|
||||
const config = config_adapter(yaml_config);
|
||||
Assert.equal(config.access_control, "access_control");
|
||||
Assert.deepEqual(config.access_control, {
|
||||
default: [],
|
||||
users: {},
|
||||
groups: {}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
179
test/unitary/data_persistence.test.ts
Normal file
179
test/unitary/data_persistence.test.ts
Normal file
|
@ -0,0 +1,179 @@
|
|||
|
||||
import * as Promise from "bluebird";
|
||||
import * as request from "request";
|
||||
|
||||
import Server from "../../src/lib/Server";
|
||||
import { UserConfiguration } from "../../src/lib/Configuration";
|
||||
import { GlobalDependencies } from "../../src/lib/GlobalDependencies";
|
||||
import * as tmp from "tmp";
|
||||
|
||||
|
||||
const requestp = Promise.promisifyAll(request) as request.Request;
|
||||
const assert = require("assert");
|
||||
const speakeasy = require("speakeasy");
|
||||
const sinon = require("sinon");
|
||||
const nedb = require("nedb");
|
||||
const session = require("express-session");
|
||||
const winston = require("winston");
|
||||
|
||||
const PORT = 8050;
|
||||
const requests = require("./requests")(PORT);
|
||||
|
||||
describe("test data persistence", function () {
|
||||
let u2f: any;
|
||||
let tmpDir: tmp.SynchrounousResult;
|
||||
const ldap_client = {
|
||||
bind: sinon.stub(),
|
||||
search: sinon.stub(),
|
||||
on: sinon.spy()
|
||||
};
|
||||
const ldap = {
|
||||
createClient: sinon.spy(function () {
|
||||
return ldap_client;
|
||||
})
|
||||
};
|
||||
|
||||
let config: UserConfiguration;
|
||||
|
||||
before(function () {
|
||||
u2f = {
|
||||
startRegistration: sinon.stub(),
|
||||
finishRegistration: sinon.stub(),
|
||||
startAuthentication: sinon.stub(),
|
||||
finishAuthentication: sinon.stub()
|
||||
};
|
||||
|
||||
const search_doc = {
|
||||
object: {
|
||||
mail: "test_ok@example.com"
|
||||
}
|
||||
};
|
||||
|
||||
const search_res = {
|
||||
on: sinon.spy(function (event: string, fn: (s: object) => void) {
|
||||
if (event != "error") fn(search_doc);
|
||||
})
|
||||
};
|
||||
|
||||
ldap_client.bind.withArgs("cn=test_ok,ou=users,dc=example,dc=com",
|
||||
"password").yields(undefined);
|
||||
ldap_client.bind.withArgs("cn=test_nok,ou=users,dc=example,dc=com",
|
||||
"password").yields("error");
|
||||
ldap_client.search.yields(undefined, search_res);
|
||||
|
||||
tmpDir = tmp.dirSync({ unsafeCleanup: true });
|
||||
config = {
|
||||
port: PORT,
|
||||
ldap: {
|
||||
url: "ldap://127.0.0.1:389",
|
||||
base_dn: "ou=users,dc=example,dc=com",
|
||||
user: "user",
|
||||
password: "password"
|
||||
},
|
||||
session: {
|
||||
secret: "session_secret",
|
||||
expiration: 50000,
|
||||
},
|
||||
store_directory: tmpDir.name,
|
||||
notifier: {
|
||||
gmail: {
|
||||
user: "user@example.com",
|
||||
pass: "password"
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
after(function () {
|
||||
tmpDir.removeCallback();
|
||||
});
|
||||
|
||||
it("should save a u2f meta and reload it after a restart of the server", function () {
|
||||
let server: Server;
|
||||
const sign_request = {};
|
||||
const sign_status = {};
|
||||
const registration_request = {};
|
||||
const registration_status = {};
|
||||
u2f.startRegistration.returns(Promise.resolve(sign_request));
|
||||
u2f.finishRegistration.returns(Promise.resolve(sign_status));
|
||||
u2f.startAuthentication.returns(Promise.resolve(registration_request));
|
||||
u2f.finishAuthentication.returns(Promise.resolve(registration_status));
|
||||
|
||||
const nodemailer = {
|
||||
createTransport: sinon.spy(function () {
|
||||
return transporter;
|
||||
})
|
||||
};
|
||||
const transporter = {
|
||||
sendMail: sinon.stub().yields()
|
||||
};
|
||||
|
||||
const deps = {
|
||||
u2f: u2f,
|
||||
nedb: nedb,
|
||||
nodemailer: nodemailer,
|
||||
session: session,
|
||||
winston: winston,
|
||||
ldapjs: ldap,
|
||||
speakeasy: speakeasy
|
||||
} as GlobalDependencies;
|
||||
|
||||
const j1 = request.jar();
|
||||
const j2 = request.jar();
|
||||
|
||||
return start_server(config, deps)
|
||||
.then(function (s) {
|
||||
server = s;
|
||||
return requests.login(j1);
|
||||
})
|
||||
.then(function (res) {
|
||||
return requests.first_factor(j1);
|
||||
})
|
||||
.then(function () {
|
||||
return requests.u2f_registration(j1, transporter);
|
||||
})
|
||||
.then(function () {
|
||||
return requests.u2f_authentication(j1);
|
||||
})
|
||||
.then(function () {
|
||||
return stop_server(server);
|
||||
})
|
||||
.then(function () {
|
||||
return start_server(config, deps);
|
||||
})
|
||||
.then(function (s) {
|
||||
server = s;
|
||||
return requests.login(j2);
|
||||
})
|
||||
.then(function () {
|
||||
return requests.first_factor(j2);
|
||||
})
|
||||
.then(function () {
|
||||
return requests.u2f_authentication(j2);
|
||||
})
|
||||
.then(function (res) {
|
||||
assert.equal(204, res.statusCode);
|
||||
server.stop();
|
||||
return Promise.resolve();
|
||||
})
|
||||
.catch(function (err) {
|
||||
console.error(err);
|
||||
return Promise.reject(err);
|
||||
});
|
||||
});
|
||||
|
||||
function start_server(config: UserConfiguration, deps: GlobalDependencies): Promise<Server> {
|
||||
return new Promise<Server>(function (resolve, reject) {
|
||||
const s = new Server();
|
||||
s.start(config, deps);
|
||||
resolve(s);
|
||||
});
|
||||
}
|
||||
|
||||
function stop_server(s: Server) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
s.stop();
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
});
|
72
test/unitary/server_config.test.ts
Normal file
72
test/unitary/server_config.test.ts
Normal file
|
@ -0,0 +1,72 @@
|
|||
|
||||
import * as assert from "assert";
|
||||
import * as sinon from "sinon";
|
||||
import nedb = require("nedb");
|
||||
import * as express from "express";
|
||||
import * as winston from "winston";
|
||||
import * as speakeasy from "speakeasy";
|
||||
import * as u2f from "authdog";
|
||||
|
||||
import { AppConfiguration, UserConfiguration } from "../../src/lib/Configuration";
|
||||
import { GlobalDependencies } from "../../src/lib/GlobalDependencies";
|
||||
import Server from "../../src/lib/Server";
|
||||
|
||||
|
||||
describe("test server configuration", function () {
|
||||
let deps: GlobalDependencies;
|
||||
|
||||
before(function () {
|
||||
const transporter = {
|
||||
sendMail: sinon.stub().yields()
|
||||
};
|
||||
|
||||
const nodemailer = {
|
||||
createTransport: sinon.spy(function () {
|
||||
return transporter;
|
||||
})
|
||||
};
|
||||
|
||||
deps = {
|
||||
speakeasy: speakeasy,
|
||||
u2f: u2f,
|
||||
nedb: nedb,
|
||||
winston: winston,
|
||||
nodemailer: nodemailer,
|
||||
ldapjs: {
|
||||
createClient: sinon.spy(function () {
|
||||
return { on: sinon.spy() };
|
||||
})
|
||||
},
|
||||
session: sinon.spy(function () {
|
||||
return function (req: express.Request, res: express.Response, next: express.NextFunction) { next(); };
|
||||
})
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
it("should set cookie scope to domain set in the config", function () {
|
||||
const config = {
|
||||
session: {
|
||||
domain: "example.com",
|
||||
secret: "secret"
|
||||
},
|
||||
ldap: {
|
||||
url: "http://ldap",
|
||||
user: "user",
|
||||
password: "password"
|
||||
},
|
||||
notifier: {
|
||||
gmail: {
|
||||
user: "user@example.com",
|
||||
pass: "password"
|
||||
}
|
||||
}
|
||||
} as UserConfiguration;
|
||||
|
||||
const server = new Server();
|
||||
server.start(config, deps);
|
||||
|
||||
assert(deps.session.calledOnce);
|
||||
assert.equal(deps.session.getCall(0).args[0].cookie.domain, "example.com");
|
||||
});
|
||||
});
|
|
@ -1,162 +0,0 @@
|
|||
|
||||
var server = require('../../src/lib/server');
|
||||
|
||||
var Promise = require('bluebird');
|
||||
var request = Promise.promisifyAll(require('request'));
|
||||
var assert = require('assert');
|
||||
var speakeasy = require('speakeasy');
|
||||
var sinon = require('sinon');
|
||||
var tmp = require('tmp');
|
||||
var nedb = require('nedb');
|
||||
var session = require('express-session');
|
||||
var winston = require('winston');
|
||||
|
||||
var PORT = 8050;
|
||||
var requests = require('./requests')(PORT);
|
||||
|
||||
|
||||
describe('test data persistence', function() {
|
||||
var u2f;
|
||||
var tmpDir;
|
||||
var ldap_client = {
|
||||
bind: sinon.stub(),
|
||||
search: sinon.stub(),
|
||||
on: sinon.spy()
|
||||
};
|
||||
var ldap = {
|
||||
createClient: sinon.spy(function() {
|
||||
return ldap_client;
|
||||
})
|
||||
}
|
||||
var config;
|
||||
|
||||
before(function() {
|
||||
u2f = {};
|
||||
u2f.startRegistration = sinon.stub();
|
||||
u2f.finishRegistration = sinon.stub();
|
||||
u2f.startAuthentication = sinon.stub();
|
||||
u2f.finishAuthentication = sinon.stub();
|
||||
|
||||
var search_doc = {
|
||||
object: {
|
||||
mail: 'test_ok@example.com'
|
||||
}
|
||||
};
|
||||
|
||||
var search_res = {};
|
||||
search_res.on = sinon.spy(function(event, fn) {
|
||||
if(event != 'error') fn(search_doc);
|
||||
});
|
||||
|
||||
ldap_client.bind.withArgs('cn=test_ok,ou=users,dc=example,dc=com',
|
||||
'password').yields(undefined);
|
||||
ldap_client.bind.withArgs('cn=test_nok,ou=users,dc=example,dc=com',
|
||||
'password').yields('error');
|
||||
ldap_client.search.yields(undefined, search_res);
|
||||
|
||||
tmpDir = tmp.dirSync({ unsafeCleanup: true });
|
||||
config = {
|
||||
port: PORT,
|
||||
totp_secret: 'totp_secret',
|
||||
ldap: {
|
||||
url: 'ldap://127.0.0.1:389',
|
||||
base_dn: 'ou=users,dc=example,dc=com',
|
||||
},
|
||||
session: {
|
||||
secret: 'session_secret',
|
||||
expiration: 50000,
|
||||
},
|
||||
store_directory: tmpDir.name,
|
||||
notifier: { gmail: { user: 'user@example.com', pass: 'password' } }
|
||||
};
|
||||
});
|
||||
|
||||
after(function() {
|
||||
tmpDir.removeCallback();
|
||||
});
|
||||
|
||||
it('should save a u2f meta and reload it after a restart of the server', function() {
|
||||
var server;
|
||||
var sign_request = {};
|
||||
var sign_status = {};
|
||||
var registration_request = {};
|
||||
var registration_status = {};
|
||||
u2f.startRegistration.returns(Promise.resolve(sign_request));
|
||||
u2f.finishRegistration.returns(Promise.resolve(sign_status));
|
||||
u2f.startAuthentication.returns(Promise.resolve(registration_request));
|
||||
u2f.finishAuthentication.returns(Promise.resolve(registration_status));
|
||||
|
||||
var nodemailer = {};
|
||||
var transporter = {
|
||||
sendMail: sinon.stub().yields()
|
||||
};
|
||||
nodemailer.createTransport = sinon.spy(function() {
|
||||
return transporter;
|
||||
});
|
||||
|
||||
var deps = {};
|
||||
deps.u2f = u2f;
|
||||
deps.nedb = nedb;
|
||||
deps.nodemailer = nodemailer;
|
||||
deps.session = session;
|
||||
deps.winston = winston;
|
||||
deps.ldapjs = ldap;
|
||||
|
||||
var j1 = request.jar();
|
||||
var j2 = request.jar();
|
||||
|
||||
return start_server(config, deps)
|
||||
.then(function(s) {
|
||||
server = s;
|
||||
return requests.login(j1);
|
||||
})
|
||||
.then(function(res) {
|
||||
return requests.first_factor(j1);
|
||||
})
|
||||
.then(function() {
|
||||
return requests.u2f_registration(j1, transporter);
|
||||
})
|
||||
.then(function() {
|
||||
return requests.u2f_authentication(j1);
|
||||
})
|
||||
.then(function() {
|
||||
return stop_server(server);
|
||||
})
|
||||
.then(function() {
|
||||
return start_server(config, deps)
|
||||
})
|
||||
.then(function(s) {
|
||||
server = s;
|
||||
return requests.login(j2);
|
||||
})
|
||||
.then(function() {
|
||||
return requests.first_factor(j2);
|
||||
})
|
||||
.then(function() {
|
||||
return requests.u2f_authentication(j2);
|
||||
})
|
||||
.then(function(res) {
|
||||
assert.equal(204, res.statusCode);
|
||||
server.close();
|
||||
return Promise.resolve();
|
||||
})
|
||||
.catch(function(err) {
|
||||
console.error(err);
|
||||
return Promise.reject(err);
|
||||
});
|
||||
});
|
||||
|
||||
function start_server(config, deps) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
var s = server.run(config, deps);
|
||||
resolve(s);
|
||||
});
|
||||
}
|
||||
|
||||
function stop_server(s) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
s.close();
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
});
|
|
@ -1,389 +0,0 @@
|
|||
|
||||
var server = require('../../src/lib/server');
|
||||
var Ldap = require('../../src/lib/ldap');
|
||||
|
||||
var Promise = require('bluebird');
|
||||
var request = Promise.promisifyAll(require('request'));
|
||||
var assert = require('assert');
|
||||
var speakeasy = require('speakeasy');
|
||||
var sinon = require('sinon');
|
||||
var MockDate = require('mockdate');
|
||||
var session = require('express-session');
|
||||
var winston = require('winston');
|
||||
var speakeasy = require('speakeasy');
|
||||
var ldapjs = require('ldapjs');
|
||||
|
||||
var PORT = 8090;
|
||||
var BASE_URL = 'http://localhost:' + PORT;
|
||||
var requests = require('./requests')(PORT);
|
||||
|
||||
describe('test the server', function() {
|
||||
var _server
|
||||
var deps;
|
||||
var u2f, nedb;
|
||||
var transporter;
|
||||
var collection;
|
||||
|
||||
beforeEach(function(done) {
|
||||
var config = {
|
||||
port: PORT,
|
||||
totp_secret: 'totp_secret',
|
||||
ldap: {
|
||||
url: 'ldap://127.0.0.1:389',
|
||||
base_dn: 'ou=users,dc=example,dc=com',
|
||||
user_name_attribute: 'cn',
|
||||
user: 'cn=admin,dc=example,dc=com',
|
||||
password: 'password',
|
||||
},
|
||||
session: {
|
||||
secret: 'session_secret',
|
||||
expiration: 50000,
|
||||
},
|
||||
store_in_memory: true,
|
||||
notifier: {
|
||||
gmail: {
|
||||
user: 'user@example.com',
|
||||
pass: 'password'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var ldap_client = {
|
||||
bind: sinon.stub(),
|
||||
search: sinon.stub(),
|
||||
modify: sinon.stub(),
|
||||
on: sinon.spy()
|
||||
};
|
||||
var ldap = {
|
||||
Change: sinon.spy(),
|
||||
createClient: sinon.spy(function() {
|
||||
return ldap_client;
|
||||
})
|
||||
};
|
||||
|
||||
u2f = {};
|
||||
u2f.startRegistration = sinon.stub();
|
||||
u2f.finishRegistration = sinon.stub();
|
||||
u2f.startAuthentication = sinon.stub();
|
||||
u2f.finishAuthentication = sinon.stub();
|
||||
|
||||
nedb = require('nedb');
|
||||
|
||||
transporter = {};
|
||||
transporter.sendMail = sinon.stub().yields();
|
||||
|
||||
var nodemailer = {};
|
||||
nodemailer.createTransport = sinon.spy(function() {
|
||||
return transporter;
|
||||
});
|
||||
|
||||
ldap_document = {
|
||||
object: {
|
||||
mail: 'test_ok@example.com',
|
||||
}
|
||||
};
|
||||
|
||||
var search_res = {};
|
||||
search_res.on = sinon.spy(function(event, fn) {
|
||||
if(event != 'error') fn(ldap_document);
|
||||
});
|
||||
|
||||
ldap_client.bind.withArgs('cn=test_ok,ou=users,dc=example,dc=com',
|
||||
'password').yields(undefined);
|
||||
ldap_client.bind.withArgs('cn=admin,dc=example,dc=com',
|
||||
'password').yields(undefined);
|
||||
|
||||
ldap_client.bind.withArgs('cn=test_nok,ou=users,dc=example,dc=com',
|
||||
'password').yields('error');
|
||||
|
||||
ldap_client.modify.yields(undefined);
|
||||
ldap_client.search.yields(undefined, search_res);
|
||||
|
||||
var deps = {};
|
||||
deps.u2f = u2f;
|
||||
deps.nedb = nedb;
|
||||
deps.nodemailer = nodemailer;
|
||||
deps.ldapjs = ldap;
|
||||
deps.session = session;
|
||||
deps.winston = winston;
|
||||
deps.speakeasy = speakeasy;
|
||||
|
||||
_server = server.run(config, deps, function() {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
_server.close();
|
||||
});
|
||||
|
||||
describe('test GET /login', function() {
|
||||
test_login();
|
||||
});
|
||||
|
||||
describe('test GET /logout', function() {
|
||||
test_logout();
|
||||
});
|
||||
|
||||
describe('test GET /reset-password-form', function() {
|
||||
test_reset_password_form();
|
||||
});
|
||||
|
||||
describe('test endpoints locks', function() {
|
||||
function should_post_and_reply_with(url, status_code) {
|
||||
return request.postAsync(url).then(function(response) {
|
||||
assert.equal(response.statusCode, status_code);
|
||||
return Promise.resolve();
|
||||
})
|
||||
}
|
||||
|
||||
function should_get_and_reply_with(url, status_code) {
|
||||
return request.getAsync(url).then(function(response) {
|
||||
assert.equal(response.statusCode, status_code);
|
||||
return Promise.resolve();
|
||||
})
|
||||
}
|
||||
|
||||
function should_post_and_reply_with_403(url) {
|
||||
return should_post_and_reply_with(url, 403);
|
||||
}
|
||||
function should_get_and_reply_with_403(url) {
|
||||
return should_get_and_reply_with(url, 403);
|
||||
}
|
||||
|
||||
function should_post_and_reply_with_401(url) {
|
||||
return should_post_and_reply_with(url, 401);
|
||||
}
|
||||
function should_get_and_reply_with_401(url) {
|
||||
return should_get_and_reply_with(url, 401);
|
||||
}
|
||||
|
||||
function should_get_and_post_reply_with_403(url) {
|
||||
var p1 = should_post_and_reply_with_403(url);
|
||||
var p2 = should_get_and_reply_with_403(url);
|
||||
return Promise.all([p1, p2]);
|
||||
}
|
||||
|
||||
it('should block /new-password', function() {
|
||||
return should_post_and_reply_with_403(BASE_URL + '/new-password')
|
||||
});
|
||||
|
||||
it('should block /u2f-register', function() {
|
||||
return should_get_and_post_reply_with_403(BASE_URL + '/u2f-register');
|
||||
});
|
||||
|
||||
it('should block /reset-password', function() {
|
||||
return should_get_and_post_reply_with_403(BASE_URL + '/reset-password');
|
||||
});
|
||||
|
||||
it('should block /2ndfactor/u2f/register_request', function() {
|
||||
return should_get_and_reply_with_403(BASE_URL + '/2ndfactor/u2f/register_request');
|
||||
});
|
||||
|
||||
it('should block /2ndfactor/u2f/register', function() {
|
||||
return should_post_and_reply_with_403(BASE_URL + '/2ndfactor/u2f/register');
|
||||
});
|
||||
|
||||
it('should block /2ndfactor/u2f/sign_request', function() {
|
||||
return should_get_and_reply_with_403(BASE_URL + '/2ndfactor/u2f/sign_request');
|
||||
});
|
||||
|
||||
it('should block /2ndfactor/u2f/sign', function() {
|
||||
return should_post_and_reply_with_403(BASE_URL + '/2ndfactor/u2f/sign');
|
||||
});
|
||||
});
|
||||
|
||||
describe('test authentication and verification', function() {
|
||||
test_authentication();
|
||||
test_reset_password();
|
||||
test_regulation();
|
||||
});
|
||||
|
||||
function test_reset_password_form() {
|
||||
it('should serve the reset password form page', function(done) {
|
||||
request.getAsync(BASE_URL + '/reset-password-form')
|
||||
.then(function(response) {
|
||||
assert.equal(response.statusCode, 200);
|
||||
done();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function test_login() {
|
||||
it('should serve the login page', function(done) {
|
||||
request.getAsync(BASE_URL + '/login')
|
||||
.then(function(response) {
|
||||
assert.equal(response.statusCode, 200);
|
||||
done();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function test_logout() {
|
||||
it('should logout and redirect to /', function(done) {
|
||||
request.getAsync(BASE_URL + '/logout')
|
||||
.then(function(response) {
|
||||
assert.equal(response.req.path, '/');
|
||||
done();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function test_authentication() {
|
||||
it('should return status code 401 when user is not authenticated', function() {
|
||||
return request.getAsync({ url: BASE_URL + '/verify' })
|
||||
.then(function(response) {
|
||||
assert.equal(response.statusCode, 401);
|
||||
return Promise.resolve();
|
||||
});
|
||||
});
|
||||
|
||||
it('should return status code 204 when user is authenticated using totp', function() {
|
||||
var j = request.jar();
|
||||
return requests.login(j)
|
||||
.then(function(res) {
|
||||
assert.equal(res.statusCode, 200, 'get login page failed');
|
||||
return requests.first_factor(j);
|
||||
})
|
||||
.then(function(res) {
|
||||
assert.equal(res.statusCode, 204, 'first factor failed');
|
||||
return requests.register_totp(j, transporter);
|
||||
})
|
||||
.then(function(secret) {
|
||||
var sec = JSON.parse(secret);
|
||||
var real_token = speakeasy.totp({
|
||||
secret: sec.base32,
|
||||
encoding: 'base32'
|
||||
});
|
||||
return requests.totp(j, real_token);
|
||||
})
|
||||
.then(function(res) {
|
||||
assert.equal(res.statusCode, 204, 'second factor failed');
|
||||
return requests.verify(j);
|
||||
})
|
||||
.then(function(res) {
|
||||
assert.equal(res.statusCode, 204, 'verify failed');
|
||||
return Promise.resolve();
|
||||
});
|
||||
});
|
||||
|
||||
it('should keep session variables when login page is reloaded', function() {
|
||||
var real_token = speakeasy.totp({
|
||||
secret: 'totp_secret',
|
||||
encoding: 'base32'
|
||||
});
|
||||
var j = request.jar();
|
||||
return requests.login(j)
|
||||
.then(function(res) {
|
||||
assert.equal(res.statusCode, 200, 'get login page failed');
|
||||
return requests.first_factor(j);
|
||||
})
|
||||
.then(function(res) {
|
||||
assert.equal(res.statusCode, 204, 'first factor failed');
|
||||
return requests.totp(j, real_token);
|
||||
})
|
||||
.then(function(res) {
|
||||
assert.equal(res.statusCode, 204, 'second factor failed');
|
||||
return requests.login(j);
|
||||
})
|
||||
.then(function(res) {
|
||||
assert.equal(res.statusCode, 200, 'login page loading failed');
|
||||
return requests.verify(j);
|
||||
})
|
||||
.then(function(res) {
|
||||
assert.equal(res.statusCode, 204, 'verify failed');
|
||||
return Promise.resolve();
|
||||
})
|
||||
.catch(function(err) {
|
||||
console.error(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return status code 204 when user is authenticated using u2f', function() {
|
||||
var sign_request = {};
|
||||
var sign_status = {};
|
||||
var registration_request = {};
|
||||
var registration_status = {};
|
||||
u2f.startRegistration.returns(Promise.resolve(sign_request));
|
||||
u2f.finishRegistration.returns(Promise.resolve(sign_status));
|
||||
u2f.startAuthentication.returns(Promise.resolve(registration_request));
|
||||
u2f.finishAuthentication.returns(Promise.resolve(registration_status));
|
||||
|
||||
var j = request.jar();
|
||||
return requests.login(j)
|
||||
.then(function(res) {
|
||||
assert.equal(res.statusCode, 200, 'get login page failed');
|
||||
return requests.first_factor(j);
|
||||
})
|
||||
.then(function(res) {
|
||||
assert.equal(res.statusCode, 204, 'first factor failed');
|
||||
return requests.u2f_registration(j, transporter);
|
||||
})
|
||||
.then(function(res) {
|
||||
assert.equal(res.statusCode, 204, 'second factor, finish register failed');
|
||||
return requests.u2f_authentication(j);
|
||||
})
|
||||
.then(function(res) {
|
||||
assert.equal(res.statusCode, 204, 'second factor, finish sign failed');
|
||||
return requests.verify(j);
|
||||
})
|
||||
.then(function(res) {
|
||||
assert.equal(res.statusCode, 204, 'verify failed');
|
||||
return Promise.resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function test_reset_password() {
|
||||
it('should reset the password', function() {
|
||||
var j = request.jar();
|
||||
return requests.login(j)
|
||||
.then(function(res) {
|
||||
assert.equal(res.statusCode, 200, 'get login page failed');
|
||||
return requests.first_factor(j);
|
||||
})
|
||||
.then(function(res) {
|
||||
assert.equal(res.statusCode, 204, 'first factor failed');
|
||||
return requests.reset_password(j, transporter, 'user', 'new-password');
|
||||
})
|
||||
.then(function(res) {
|
||||
assert.equal(res.statusCode, 204, 'second factor, finish register failed');
|
||||
return Promise.resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function test_regulation() {
|
||||
it('should regulate authentication', function() {
|
||||
var j = request.jar();
|
||||
MockDate.set('1/2/2017 00:00:00');
|
||||
return requests.login(j)
|
||||
.then(function(res) {
|
||||
assert.equal(res.statusCode, 200, 'get login page failed');
|
||||
return requests.failing_first_factor(j);
|
||||
})
|
||||
.then(function(res) {
|
||||
assert.equal(res.statusCode, 401, 'first factor failed');
|
||||
return requests.failing_first_factor(j);
|
||||
})
|
||||
.then(function(res) {
|
||||
assert.equal(res.statusCode, 401, 'first factor failed');
|
||||
return requests.failing_first_factor(j);
|
||||
})
|
||||
.then(function(res) {
|
||||
assert.equal(res.statusCode, 401, 'first factor failed');
|
||||
return requests.failing_first_factor(j);
|
||||
})
|
||||
.then(function(res) {
|
||||
assert.equal(res.statusCode, 403, 'first factor failed');
|
||||
MockDate.set('1/2/2017 00:30:00');
|
||||
return requests.failing_first_factor(j);
|
||||
})
|
||||
.then(function(res) {
|
||||
assert.equal(res.statusCode, 401, 'first factor failed');
|
||||
return Promise.resolve();
|
||||
})
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
|
||||
var sinon = require('sinon');
|
||||
var server = require('../../src/lib/server');
|
||||
var assert = require('assert');
|
||||
|
||||
describe('test server configuration', function() {
|
||||
var deps;
|
||||
var config;
|
||||
|
||||
before(function() {
|
||||
config = {};
|
||||
config.notifier = {
|
||||
gmail: {
|
||||
user: 'user@example.com',
|
||||
pass: 'password'
|
||||
}
|
||||
}
|
||||
|
||||
transporter = {};
|
||||
transporter.sendMail = sinon.stub().yields();
|
||||
|
||||
var nodemailer = {};
|
||||
nodemailer.createTransport = sinon.spy(function() {
|
||||
return transporter;
|
||||
});
|
||||
|
||||
deps = {};
|
||||
deps.nedb = require('nedb');
|
||||
deps.winston = sinon.spy();
|
||||
deps.nodemailer = nodemailer;
|
||||
deps.ldapjs = {};
|
||||
deps.ldapjs.createClient = sinon.spy(function() {
|
||||
return { on: sinon.spy() };
|
||||
});
|
||||
deps.session = sinon.spy(function() {
|
||||
return function(req, res, next) { next(); };
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should set cookie scope to domain set in the config', function() {
|
||||
config.session = {};
|
||||
config.session.domain = 'example.com';
|
||||
config.session.secret = 'secret';
|
||||
config.ldap = {};
|
||||
config.ldap.url = 'http://ldap';
|
||||
server.run(config, deps);
|
||||
|
||||
assert(deps.session.calledOnce);
|
||||
assert.equal(deps.session.getCall(0).args[0].cookie.domain, 'example.com');
|
||||
});
|
||||
});
|
|
@ -1,32 +0,0 @@
|
|||
|
||||
var totp = require('../../src/lib/totp');
|
||||
var sinon = require('sinon');
|
||||
var Promise = require('bluebird');
|
||||
|
||||
describe('test TOTP validation', function() {
|
||||
it('should validate the TOTP token', function() {
|
||||
var totp_secret = 'NBD2ZV64R9UV1O7K';
|
||||
var token = 'token';
|
||||
var totp_mock = sinon.mock();
|
||||
totp_mock.returns('token');
|
||||
var speakeasy_mock = {
|
||||
totp: totp_mock
|
||||
}
|
||||
return totp.validate(speakeasy_mock, token, totp_secret);
|
||||
});
|
||||
|
||||
it('should not validate a wrong TOTP token', function() {
|
||||
var totp_secret = 'NBD2ZV64R9UV1O7K';
|
||||
var token = 'wrong token';
|
||||
var totp_mock = sinon.mock();
|
||||
totp_mock.returns('token');
|
||||
var speakeasy_mock = {
|
||||
totp: totp_mock
|
||||
}
|
||||
return totp.validate(speakeasy_mock, token, totp_secret)
|
||||
.catch(function() {
|
||||
return Promise.resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,212 +0,0 @@
|
|||
|
||||
var UserDataStore = require('../../src/lib/user_data_store');
|
||||
var DataStore = require('nedb');
|
||||
var assert = require('assert');
|
||||
var Promise = require('bluebird');
|
||||
var sinon = require('sinon');
|
||||
var MockDate = require('mockdate');
|
||||
|
||||
describe('test user data store', function() {
|
||||
describe('test u2f meta', test_u2f_meta);
|
||||
describe('test u2f registration token', test_u2f_registration_token);
|
||||
});
|
||||
|
||||
function test_u2f_meta() {
|
||||
it('should save a u2f meta', function() {
|
||||
var options = {};
|
||||
options.inMemoryOnly = true;
|
||||
|
||||
var data_store = new UserDataStore(DataStore, options);
|
||||
|
||||
var userid = 'user';
|
||||
var app_id = 'https://localhost';
|
||||
var meta = {};
|
||||
meta.publicKey = 'pbk';
|
||||
|
||||
return data_store.set_u2f_meta(userid, app_id, meta)
|
||||
.then(function(numUpdated) {
|
||||
assert.equal(1, numUpdated);
|
||||
return Promise.resolve();
|
||||
});
|
||||
});
|
||||
|
||||
it('should retrieve no u2f meta', function() {
|
||||
var options = {};
|
||||
options.inMemoryOnly = true;
|
||||
|
||||
var data_store = new UserDataStore(DataStore, options);
|
||||
|
||||
var userid = 'user';
|
||||
var app_id = 'https://localhost';
|
||||
var meta = {};
|
||||
meta.publicKey = 'pbk';
|
||||
|
||||
return data_store.get_u2f_meta(userid, app_id)
|
||||
.then(function(doc) {
|
||||
assert.equal(undefined, doc);
|
||||
return Promise.resolve();
|
||||
});
|
||||
});
|
||||
|
||||
it('should insert and retrieve a u2f meta', function() {
|
||||
var options = {};
|
||||
options.inMemoryOnly = true;
|
||||
|
||||
var data_store = new UserDataStore(DataStore, options);
|
||||
|
||||
var userid = 'user';
|
||||
var app_id = 'https://localhost';
|
||||
var meta = {};
|
||||
meta.publicKey = 'pbk';
|
||||
|
||||
return data_store.set_u2f_meta(userid, app_id, meta)
|
||||
.then(function(numUpdated, data) {
|
||||
assert.equal(1, numUpdated);
|
||||
return data_store.get_u2f_meta(userid, app_id)
|
||||
})
|
||||
.then(function(doc) {
|
||||
assert.deepEqual(meta, doc.meta);
|
||||
assert.deepEqual(userid, doc.userid);
|
||||
assert.deepEqual(app_id, doc.appid);
|
||||
assert('_id' in doc);
|
||||
return Promise.resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function test_u2f_registration_token() {
|
||||
it('should save u2f registration token', function() {
|
||||
var options = {};
|
||||
options.inMemoryOnly = true;
|
||||
|
||||
var data_store = new UserDataStore(DataStore, options);
|
||||
|
||||
var userid = 'user';
|
||||
var token = 'token';
|
||||
var max_age = 60;
|
||||
var content = 'abc';
|
||||
|
||||
return data_store.issue_identity_check_token(userid, token, content, max_age)
|
||||
.then(function(document) {
|
||||
assert.equal(document.userid, userid);
|
||||
assert.equal(document.token, token);
|
||||
assert.deepEqual(document.content, { userid: 'user', data: content });
|
||||
assert('max_date' in document);
|
||||
assert('_id' in document);
|
||||
return Promise.resolve();
|
||||
})
|
||||
.catch(function(err) {
|
||||
console.error(err);
|
||||
return Promise.reject(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should save u2f registration token and consume it', function(done) {
|
||||
var options = {};
|
||||
options.inMemoryOnly = true;
|
||||
|
||||
var data_store = new UserDataStore(DataStore, options);
|
||||
|
||||
var userid = 'user';
|
||||
var token = 'token';
|
||||
var max_age = 50;
|
||||
|
||||
data_store.issue_identity_check_token(userid, token, {}, max_age)
|
||||
.then(function(document) {
|
||||
return data_store.consume_identity_check_token(token);
|
||||
})
|
||||
.then(function() {
|
||||
done();
|
||||
})
|
||||
.catch(function(err) {
|
||||
console.error(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not be able to consume registration token twice', function(done) {
|
||||
var options = {};
|
||||
options.inMemoryOnly = true;
|
||||
|
||||
var data_store = new UserDataStore(DataStore, options);
|
||||
|
||||
var userid = 'user';
|
||||
var token = 'token';
|
||||
var max_age = 50;
|
||||
|
||||
data_store.issue_identity_check_token(userid, token, {}, max_age)
|
||||
.then(function(document) {
|
||||
return data_store.consume_identity_check_token(token);
|
||||
})
|
||||
.then(function(document) {
|
||||
return data_store.consume_identity_check_token(token);
|
||||
})
|
||||
.catch(function(err) {
|
||||
console.error(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail when token does not exist', function() {
|
||||
var options = {};
|
||||
options.inMemoryOnly = true;
|
||||
|
||||
var data_store = new UserDataStore(DataStore, options);
|
||||
|
||||
var token = 'token';
|
||||
|
||||
return data_store.consume_identity_check_token(token)
|
||||
.then(function(document) {
|
||||
return Promise.reject();
|
||||
})
|
||||
.catch(function(err) {
|
||||
return Promise.resolve(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail when token expired', function(done) {
|
||||
var options = {};
|
||||
options.inMemoryOnly = true;
|
||||
|
||||
var data_store = new UserDataStore(DataStore, options);
|
||||
|
||||
var userid = 'user';
|
||||
var token = 'token';
|
||||
var max_age = 60;
|
||||
MockDate.set('1/1/2000');
|
||||
|
||||
data_store.issue_identity_check_token(userid, token, {}, max_age)
|
||||
.then(function() {
|
||||
MockDate.set('1/2/2000');
|
||||
return data_store.consume_identity_check_token(token);
|
||||
})
|
||||
.catch(function(err) {
|
||||
MockDate.reset();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should save the userid and some data with the token', function(done) {
|
||||
var options = {};
|
||||
options.inMemoryOnly = true;
|
||||
|
||||
var data_store = new UserDataStore(DataStore, options);
|
||||
|
||||
var userid = 'user';
|
||||
var token = 'token';
|
||||
var max_age = 60;
|
||||
MockDate.set('1/1/2000');
|
||||
var data = 'abc';
|
||||
|
||||
data_store.issue_identity_check_token(userid, token, data, max_age)
|
||||
.then(function() {
|
||||
return data_store.consume_identity_check_token(token);
|
||||
})
|
||||
.then(function(content) {
|
||||
var expected_content = {};
|
||||
expected_content.userid = 'user';
|
||||
expected_content.data = 'abc';
|
||||
assert.deepEqual(content, expected_content);
|
||||
done();
|
||||
})
|
||||
});
|
||||
}
|
32
test/unitary/totp.test.ts
Normal file
32
test/unitary/totp.test.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
|
||||
const totp = require("../../src/lib/totp");
|
||||
const sinon = require("sinon");
|
||||
import Promise = require("bluebird");
|
||||
|
||||
describe("test TOTP validation", function() {
|
||||
it("should validate the TOTP token", function() {
|
||||
const totp_secret = "NBD2ZV64R9UV1O7K";
|
||||
const token = "token";
|
||||
const totp_mock = sinon.mock();
|
||||
totp_mock.returns("token");
|
||||
const speakeasy_mock = {
|
||||
totp: totp_mock
|
||||
};
|
||||
return totp.validate(speakeasy_mock, token, totp_secret);
|
||||
});
|
||||
|
||||
it("should not validate a wrong TOTP token", function() {
|
||||
const totp_secret = "NBD2ZV64R9UV1O7K";
|
||||
const token = "wrong token";
|
||||
const totp_mock = sinon.mock();
|
||||
totp_mock.returns("token");
|
||||
const speakeasy_mock = {
|
||||
totp: totp_mock
|
||||
};
|
||||
return totp.validate(speakeasy_mock, token, totp_secret)
|
||||
.catch(function() {
|
||||
return Promise.resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
70
test/unitary/user_data_store/authentication_audit.test.ts
Normal file
70
test/unitary/user_data_store/authentication_audit.test.ts
Normal file
|
@ -0,0 +1,70 @@
|
|||
|
||||
import * as assert from "assert";
|
||||
import * as Promise from "bluebird";
|
||||
import * as sinon from "sinon";
|
||||
import * as MockDate from "mockdate";
|
||||
import UserDataStore from "../../../src/lib/UserDataStore";
|
||||
|
||||
describe("test user data store", function() {
|
||||
describe("test authentication traces", test_authentication_traces);
|
||||
});
|
||||
|
||||
function test_authentication_traces() {
|
||||
it("should save an authentication trace in db", function() {
|
||||
const options = {
|
||||
inMemoryOnly: true
|
||||
};
|
||||
|
||||
const data_store = new UserDataStore(options);
|
||||
const userid = "user";
|
||||
const type = "1stfactor";
|
||||
const is_success = false;
|
||||
return data_store.save_authentication_trace(userid, type, is_success)
|
||||
.then(function(doc) {
|
||||
assert("_id" in doc);
|
||||
assert.equal(doc.userid, "user");
|
||||
assert.equal(doc.is_success, false);
|
||||
assert.equal(doc.type, "1stfactor");
|
||||
return Promise.resolve();
|
||||
});
|
||||
});
|
||||
|
||||
it("should return 3 last authentication traces", function() {
|
||||
const options = {
|
||||
inMemoryOnly: true
|
||||
};
|
||||
|
||||
const data_store = new UserDataStore(options);
|
||||
const userid = "user";
|
||||
const type = "1stfactor";
|
||||
const is_success = false;
|
||||
MockDate.set("2/1/2000");
|
||||
return data_store.save_authentication_trace(userid, type, false)
|
||||
.then(function(doc) {
|
||||
MockDate.set("1/2/2000");
|
||||
return data_store.save_authentication_trace(userid, type, true);
|
||||
})
|
||||
.then(function(doc) {
|
||||
MockDate.set("1/7/2000");
|
||||
return data_store.save_authentication_trace(userid, type, false);
|
||||
})
|
||||
.then(function(doc) {
|
||||
MockDate.set("1/2/2000");
|
||||
return data_store.save_authentication_trace(userid, type, false);
|
||||
})
|
||||
.then(function(doc) {
|
||||
MockDate.set("1/5/2000");
|
||||
return data_store.save_authentication_trace(userid, type, false);
|
||||
})
|
||||
.then(function(doc) {
|
||||
return data_store.get_last_authentication_traces(userid, type, false, 3);
|
||||
})
|
||||
.then(function(docs) {
|
||||
assert.equal(docs.length, 3);
|
||||
assert.deepEqual(docs[0].date, new Date("2/1/2000"));
|
||||
assert.deepEqual(docs[1].date, new Date("1/7/2000"));
|
||||
assert.deepEqual(docs[2].date, new Date("1/5/2000"));
|
||||
return Promise.resolve();
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,69 +0,0 @@
|
|||
|
||||
var assert = require('assert');
|
||||
var Promise = require('bluebird');
|
||||
var sinon = require('sinon');
|
||||
var MockDate = require('mockdate');
|
||||
var UserDataStore = require('../../../src/lib/user_data_store');
|
||||
var DataStore = require('nedb');
|
||||
|
||||
describe('test user data store', function() {
|
||||
describe('test authentication traces', test_authentication_traces);
|
||||
});
|
||||
|
||||
function test_authentication_traces() {
|
||||
it('should save an authentication trace in db', function() {
|
||||
var options = {};
|
||||
options.inMemoryOnly = true;
|
||||
|
||||
var data_store = new UserDataStore(DataStore, options);
|
||||
var userid = 'user';
|
||||
var type = '1stfactor';
|
||||
var is_success = false;
|
||||
return data_store.save_authentication_trace(userid, type, is_success)
|
||||
.then(function(doc) {
|
||||
assert('_id' in doc);
|
||||
assert.equal(doc.userid, 'user');
|
||||
assert.equal(doc.is_success, false);
|
||||
assert.equal(doc.type, '1stfactor');
|
||||
return Promise.resolve();
|
||||
});
|
||||
});
|
||||
|
||||
it('should return 3 last authentication traces', function() {
|
||||
var options = {};
|
||||
options.inMemoryOnly = true;
|
||||
|
||||
var data_store = new UserDataStore(DataStore, options);
|
||||
var userid = 'user';
|
||||
var type = '1stfactor';
|
||||
var is_success = false;
|
||||
MockDate.set('2/1/2000');
|
||||
return data_store.save_authentication_trace(userid, type, false)
|
||||
.then(function(doc) {
|
||||
MockDate.set('1/2/2000');
|
||||
return data_store.save_authentication_trace(userid, type, true);
|
||||
})
|
||||
.then(function(doc) {
|
||||
MockDate.set('1/7/2000');
|
||||
return data_store.save_authentication_trace(userid, type, false);
|
||||
})
|
||||
.then(function(doc) {
|
||||
MockDate.set('1/2/2000');
|
||||
return data_store.save_authentication_trace(userid, type, false);
|
||||
})
|
||||
.then(function(doc) {
|
||||
MockDate.set('1/5/2000');
|
||||
return data_store.save_authentication_trace(userid, type, false);
|
||||
})
|
||||
.then(function(doc) {
|
||||
return data_store.get_last_authentication_traces(userid, type, false, 3);
|
||||
})
|
||||
.then(function(docs) {
|
||||
assert.equal(docs.length, 3);
|
||||
assert.deepEqual(docs[0].date, new Date('2/1/2000'));
|
||||
assert.deepEqual(docs[1].date, new Date('1/7/2000'));
|
||||
assert.deepEqual(docs[2].date, new Date('1/5/2000'));
|
||||
return Promise.resolve();
|
||||
})
|
||||
});
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
|
||||
var assert = require('assert');
|
||||
var Promise = require('bluebird');
|
||||
var sinon = require('sinon');
|
||||
var MockDate = require('mockdate');
|
||||
var UserDataStore = require('../../../src/lib/user_data_store');
|
||||
var DataStore = require('nedb');
|
||||
|
||||
describe('test user data store', function() {
|
||||
describe('test totp secrets store', test_totp_secrets);
|
||||
});
|
||||
|
||||
function test_totp_secrets() {
|
||||
it('should save and reload a totp secret', function() {
|
||||
var options = {};
|
||||
options.inMemoryOnly = true;
|
||||
|
||||
var data_store = new UserDataStore(DataStore, options);
|
||||
var userid = 'user';
|
||||
var secret = {};
|
||||
secret.ascii = 'abc';
|
||||
secret.base32 = 'ABCDKZLEFZGREJK';
|
||||
|
||||
return data_store.set_totp_secret(userid, secret)
|
||||
.then(function() {
|
||||
return data_store.get_totp_secret(userid);
|
||||
})
|
||||
.then(function(doc) {
|
||||
assert('_id' in doc);
|
||||
assert.equal(doc.userid, 'user');
|
||||
assert.equal(doc.secret.ascii, 'abc');
|
||||
assert.equal(doc.secret.base32, 'ABCDKZLEFZGREJK');
|
||||
return Promise.resolve();
|
||||
});
|
||||
});
|
||||
|
||||
it('should only remember last secret', function() {
|
||||
var options = {};
|
||||
options.inMemoryOnly = true;
|
||||
|
||||
var data_store = new UserDataStore(DataStore, options);
|
||||
var userid = 'user';
|
||||
var secret1 = {};
|
||||
secret1.ascii = 'abc';
|
||||
secret1.base32 = 'ABCDKZLEFZGREJK';
|
||||
var secret2 = {};
|
||||
secret2.ascii = 'def';
|
||||
secret2.base32 = 'XYZABC';
|
||||
|
||||
return data_store.set_totp_secret(userid, secret1)
|
||||
.then(function() {
|
||||
return data_store.set_totp_secret(userid, secret2)
|
||||
})
|
||||
.then(function() {
|
||||
return data_store.get_totp_secret(userid);
|
||||
})
|
||||
.then(function(doc) {
|
||||
assert('_id' in doc);
|
||||
assert.equal(doc.userid, 'user');
|
||||
assert.equal(doc.secret.ascii, 'def');
|
||||
assert.equal(doc.secret.base32, 'XYZABC');
|
||||
return Promise.resolve();
|
||||
});
|
||||
});
|
||||
}
|
72
test/unitary/user_data_store/totp_secret.test.ts
Normal file
72
test/unitary/user_data_store/totp_secret.test.ts
Normal file
|
@ -0,0 +1,72 @@
|
|||
|
||||
import * as assert from "assert";
|
||||
import * as Promise from "bluebird";
|
||||
import * as sinon from "sinon";
|
||||
import * as MockDate from "mockdate";
|
||||
import UserDataStore from "../../../src/lib/UserDataStore";
|
||||
|
||||
describe("test user data store", function() {
|
||||
describe("test totp secrets store", test_totp_secrets);
|
||||
});
|
||||
|
||||
function test_totp_secrets() {
|
||||
it("should save and reload a totp secret", function() {
|
||||
const options = {
|
||||
inMemoryOnly: true
|
||||
};
|
||||
|
||||
const data_store = new UserDataStore(options);
|
||||
const userid = "user";
|
||||
const secret = {
|
||||
ascii: "abc",
|
||||
base32: "ABCDKZLEFZGREJK",
|
||||
otpauth_url: "totp://test"
|
||||
};
|
||||
|
||||
return data_store.set_totp_secret(userid, secret)
|
||||
.then(function() {
|
||||
return data_store.get_totp_secret(userid);
|
||||
})
|
||||
.then(function(doc) {
|
||||
assert("_id" in doc);
|
||||
assert.equal(doc.userid, "user");
|
||||
assert.equal(doc.secret.ascii, "abc");
|
||||
assert.equal(doc.secret.base32, "ABCDKZLEFZGREJK");
|
||||
return Promise.resolve();
|
||||
});
|
||||
});
|
||||
|
||||
it("should only remember last secret", function() {
|
||||
const options = {
|
||||
inMemoryOnly: true
|
||||
};
|
||||
|
||||
const data_store = new UserDataStore(options);
|
||||
const userid = "user";
|
||||
const secret1 = {
|
||||
ascii: "abc",
|
||||
base32: "ABCDKZLEFZGREJK",
|
||||
otpauth_url: "totp://test"
|
||||
};
|
||||
const secret2 = {
|
||||
ascii: "def",
|
||||
base32: "XYZABC",
|
||||
otpauth_url: "totp://test"
|
||||
};
|
||||
|
||||
return data_store.set_totp_secret(userid, secret1)
|
||||
.then(function() {
|
||||
return data_store.set_totp_secret(userid, secret2);
|
||||
})
|
||||
.then(function() {
|
||||
return data_store.get_totp_secret(userid);
|
||||
})
|
||||
.then(function(doc) {
|
||||
assert("_id" in doc);
|
||||
assert.equal(doc.userid, "user");
|
||||
assert.equal(doc.secret.ascii, "def");
|
||||
assert.equal(doc.secret.base32, "XYZABC");
|
||||
return Promise.resolve();
|
||||
});
|
||||
});
|
||||
}
|
|
@ -10,12 +10,13 @@
|
|||
"allowJs": true,
|
||||
"paths": {
|
||||
"*": [
|
||||
"node_modules/*",
|
||||
"node_modules/@types/*",
|
||||
"src/types/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
"src/**/*",
|
||||
"test/**/*"
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user