Move notifiers to typescript

pull/33/head
Clement Michaud 2017-05-20 09:49:05 +02:00
parent b0c6c61df5
commit 57278a7306
27 changed files with 411 additions and 273 deletions

View File

@ -48,6 +48,7 @@
"@types/assert": "0.0.31",
"@types/bluebird": "^3.5.3",
"@types/body-parser": "^1.16.3",
"@types/ejs": "^2.3.33",
"@types/express": "^4.0.35",
"@types/express-session": "0.0.32",
"@types/ldapjs": "^1.0.0",
@ -56,6 +57,7 @@
"@types/nedb": "^1.8.3",
"@types/nodemailer": "^1.3.32",
"@types/object-path": "^0.9.28",
"@types/proxyquire": "^1.3.27",
"@types/request": "0.0.43",
"@types/sinon": "^2.2.1",
"@types/speakeasy": "^2.0.1",
@ -67,6 +69,7 @@
"grunt-run": "^0.6.0",
"mocha": "^3.2.0",
"mockdate": "^2.0.1",
"proxyquire": "^1.8.0",
"request": "^2.79.0",
"should": "^11.1.1",
"sinon": "^1.17.6",

View File

@ -30,14 +30,18 @@ interface SessionCookieConfiguration {
domain?: string;
}
interface GMailNotifier {
user: string;
pass: string;
export interface GmailNotifierConfiguration {
username: string;
password: string;
}
type NotifierType = string;
export interface NotifiersConfiguration {
gmail: GMailNotifier;
export interface FileSystemNotifierConfiguration {
filename: string;
}
export interface NotifierConfiguration {
gmail?: GmailNotifierConfiguration;
filesystem?: FileSystemNotifierConfiguration;
}
export interface UserConfiguration {
@ -46,7 +50,7 @@ export interface UserConfiguration {
ldap: LdapConfiguration;
session: SessionCookieConfiguration;
store_directory?: string;
notifier: NotifiersConfiguration;
notifier: NotifierConfiguration;
access_control?: ACLConfiguration;
}
@ -57,6 +61,6 @@ export interface AppConfiguration {
session: SessionCookieConfiguration;
store_in_memory?: boolean;
store_directory?: string;
notifier: NotifiersConfiguration;
notifier: NotifierConfiguration;
access_control?: ACLConfiguration;
}

View File

@ -0,0 +1,42 @@
import * as ObjectPath from "object-path";
import { AppConfiguration, UserConfiguration, NotifierConfiguration, ACLConfiguration, LdapConfiguration } from "./Configuration";
function get_optional<T>(config: object, path: string, default_value: T): T {
let entry = default_value;
if (ObjectPath.has(config, path)) {
entry = ObjectPath.get<object, T>(config, path);
}
return entry;
}
function ensure_key_existence(config: object, path: string): void {
if (!ObjectPath.has(config, path)) {
throw new Error(`Configuration error: key '${path}' is missing in configuration file`);
}
}
export default class ConfigurationAdapter {
static adapt(yaml_config: UserConfiguration): AppConfiguration {
ensure_key_existence(yaml_config, "ldap");
ensure_key_existence(yaml_config, "session.secret");
const port = ObjectPath.get(yaml_config, "port", 8080);
return {
port: port,
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, NotifierConfiguration>(yaml_config, "notifier"),
access_control: ObjectPath.get<object, ACLConfiguration>(yaml_config, "access_control")
};
}
}

View File

@ -0,0 +1,22 @@
import * as winston from "winston";
import nodemailer = require("nodemailer");
export interface Nodemailer {
createTransport: (options?: any, defaults?: Object) => nodemailer.Transporter;
}
export interface GlobalDependencies {
u2f: object;
nodemailer: Nodemailer;
ldapjs: object;
session: any;
winston: winston.Winston;
speakeasy: object;
nedb: any;
}
export type NodemailerDependencies = Nodemailer;
export interface NotifierDependencies {
nodemailer: Nodemailer;
}

View File

@ -1,11 +0,0 @@
import * as winston from "winston";
export interface GlobalDependencies {
u2f: object;
nodemailer: any;
ldapjs: object;
session: any;
winston: winston.Winston;
speakeasy: object;
nedb: any;
}

View File

@ -0,0 +1,6 @@
export interface Identity {
userid: string;
email: string;
}

View File

@ -1,16 +1,16 @@
import { UserConfiguration } from "./Configuration";
import { GlobalDependencies } from "./GlobalDependencies";
import { GlobalDependencies } from "./Dependencies";
import { AuthenticationRegulator } from "./AuthenticationRegulator";
import UserDataStore from "./UserDataStore";
import ConfigurationAdapter from "./ConfigurationAdapter";
import { NotifierFactory } from "./notifiers/NotifierFactory";
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");
@ -19,7 +19,7 @@ export default class Server {
private httpServer: http.Server;
start(yaml_configuration: UserConfiguration, deps: GlobalDependencies): Promise<void> {
const config = config_adapter(yaml_configuration);
const config = ConfigurationAdapter.adapt(yaml_configuration);
const view_directory = Path.resolve(__dirname, "../views");
const public_html_directory = Path.resolve(__dirname, "../public_html");
@ -54,7 +54,7 @@ export default class Server {
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 notifier = NotifierFactory.build(config.notifier, deps);
const ldap = new Ldap(deps, config.ldap);
const access_control = AccessControl(deps.winston, config.access_control);

View File

@ -1,40 +0,0 @@
import * as ObjectPath from "object-path";
import { AppConfiguration, UserConfiguration, NotifiersConfiguration, ACLConfiguration, LdapConfiguration } from "./Configuration";
function get_optional<T>(config: object, path: string, default_value: T): T {
let entry = default_value;
if (ObjectPath.has(config, path)) {
entry = ObjectPath.get<object, T>(config, path);
}
return entry;
}
function ensure_key_existence(config: object, path: string): void {
if (!ObjectPath.has(config, path)) {
throw new Error(`Configuration error: key '${path}' is missing in configuration file`);
}
}
export = function(yaml_config: UserConfiguration): AppConfiguration {
ensure_key_existence(yaml_config, "ldap");
ensure_key_existence(yaml_config, "session.secret");
const port = ObjectPath.get(yaml_config, "port", 8080);
return {
port: port,
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, NotifiersConfiguration>(yaml_config, "notifier"),
access_control: ObjectPath.get<object, ACLConfiguration>(yaml_config, "access_control")
};
};

View File

@ -1,24 +0,0 @@
module.exports = Notifier;
var GmailNotifier = require('./notifiers/gmail.js');
var FSNotifier = require('./notifiers/filesystem.js');
function notifier_factory(options, deps) {
if('gmail' in options) {
return new GmailNotifier(options.gmail, deps);
}
else if('filesystem' in options) {
return new FSNotifier(options.filesystem);
}
}
function Notifier(options, deps) {
this._notifier = notifier_factory(options, deps);
}
Notifier.prototype.notify = function(identity, subject, link) {
return this._notifier.notify(identity, subject, link);
}

View File

@ -0,0 +1,25 @@
import * as BluebirdPromise from "bluebird";
import * as util from "util";
import * as fs from "fs";
import { INotifier } from "./INotifier";
import { Identity } from "../Identity";
import { FileSystemNotifierConfiguration } from "../Configuration";
export class FileSystemNotifier extends INotifier {
private filename: string;
constructor(options: FileSystemNotifierConfiguration) {
super();
this.filename = options.filename;
}
notify(identity: Identity, subject: string, link: string): BluebirdPromise<void> {
const content = util.format("User: %s\nSubject: %s\nLink: %s", identity.userid,
subject, link);
const writeFilePromised = BluebirdPromise.promisify<void, string, string>(fs.writeFile);
return writeFilePromised(this.filename, content);
}
}

View File

@ -0,0 +1,44 @@
import * as Promise from "bluebird";
import * as fs from "fs";
import * as ejs from "ejs";
import nodemailer = require("nodemailer");
import { NodemailerDependencies } from "../Dependencies";
import { Identity } from "../Identity";
import { INotifier } from "../notifiers/INotifier";
import { GmailNotifierConfiguration } from "../Configuration";
const email_template = fs.readFileSync(__dirname + "/../../resources/email-template.ejs", "UTF-8");
export class GMailNotifier extends INotifier {
private transporter: any;
constructor(options: GmailNotifierConfiguration, deps: NodemailerDependencies) {
super();
const transporter = deps.createTransport({
service: "gmail",
auth: {
user: options.username,
pass: options.password
}
});
this.transporter = Promise.promisifyAll(transporter);
}
notify(identity: Identity, subject: string, link: string): Promise<void> {
const d = {
url: link,
button_title: "Continue",
title: subject
};
const mailOptions = {
from: "auth-server@open-intent.io",
to: identity.email,
subject: subject,
html: ejs.render(email_template, d)
};
return this.transporter.sendMailAsync(mailOptions);
}
}

View File

@ -0,0 +1,7 @@
import * as BluebirdPromise from "bluebird";
import { Identity } from "../Identity";
export abstract class INotifier {
abstract notify(identity: Identity, subject: string, link: string): BluebirdPromise<void>;
}

View File

@ -0,0 +1,22 @@
import { NotifierConfiguration } from "..//Configuration";
import { NotifierDependencies } from "../Dependencies";
import { INotifier } from "./INotifier";
import { GMailNotifier } from "./GMailNotifier";
import { FileSystemNotifier } from "./FileSystemNotifier";
export class NotifierFactory {
static build(options: NotifierConfiguration, deps: NotifierDependencies): INotifier {
if ("gmail" in options) {
return new GMailNotifier(options.gmail, deps.nodemailer);
}
else if ("filesystem" in options) {
return new FileSystemNotifier(options.filesystem);
}
}
}

View File

@ -1,16 +0,0 @@
module.exports = FSNotifier;
var Promise = require('bluebird');
var fs = Promise.promisifyAll(require('fs'));
var util = require('util');
function FSNotifier(options) {
this._filename = options.filename;
}
FSNotifier.prototype.notify = function(identity, subject, link) {
var content = util.format('User: %s\nSubject: %s\nLink: %s', identity.userid,
subject, link);
return fs.writeFileAsync(this._filename, content);
}

View File

@ -1,33 +0,0 @@
module.exports = GmailNotifier;
var Promise = require('bluebird');
var fs = require('fs');
var ejs = require('ejs');
var email_template = fs.readFileSync(__dirname + '/../../resources/email-template.ejs', 'UTF-8');
function GmailNotifier(options, deps) {
var transporter = deps.nodemailer.createTransport({
service: 'gmail',
auth: {
user: options.username,
pass: options.password
}
});
this.transporter = Promise.promisifyAll(transporter);
}
GmailNotifier.prototype.notify = function(identity, subject, link) {
var d = {};
d.url = link;
d.button_title = 'Continue';
d.title = subject;
var mailOptions = {};
mailOptions.from = 'auth-server@open-intent.io';
mailOptions.to = identity.email;
mailOptions.subject = subject;
mailOptions.html = ejs.render(email_template, d);
return this.transporter.sendMailAsync(mailOptions);
}

View File

@ -44,8 +44,8 @@ describe("test the server", function () {
store_in_memory: true,
notifier: {
gmail: {
user: "user@example.com",
pass: "password"
username: "user@example.com",
password: "password"
}
}
};

View File

@ -1,7 +1,6 @@
import * as Assert from "assert";
import { UserConfiguration } from "../../src/lib/Configuration";
import config_adapter = require("../../src/lib/config_adapter");
import ConfigurationAdapter from "../../src/lib/ConfigurationAdapter";
describe("test config adapter", function() {
function build_yaml_config(): UserConfiguration {
@ -22,8 +21,8 @@ describe("test config adapter", function() {
logs_level: "debug",
notifier: {
gmail: {
user: "user",
pass: "password"
username: "user",
password: "password"
}
}
};
@ -33,14 +32,14 @@ describe("test config adapter", function() {
it("should read the port from the yaml file", function() {
const yaml_config = build_yaml_config();
yaml_config.port = 7070;
const config = config_adapter(yaml_config);
const config = ConfigurationAdapter.adapt(yaml_config);
Assert.equal(config.port, 7070);
});
it("should default the port to 8080 if not provided", function() {
const yaml_config = build_yaml_config();
delete yaml_config.port;
const config = config_adapter(yaml_config);
const config = ConfigurationAdapter.adapt(yaml_config);
Assert.equal(config.port, 8080);
});
@ -55,7 +54,7 @@ describe("test config adapter", function() {
password: "pass"
};
const config = config_adapter(yaml_config);
const config = ConfigurationAdapter.adapt(yaml_config);
Assert.equal(config.ldap.url, "http://ldap");
Assert.equal(config.ldap.additional_user_dn, "ou=users");
@ -71,7 +70,7 @@ describe("test config adapter", function() {
secret: "secret",
expiration: 3600
};
const config = config_adapter(yaml_config);
const config = ConfigurationAdapter.adapt(yaml_config);
Assert.equal(config.session.domain, "example.com");
Assert.equal(config.session.secret, "secret");
Assert.equal(config.session.expiration, 3600);
@ -80,7 +79,7 @@ describe("test config adapter", function() {
it("should get the log level", function() {
const yaml_config = build_yaml_config();
yaml_config.logs_level = "debug";
const config = config_adapter(yaml_config);
const config = ConfigurationAdapter.adapt(yaml_config);
Assert.equal(config.logs_level, "debug");
});
@ -88,15 +87,15 @@ describe("test config adapter", function() {
const yaml_config = build_yaml_config();
yaml_config.notifier = {
gmail: {
user: "user",
pass: "pass"
username: "user",
password: "pass"
}
};
const config = config_adapter(yaml_config);
const config = ConfigurationAdapter.adapt(yaml_config);
Assert.deepEqual(config.notifier, {
gmail: {
user: "user",
pass: "pass"
username: "user",
password: "pass"
}
});
});
@ -108,7 +107,7 @@ describe("test config adapter", function() {
users: {},
groups: {}
};
const config = config_adapter(yaml_config);
const config = ConfigurationAdapter.adapt(yaml_config);
Assert.deepEqual(config.access_control, {
default: [],
users: {},

View File

@ -4,7 +4,7 @@ import * as request from "request";
import Server from "../../src/lib/Server";
import { UserConfiguration } from "../../src/lib/Configuration";
import { GlobalDependencies } from "../../src/lib/GlobalDependencies";
import { GlobalDependencies } from "../../src/lib/Dependencies";
import * as tmp from "tmp";
@ -77,8 +77,8 @@ describe("test data persistence", function () {
store_directory: tmpDir.name,
notifier: {
gmail: {
user: "user@example.com",
pass: "password"
username: "user@example.com",
password: "password"
}
}
};

View File

@ -0,0 +1,7 @@
import sinon = require("sinon");
import { Nodemailer } from "../../../src/lib/Dependencies";
export = {
createTransport: sinon.stub()
};

View File

@ -0,0 +1,42 @@
import * as sinon from "sinon";
import * as assert from "assert";
import { FileSystemNotifier } from "../../../src/lib/notifiers/FileSystemNotifier";
import * as tmp from "tmp";
import * as fs from "fs";
const NOTIFICATIONS_DIRECTORY = "notifications";
describe("test FS notifier", function() {
let tmpDir: tmp.SynchrounousResult;
before(function() {
tmpDir = tmp.dirSync({ unsafeCleanup: true });
});
after(function() {
tmpDir.removeCallback();
});
it("should write the notification in a file", function() {
const options = {
filename: tmpDir.name + "/" + NOTIFICATIONS_DIRECTORY
};
const sender = new FileSystemNotifier(options);
const subject = "subject";
const identity = {
userid: "user",
email: "user@example.com"
};
const url = "http://test.com";
return sender.notify(identity, subject, url)
.then(function() {
const content = fs.readFileSync(options.filename, "UTF-8");
assert(content.length > 0);
return Promise.resolve();
});
});
});

View File

@ -0,0 +1,39 @@
import * as sinon from "sinon";
import * as assert from "assert";
import nodemailerMock = require("../mocks/nodemailer");
import GMailNotifier = require("../../../src/lib/notifiers/GMailNotifier");
describe("test gmail notifier", function () {
it("should send an email", function () {
const transporter = {
sendMail: sinon.stub().yields()
};
nodemailerMock.createTransport.returns(transporter);
const options = {
username: "user_gmail",
password: "pass_gmail"
};
const sender = new GMailNotifier.GMailNotifier(options, nodemailerMock);
const subject = "subject";
const identity = {
userid: "user",
email: "user@example.com"
};
const url = "http://test.com";
return sender.notify(identity, subject, url)
.then(function () {
assert.equal(nodemailerMock.createTransport.getCall(0).args[0].auth.user, "user_gmail");
assert.equal(nodemailerMock.createTransport.getCall(0).args[0].auth.pass, "pass_gmail");
assert.equal(transporter.sendMail.getCall(0).args[0].to, "user@example.com");
assert.equal(transporter.sendMail.getCall(0).args[0].subject, "subject");
return Promise.resolve();
});
});
});

View File

@ -0,0 +1,39 @@
import * as sinon from "sinon";
import * as BluebirdPromise from "bluebird";
import * as assert from "assert";
import NodemailerMock = require("../mocks/nodemailer");
import { NotifierFactory } from "../../../src/lib/notifiers/NotifierFactory";
import { GMailNotifier } from "../../../src/lib/notifiers/GMailNotifier";
import { FileSystemNotifier } from "../../../src/lib/notifiers/FileSystemNotifier";
import { NotifierDependencies } from "../../../src/lib/Dependencies";
describe("test notifier", function() {
const deps: NotifierDependencies = {
nodemailer: NodemailerMock
};
it("should build a Gmail Notifier", function() {
const options = {
gmail: {
username: "abc",
password: "password"
}
};
assert(NotifierFactory.build(options, deps) instanceof GMailNotifier);
});
it("should build a FS Notifier", function() {
const options = {
filesystem: {
filename: "abc"
}
};
assert(NotifierFactory.build(options, deps) instanceof FileSystemNotifier);
});
});

View File

@ -1,37 +0,0 @@
var sinon = require('sinon');
var assert = require('assert');
var FSNotifier = require('../../../src/lib/notifiers/filesystem');
var tmp = require('tmp');
var fs = require('fs');
describe('test FS notifier', function() {
var tmpDir;
before(function() {
tmpDir = tmp.dirSync({ unsafeCleanup: true });
});
after(function() {
tmpDir.removeCallback();
});
it('should write the notification in a file', function() {
var options = {};
options.filename = tmpDir.name + '/notification';
var sender = new FSNotifier(options);
var subject = 'subject';
var identity = {};
identity.userid = 'user';
identity.email = 'user@example.com';
var url = 'http://test.com';
return sender.notify(identity, subject, url)
.then(function() {
var content = fs.readFileSync(options.filename, 'UTF-8');
assert(content.length > 0);
return Promise.resolve();
});
});
});

View File

@ -1,36 +0,0 @@
var sinon = require('sinon');
var assert = require('assert');
var GmailNotifier = require('../../../src/lib/notifiers/gmail');
describe('test gmail notifier', function() {
it('should send an email', function() {
var nodemailer = {};
var transporter = {};
nodemailer.createTransport = sinon.stub().returns(transporter);
transporter.sendMail = sinon.stub().yields();
var options = {};
options.username = 'user_gmail';
options.password = 'pass_gmail';
var deps = {};
deps.nodemailer = nodemailer;
var sender = new GmailNotifier(options, deps);
var subject = 'subject';
var identity = {};
identity.userid = 'user';
identity.email = 'user@example.com';
var url = 'http://test.com';
return sender.notify(identity, subject, url)
.then(function() {
assert.equal(nodemailer.createTransport.getCall(0).args[0].auth.user, 'user_gmail');
assert.equal(nodemailer.createTransport.getCall(0).args[0].auth.pass, 'pass_gmail');
assert.equal(transporter.sendMail.getCall(0).args[0].to, 'user@example.com');
assert.equal(transporter.sendMail.getCall(0).args[0].subject, 'subject');
return Promise.resolve();
});
});
});

View File

@ -1,35 +0,0 @@
var sinon = require('sinon');
var Promise = require('bluebird');
var assert = require('assert');
var Notifier = require('../../../src/lib/notifier');
var GmailNotifier = require('../../../src/lib/notifiers/gmail');
var FSNotifier = require('../../../src/lib/notifiers/filesystem');
describe('test notifier', function() {
it('should build a Gmail Notifier', function() {
var deps = {};
deps.nodemailer = {};
deps.nodemailer.createTransport = sinon.stub().returns({});
var options = {};
options.gmail = {};
options.gmail.user = 'abc';
options.gmail.pass = 'abcd';
var notifier = new Notifier(options, deps);
assert(notifier._notifier instanceof GmailNotifier);
});
it('should build a FS Notifier', function() {
var deps = {};
var options = {};
options.filesystem = {};
options.filesystem.filename = 'abc';
var notifier = new Notifier(options, deps);
assert(notifier._notifier instanceof FSNotifier);
});
});

View File

@ -8,7 +8,7 @@ import * as speakeasy from "speakeasy";
import * as u2f from "authdog";
import { AppConfiguration, UserConfiguration } from "../../src/lib/Configuration";
import { GlobalDependencies } from "../../src/lib/GlobalDependencies";
import { GlobalDependencies, Nodemailer } from "../../src/lib/Dependencies";
import Server from "../../src/lib/Server";
@ -20,18 +20,18 @@ describe("test server configuration", function () {
sendMail: sinon.stub().yields()
};
const nodemailer = {
const nodemailer: Nodemailer = {
createTransport: sinon.spy(function () {
return transporter;
})
};
deps = {
nodemailer: nodemailer,
speakeasy: speakeasy,
u2f: u2f,
nedb: nedb,
winston: winston,
nodemailer: nodemailer,
ldapjs: {
createClient: sinon.spy(function () {
return { on: sinon.spy() };
@ -57,8 +57,8 @@ describe("test server configuration", function () {
},
notifier: {
gmail: {
user: "user@example.com",
pass: "password"
username: "user@example.com",
password: "password"
}
}
} as UserConfiguration;

View File

@ -0,0 +1,69 @@
import Server from "../../src/lib/Server";
import { UserConfiguration } from "../../src/lib/Configuration";
import { GlobalDependencies } from "../../src/lib/Dependencies";
import * as express from "express";
const sinon = require("sinon");
const assert = require("assert");
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 = {
nodemailer: nodemailer,
speakeasy: sinon.spy(),
u2f: sinon.spy(),
nedb: require("nedb"),
winston: sinon.spy(),
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 = {
notifier: {
gmail: {
username: "user@example.com",
password: "password"
}
},
session: {
domain: "example.com",
secret: "secret"
},
ldap: {
url: "http://ldap",
base_dn: "cn=test,dc=example,dc=com",
user: "user",
password: "password"
}
};
const server = new Server();
server.start(config, deps);
assert(deps.session.calledOnce);
assert.equal(deps.session.getCall(0).args[0].cookie.domain, "example.com");
});
});